Image by Pexels from Pixabay
原生YOLO是用C语言写的,有人已经将YOLO移植到了Python上,有各种框架的实现,今天教你不使用PyTorch、Tensorflow之类的框架,只使用OpenCV和Numpy将YOLO 3跑起来。
我使用的OpenCV是4.4.0,3.X的版本我没试过,但是应该也可以跑,直接上代码。
"""此处源码来自https://www.learnopencv.com/deep-learning-based-object-detection-using-yolov3-with-opencv-python-c/这个连结不只包含Python代码,还有C++代码,我只整理(并且简化)了Python的部分"""import cv2import numpy as npclass OpenCVDarknet: """运行YOLO进行图像物件识别的类""" conf_threshold: float = 0.5 # 置信度阈值 nms_threshold: float = 0.4 # Non-maximum suppression threshold inp_width: float = 416 inp_height: float = 416 # 输入图像的宽高 def __init__(self, model_configuration, model_weights, classes_file): with open(classes_file, 'rt') as f: self.classes = f.read().rstrip('\n').split('\n') self.net = cv2.dnn.readNet(cv2.samples.findFile(model_weights), cv2.samples.findFile(model_configuration), 'darknet') # 或者上面写法换成这种 # self.net = cv2.dnn.readNetFromDarknet(model_configuration, model_weights) self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) def get_outputs_names(self, net): """获取输出层的名称""" # 获取网络中所有层的名称 layers_names = self.net.getLayerNames() # 获取输出图层的名称 return [ layers_names[i[0] - 1] for i in self.net.getUnconnectedOutLayers() ] def draw_pred(self, image, class_id, conf, left, top, right, bottom): """绘制预测的边界框""" # 绘制一个边界框 cv2.rectangle(image, (left, top), (right, bottom), (0, 0, 255)) # 获取类别名称及其置信度的标签 if self.classes: assert (class_id < len(self.classes)) label = '%s:%s' % (self.classes[class_id], format(conf, ".2%")) # 在边框上方显示标签 label_size, base_line = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1) top = max(top, label_size[1]) cv2.putText(image, label, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255)) def post_process(self, image, outs): """移除低置信度的识别结果""" image_height, image_width = image.shape[:2] # 扫描从网络输出的所有边界框,并仅保留其中具有高置信度得分的。 # 向box赋予得分最高的类别标签。 class_ids, confidences, boxes = [], [], [] for out in outs: for detection in out: scores = detection[5:] class_id = np.argmax(scores) confidence = scores[class_id] if confidence > self.conf_threshold: center_x = int(detection[0] * image_width) center_y = int(detection[1] * image_height) width = int(detection[2] * image_width) height = int(detection[3] * image_height) left = int(center_x - width / 2) top = int(center_y - height / 2) class_ids.append(class_id) confidences.append(float(confidence)) boxes.append([left, top, width, height]) # 削除具有较低置信度的冗余重叠框 indices = cv2.dnn.NMSBoxes(boxes, confidences, self.conf_threshold, self.nms_threshold) for i in indices: i = i[0] box = boxes[i] left = box[0] top = box[1] width = box[2] height = box[3] self.draw_pred(image, class_ids[i], confidences[i], left, top, left + width, top + height) def __call__(self, image, output_file): if isinstance(image, str): image = cv2.imread(image, cv2.IMREAD_COLOR) assert isinstance(image, np.ndarray), \ "传入的参数错误,应当是图片文件名或者使用OpenCV读取的图片" # 从图片新建一个4D blob blob = cv2.dnn.blobFromImage(image, 1 / 255, (self.inp_width, self.inp_height), [0, 0, 0], 1, crop=False) self.net.setInput(blob) # 进行识别 outs = self.net.forward(self.get_outputs_names(self.net)) # 移除低置信度的识别结果 self.post_process(image, outs) t, _ = self.net.getPerfProfile() print('Inference time: %.2f ms' % (t * 1000.0 / cv2.getTickFrequency())) # 将识别结果保存到一张图片中 cv2.imwrite(output_file, image.astype(np.uint8))net = OpenCVDarknet("./yolov4.cfg", "./yolov4.weights", "coco.names")net("test.jpg", "result.jpg")
这个代码主干是我从别处抄的,源网页的下载代码功能在我这不好使,并且他的代码在我这显示出来的时候很乱,甚至完全没有缩进,我一点一点根据他的意思自己整理的缩进
。可能是我这网络环境不行,如果读者有幸能够加载代码格式的话则可以直接检视原文,不用看我这个简化版的。
下面进行一下简单讲解。此处我使用的是YOLO V3,V4我也试成功了,但是需要OpenCV比较新的版本才能跑起来,如果读者还没用上最新版本,可以更新OpenCV或者用YOLO V3,用得到的下载地址我会统一放在文章末尾(我给的是官方的连结,比较推荐的方式是到搜索引擎搜一下看有没有云盘连结,这种方式更加快捷)。
代码开篇给了四个超参数,分别是两个跟置信度有关的阈值和图片的宽高,如果有性能和准确度方面的特殊需求可以自行调整。宽高我这里设定为416,也可以设定为320提高速度,或者设定为608提高精度。
原本代码中提供了读取摄像头或者视频的功能,但是我这里为了简单就给删去了,如果读者对OpenCV处理视频感兴趣的话我可以出一篇文章讲讲这个话题。
具体使用起来,形如上面120行代码,初始化OpenCVDarknet类,指定必要的文件路径参数,使用时只需要指定图片的输入文件名和输出文件名就好了。这样设计的好处就是使用起来相当简单,如果你想要处理视频或者摄像头的图像,可以直接写一个对应功能的函数,将初始化的OpenCVDarknet类实例包裹进去就行了,可以做到开箱即用,非常的人性化。
这是我将宽高指定为608的识别结果,如果是上面代码中的416,则数值会有差异。
YOLO V3权重下载地址:https://pjreddie.com/media/files/yolov3.weights
YOLO V3的cfg文件:https://github.com/pjreddie/darknet/blob/master/cfg/yolov3.cfg
coco.names:https://github.com/pjreddie/darknet/blob/master/data/coco.names