基于YOLOv5深度学习的车距检测系统(单目测距+源码+远程部署)
目录
核心源码
main.py
import argparse import torch.backends.cudnn as cudnn from models.experimental import attempt_load from utils.dataloaders import LoadStreams, LoadImages from utils.general import * from utils.plots import * from utils.torch_utils import * import plotly.graph_objects as go start_time = time.time() print('Pandas Version:', pd.__version__) print('Nunpy Version:', np.__version__) @torch.no_grad() class DistanceEstimation: def __init__(self): self.W = 640 self.H = 480 self.excel_path = r'./camera_parameters.xlsx' def camera_parameters(self, excel_path): df_intrinsic = pd.read_excel(excel_path, sheet_name='内参矩阵', header=None) df_p = pd.read_excel(excel_path, sheet_name='外参矩阵', header=None) print('外参矩阵形状:', df_p.values.shape) print('内参矩阵形状:', df_intrinsic.values.shape) return df_p.values, df_intrinsic.values def object_point_world_position(self, u, v, w, h, p, k): u1 = u v1 = v + h / 2 print('关键点坐标:', u1, v1) alpha = -(90 + 0) / (2 * math.pi) peta = 0 gama = -90 / (2 * math.pi) fx = k[0, 0] fy = k[1, 1] H = 1 angle_a = 0 angle_b = math.atan((v1 - self.H / 2) / fy) angle_c = angle_b + angle_a print('angle_b', angle_b) depth = (H / np.sin(angle_c)) * math.cos(angle_b) print('depth', depth) k_inv = np.linalg.inv(k) p_inv = np.linalg.inv(p) # print(p_inv) point_c = np.array([u1, v1, 1]) point_c = np.transpose(point_c) print('point_c', point_c) print('k_inv', k_inv) c_position = np.matmul(k_inv, depth * point_c) print('c_position', c_position) c_position = np.append(c_position, 1) c_position = np.transpose(c_position) c_position = np.matmul(p_inv, c_position) d1 = np.array((c_position[0], c_position[1]), dtype=float) return d1 def distance(self, kuang, xw=5, yw=0.1): print('=' * 50) print('开始测距') fig = go.Figure() p, k = self.camera_parameters(self.excel_path) if len(kuang): obj_position = [] u, v, w, h = kuang[1] * self.W, kuang[2] * self.H, kuang[3] * self.W, kuang[4] * self.H print('目标框', u, v, w, h) d1 = self.object_point_world_position(u, v, w, h, p, k) distance = 0 print('距离', d1) if d1[0] <= 0: d1[:] = 0 else: distance = math.sqrt(math.pow(d1[0], 2) + math.pow(d1[1], 2)) return distance, d1 def Detect(self, weights='yolov5s.pt', source='data/images', # file/dir/URL/glob, 0 for webcam imgsz=640, # inference size (pixels) conf_thres=0.25, # confidence threshold iou_thres=0.45, # NMS IOU threshold max_det=1000, # maximum detections per image device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu view_img=False, # show results save_txt=False, # save results to *.txt save_conf=False, # save confidences in --save-txt labels save_crop=False, # save cropped prediction boxes nosave=False, # do not save images/videos classes=None, # filter by class: --class 0, or --class 0 2 3 agnostic_nms=False, # class-agnostic NMS augment=False, # augmented inference update=False, # update all models project='inference/output', # save results to project/name name='exp', # save results to project/name exist_ok=False, # existing project/name ok, do not increment line_thickness=3, # bounding box thickness (pixels) hide_labels=False, # hide labels hide_conf=False, # hide confidences half=False, # use FP16 half-precision inference ): save_img = not nosave and not source.endswith('.txt') # save inference images webcam = source.isnumeric() or source.endswith('.txt') or source.lower().startswith( ('rtsp://', 'rtmp://', 'http://', 'https://')) #save_dir = increment_path(Path(project) / name, exist_ok=exist_ok) # increment run #(save_dir / 'labels' if save_txt else save_dir).mkdir(parents=True, exist_ok=True) # make dir save_dir = Path(project) # Initialize set_logging() device = select_device(device) half &= device.type != 'cpu' # half precision only supported on CUDA 仅在使用CUDA时采用半精度 # Load model model = attempt_load(weights, map_location=device) # load FP32 model stride = max(int(model.stride.max()), 32) # model stride names = model.module.names if hasattr(model, "module") else model.names # get class names if half: model.half() # to FP16 # Set Dataloader vid_path, vid_writer = None, None if webcam: view_img = check_imshow() cudnn.benchmark = True # set True to speed up constant image size inference dataset = LoadStreams(source, img_size=imgsz, stride=stride) else: dataset = LoadImages(source, img_size=imgsz, stride=stride) # Run inference if device.type != 'cpu': model(torch.zeros(1, 3, imgsz, imgsz).to(device).type_as(next(model.parameters()))) # run once t0 = time.time() for path, img, im0s, vid_cap in dataset: img = torch.from_numpy(img).to(device) img = img.half() if half else img.float() # uint8 to fp16/32 img /= 255.0 # 0 - 255 to 0.0 - 1.0 if img.ndimension() == 3: img = img.unsqueeze(0) # Inference t1 = time_synchronized() pred = model(img, augment=augment)[0] # Apply NMS pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det) t2 = time_synchronized() # Process detections 检测过程 for i, det in enumerate(pred): # detections per image if webcam: # batch_size >= 1 p, s, im0, frame = path[i], f'{i}: ', im0s[i].copy(), dataset.count #path[i]为source 即为0 else: p, s, im0, frame = path, '', im0s.copy(), getattr(dataset, 'frame', 0) p = Path(p) # to Path p为inference/images/demo_distance.mp4 save_path = str(save_dir / p.name) # img.jpg inference/output/demo_distance.mp4 txt_path = str(save_dir / p.stem) + ('' if dataset.mode == 'image' else f'_{frame}') # img.txt inference/output/demo_distance_frame #print('txt', txt_path) s += '%gx%g ' % img.shape[2:] # print string 图片形状 eg.640X480 gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] # normalization gain whwh imc = im0.copy() if save_crop else im0 # for save_crop if len(det): # Rescale boxes from img_size to im0 size det[:, :4] = scale_coords(img.shape[2:], det[:, :4], im0.shape).round() # Print results for c in det[:, -1].unique(): if not names[int(c)] in ['person', 'car', 'truck', 'bicycle', 'motorcycle', 'bus', 'traffic light', 'stop sign']: continue n = (det[:, -1] == c).sum() # detections per class s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string # Write results for *xyxy, conf, cls in reversed(det): if not names[int(cls)] in ['person','chair', 'car', 'truck', 'bicycle', 'motorcycle', 'bus', 'traffic light', 'stop sign']: continue n = (det[:, -1] == c).sum() # detections per class s += f"{n} {names[int(c)]}{'s' * (n > 1)}, " # add to string xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() kuang = [int(cls), xywh[0], xywh[1], xywh[2], xywh[3]] if save_txt: # Write to file xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized with open(txt_path + '.txt', 'a') as f: f.write(('%g ' * 5 + '\n') % (int(cls), *xywh)) distance, d = self.distance(kuang) if save_img or save_crop or view_img: # Add bbox to image c = int(cls) # integer class label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}') if label != None and distance!=0: label = label + ' ' + str('%.1f' % d[0]) + 'm'+ str('%.1f' % d[1]) + 'm' plot_one_box(xyxy, im0, label=label, color=colors(c, True), line_thickness=line_thickness) if save_crop: save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True) # Print time (inference + NMS) print(f'{s}Done. ({t2 - t1:.3f}s)') # Stream results if view_img: cv2.imshow(str(p), im0) cv2.waitKey(1) # 1 millisecond # Save results (image with detections) if save_img: if dataset.mode == 'image': cv2.imwrite(save_path, im0) else: # 'video' or 'stream' if vid_path != save_path: # new video vid_path = save_path if isinstance(vid_writer, cv2.VideoWriter): vid_writer.release() # release previous video writer if vid_cap: # video fps = vid_cap.get(cv2.CAP_PROP_FPS) w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) else: # stream fps, w, h = 30, im0.shape[1], im0.shape[0] save_path += '.mp4' vid_writer = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h)) vid_writer.write(im0) if save_txt or save_img: s = f"\n{len(list(save_dir.glob('labels/*.txt')))} labels saved to {save_dir / 'labels'}" if save_txt else '' print(f"Results saved to {save_dir}{s}") if update: strip_optimizer(weights) # update model (to fix SourceChangeWarning) print(f'Done. ({time.time() - t0:.3f}s)') if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--weights', nargs='+', type=str, default='weights/yolov5s.pt', help='model.pt path(s)') # parser.add_argument('--source', type=str, default='inference/images/demo_distance.mp4', help='inference/dir/URL/glob, 0 for webcam') parser.add_argument('--source', type=str, default='inference/inputs', help='inference/dir/URL/glob, 0 for webcam') parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=1440, help='inference size (pixels)') parser.add_argument('--conf-thres', type=float, default=0.5, help='confidence threshold') parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold') parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image') parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') parser.add_argument('--view-img', action='store_true', help='show results') parser.add_argument('--save_txt',default=False, action='store_true', help='save results to *.txt') parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels') parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes') parser.add_argument('--nosave', action='store_true', help='do not save images/videos') parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --class 0, or --class 0 2 3') parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS') parser.add_argument('--augment', action='store_true', help='augmented inference') parser.add_argument('--update', action='store_true', help='update all models') parser.add_argument('--project', default='inference/output', help='save results to project/name') #保存地址 parser.add_argument('--name', default='exp', help='save results to project/name') parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment') parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)') parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels') parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences') parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference') opt = parser.parse_args() print(opt) check_requirements(exclude=('tensorboard', 'thop')) print('开始进行目标检测和单目测距!') DE = DistanceEstimation() DE.Detect(**vars(opt)) if time.time()>(start_time + 10): cv2.waitKey (0) cv2.destroyAllWindows()
detect.py
import os import shutil import xml.etree.ElementTree as ET import json import tensorflow as tf import matplotlib.pyplot as plt coco = dict() coco['images'] = [] coco['type'] = 'instances' coco['annotations'] = [] coco['categories'] = [] category_set = dict() image_set = set() category_item_id = 0 image_id = 20200000000 annotation_id = 0 def addCatItem(name): global category_item_id category_item = dict() category_item['supercategory'] = 'none' category_item_id += 1 category_item['id'] = category_item_id category_item['name'] = name coco['categories'].append(category_item) category_set[name] = category_item_id return category_item_id def addImgItem(file_name, size): global image_id if file_name is None: raise Exception('Could not find filename tag in xml file.') if size['width'] is None: raise Exception('Could not find width tag in xml file.') if size['height'] is None: raise Exception('Could not find height tag in xml file.') image_id += 1 image_item = dict() image_item['id'] = image_id image_item['file_name'] = file_name image_item['width'] = size['width'] image_item['height'] = size['height'] coco['images'].append(image_item) image_set.add(file_name) return image_id def addAnnoItem(object_name, image_id, category_id, bbox): global annotation_id annotation_item = dict() annotation_item['segmentation'] = [] seg = [] # bbox[] is x,y,w,h # left_top seg.append(bbox[0]) seg.append(bbox[1]) # left_bottom seg.append(bbox[0]) seg.append(bbox[1] + bbox[3]) # right_bottom seg.append(bbox[0] + bbox[2]) seg.append(bbox[1] + bbox[3]) # right_top seg.append(bbox[0] + bbox[2]) seg.append(bbox[1]) annotation_item['segmentation'].append(seg) annotation_item['area'] = bbox[2] * bbox[3] annotation_item['iscrowd'] = 0 annotation_item['ignore'] = 0 annotation_item['image_id'] = image_id annotation_item['bbox'] = bbox annotation_item['category_id'] = category_id annotation_id += 1 annotation_item['id'] = annotation_id coco['annotations'].append(annotation_item) def parseXmlFiles(xml_path): for f in os.listdir(xml_path): if not f.endswith('.xml'): continue bndbox = dict() size = dict() current_image_id = None current_category_id = None file_name = None size['width'] = None size['height'] = None size['depth'] = None xml_file = os.path.join(xml_path, f) tree = ET.parse(xml_file) root = tree.getroot() if root.tag != 'annotation': raise Exception('pascal voc xml root element should be annotation, rather than {}'.format(root.tag)) # elem is <folder>, <filename>, <size>, <object> for elem in root: current_parent = elem.tag current_sub = None object_name = None if elem.tag == 'folder': continue if elem.tag == 'filename': file_name = elem.text if file_name in category_set: raise Exception('file_name duplicated') # add img item only after parse <size> tag elif current_image_id is None and file_name is not None and size['width'] is not None: if file_name not in image_set: current_image_id = addImgItem(file_name, size) #print('add image with {} and {}'.format(file_name, size)) else: raise Exception('duplicated image: {}'.format(file_name)) # subelem is <width>, <height>, <depth>, <name>, <bndbox> for subelem in elem: bndbox['xmin'] = None bndbox['xmax'] = None bndbox['ymin'] = None bndbox['ymax'] = None current_sub = subelem.tag if current_parent == 'object' and subelem.tag == 'name': object_name = subelem.text if object_name not in category_set: current_category_id = addCatItem(object_name) else: current_category_id = category_set[object_name] elif current_parent == 'size': if size[subelem.tag] is not None: raise Exception('xml structure broken at size tag.') size[subelem.tag] = int(subelem.text) # option is <xmin>, <ymin>, <xmax>, <ymax>, when subelem is <bndbox> for option in subelem: if current_sub == 'bndbox': if bndbox[option.tag] is not None: raise Exception('xml structure corrupted at bndbox tag.') bndbox[option.tag] = int(option.text) # only after parse the <object> tag if bndbox['xmin'] is not None: if object_name is None: raise Exception('xml structure broken at bndbox tag') if current_image_id is None: raise Exception('xml structure broken at bndbox tag') if current_category_id is None: raise Exception('xml structure broken at bndbox tag') bbox = [] # x bbox.append(bndbox['xmin']) # y bbox.append(bndbox['ymin']) # w bbox.append(bndbox['xmax'] - bndbox['xmin']) # h bbox.append(bndbox['ymax'] - bndbox['ymin']) # print('add annotation with {},{},{},{}'.format(object_name, current_image_id, current_category_id, bbox)) addAnnoItem(object_name, current_image_id, current_category_id, bbox) print('完成xml到jsion格式转换!') print('目标的种类及编号为:', coco['categories']) def del_images(image_path, ann_path): ''' 该函数用于删除未标注的图片 :param xml_path: xml标注文件夹绝对路径 :param image_path: 图片文件夹绝对路径 :return: 删除的图片列表 ''' xmls = os.listdir(ann_path) images = os.listdir(image_path) del_images_list = [] xml_index = [] for xml in xmls: xml_index.append(xml.split('.')[0]) for image in images: image_index = image.split('.')[0] if image_index not in xml_index and os.path.isfile(os.path.join(image_path, image)): os.remove(os.path.join(image_path, image)) print('删除:',image) del_images_list.append(image) print('完成删除未标注图片{0}张'.format(len(del_images_list ))) print('删除的图片名列表:', del_images) xmls = os.listdir(ann_path) images = os.listdir(image_path) print('图片张数:',len(images)) print('标注文件数:', len(xmls)) if len(images) != len(xmls): print('错误:图片与标注文件不对应!') else: print('图片与标注文件数相等') return del_images_list def rename(image_path, ann_path): ''' rename函数根据图像名找到对应的标注文件,然后将两个文件改成相同的数字名 :param image_path: :param ann_path: :return: ''' image_names = os.listdir(image_path) ann_names = os.listdir(ann_path) print('图片数量:{0},标注文件数量{1}'.format(len(image_names), len(ann_names))) if len(image_names) != len(ann_names): print('图片与标注文件数量相等') i = 0 for image_name in image_names: image_oldname = os.path.join(image_path, image_name) index = image_name.split('.')[0] ann_name = index + ".xml" ann_oldname = os.path.join(ann_path, ann_name) las = image_name.split('.')[1] im_newname = str(i) + '.' + las an_newname = str(i) + '.xml' image_newname = os.path.join(image_path, im_newname) ann_newname = os.path.join(ann_path, an_newname) i += 1 os.rename(image_oldname, image_newname) os.rename(ann_oldname, ann_newname) print('完成图像与对应标注文件的重命名') def split_data(image_path, ann_path, save_split_path, rate = 0.8): ''' 按比例rate将数据集划分为训练集和验证集,并检查图片与标注文件的对应性 :param image_path: 已标注图片路径 :param ann_path: 标注文件路径 :param save_split_path: 划分数据集保存的路径 :param rate: 划分给训练集的比例 :return: 返回训练集和测试集的图片与标注文件路径 ''' if not os.path.exists(save_split_path): os.mkdir(save_split_path) else: shutil.rmtree(save_split_path) os.mkdir(save_split_path) ann_train_path = os.path.join(save_split_path,'ann_train/') ann_val_path = os.path.join(save_split_path,'ann_val/') image_train_path = os.path.join(save_split_path,'images_train/') image_val_path = os.path.join(save_split_path,'images_val/') # 创建文件夹 os.mkdir(ann_train_path) os.mkdir(ann_val_path) os.mkdir(image_train_path) os.mkdir(image_val_path) print('清空文件夹') images_names = os.listdir(image_path) # 取图片的原始路径 images_number = len(images_names) ann_names = os.listdir(ann_path) ann_number = len(ann_names) if images_number != ann_number: print('错误:图片数与标注文件数不相等') # 自定义抽取训练图片的比例,比方说100张抽10张,那就是0.1 sample_number = int(images_number * rate) # 按照rate比例从文件夹中取一定数量图片 for name in images_names[0:sample_number]: shutil.copy(image_path + name, image_train_path + name) for name in ann_names[0:sample_number]: shutil.copy(ann_path + name, ann_train_path + name) for name in images_names[sample_number:images_number+1]: shutil.copy(image_path + name, image_val_path + name) for name in ann_names[sample_number:images_number+1]: shutil.copy(ann_path + name, ann_val_path + name) print('完成训练集({0})与测试集({1})划分'.format(round(rate,1),round((1-rate), 1))) print('图片总数为{0},标注文件总数为{1}'.format(images_number, ann_number)) print('{0} 张图片用于训练,{1} 张图片用于验证'.format(sample_number, images_number - sample_number)) # 检验图片与标注的匹配关系 image_train_names = os.listdir(image_train_path) ann_train_names = os.listdir(ann_train_path) count = 0 for i in range(len(image_train_names)): if image_train_names[i].split('.')[0] != ann_train_names[i].split('.')[0]: print('{0} 图片与{1}标注文件不匹配'.format(image_train_names[i][0]+image_train_names[i][1], ann_train_names[i][ann_train_names[i][1]])) count +=1 if count == 0: print('训练集所有图片与标注文件一一对应') else: print('训练集图片与标注文件不匹配数目:',count) image_val_names = os.listdir(image_val_path) ann_val_names = os.listdir(ann_val_path) c = 0 for i in range(len(image_val_names)): if image_val_names[i].split('.')[0] != ann_val_names[i].split('.')[0]: print('{0} 图片与{1}标注文件不匹配'.format(image_val_names[i][0]+image_val_names[i][1], ann_val_names[i][ann_val_names[i][1]])) c +=1 if count == 0: print('验证集所有图片与标注文件一一对应') else: print('验证集图片与标注文件不匹配数目:', c) return image_train_path, image_val_path, ann_train_path, ann_val_path def voc2coco_json(image_path, ann_path, save_split_path,save_coco_path): del_iammges = del_images(image_path, ann_path) if not os.path.exists(save_coco_path): os.mkdir(save_coco_path) else: shutil.rmtree(save_coco_path) os.mkdir(save_coco_path) annotations_path = os.path.join(save_coco_path,'annotations/') train2017_path = os.path.join(save_coco_path,'train2017/') val2017_path = os.path.join(save_coco_path,'val2017/') os.mkdir(annotations_path) os.mkdir(train2017_path) os.mkdir(val2017_path) image_train_path, image_val_path, ann_train_path, ann_val_path=split_data(image_path, ann_path, save_split_path, rate = 0.8) json_file = [os.path.join(annotations_path, 'instances_train2017.json'), os.path.join(annotations_path, 'instances_val2017.json')] ann_path = [ann_train_path, ann_val_path] for i in range(len(ann_path)): parseXmlFiles(ann_path[i]) json.dump(coco, open(json_file[i], 'w')) images_train = os.listdir(image_train_path) images_val = os.listdir(image_val_path) for name in images_train: shutil.copy(image_train_path+name, train2017_path+name) for name in images_val: shutil.copy(image_val_path+name, val2017_path+name) print('完成数据清洗、拆分、xlm到json转换') def data_augment(image_path, save_image_path = None): #images = os.listdir(image_path) image_string = tf.io.read_file(image_path) image = tf.image.decode_jpeg(image_string, channels = 3) # 翻转图像(垂直和水平) flipped_h = tf.image.flip_left_right(image) flipped_v = tf.image.flip_up_down(image) bright_0 = tf.image.adjust_brightness(image, 0.2) bright_5 = tf.image.adjust_brightness(image, 0.5) bright_8 = tf.image.adjust_brightness(image, 0.6) bright_10 = tf.image.adjust_brightness(image, 0.8) grayscaled = tf.image.rgb_to_grayscale(image) saturated_3 = tf.image.adjust_saturation(image, 3) saturated_8 = tf.image.adjust_saturation(image, 8) #visualize(image, bright_0) #visualize(image, flipped_h) #visualize(image, flipped_v) # visualize(image, tf.squeeze(grayscaled)) visualize(image, saturated_3) def visualize(original, augmented): plt.figure(figsize = (20, 10)) plt.subplot(1, 2, 1) plt.title("Original Picture", fontsize=50, fontweight='bold') # plt.axis("off") # 关闭坐标轴显示 #plt.imshow(original) plt.subplot(1, 2, 2) plt.title("saturation 3", fontsize=50, fontweight='bold') # plt.axis("off") # 关闭坐标轴显示 #plt.imshow(augmented) plt.xticks(fontsize = 30) plt.yticks(fontsize = 30) plt.tight_layout() plt.savefig('./saturation3.png') #plt.show() if __name__ == '__main__': image_path = "D:/PythonFile/aicar/aicar标志物数据集/JPEGImages/" ann_path = "D:/PythonFile/aicar/aicar标志物数据集/Annotations/" save_coco_path ="D:/PythonFile/aicar/coco2017/" save_split_path = "D:/PythonFile/aicar/splitdata/" #voc2coco_json(image_path, ann_path, save_split_path, save_coco_path) data_augment("D:\PythonFile\shangqi\ObjectDistance\dog.png")
安装环境
runs文件夹中,存放训练和评估的结果图
环境安装:
请按照给定的python版本配置环境,否则可能会因依赖不兼容而出错,
在文件目录下cmd进入终端
(1)使用anaconda新建python3.10环境:
conda create -n env_rec python=3.10
(2)激活创建的环境:
conda activate env_rec
(3)使用pip安装所需的依赖,可通过requirements.txt:
pip install -r requirements.txt
在settings中找到project python interpreter 点击Add Interpreter
点击conda,在Use existing environment中选择刚才创建的虚拟环境 ,最后点击确定。如果conda Executable中路径没有,那就把anaconda3的路径添加上