使用SSD模型进行图像中人体检测

在本系列文章中,选择了两种基于SSD的SSD模型进行进一步的工作,一种基于MobileNet,另一种基于SqueezeNet。在本文中,将开发一些Python代码,这些代码将使能够使用这些模型在图像中检测人体。

所选的深度神经网络(DNN)实现为Caffe模型。Caffe模型由两部分组成:模型结构(.prototxt)文件和训练好的模型(.caffemodel)。Caffe模型结构以类似于JSON的格式编写。训练好的模型是CNN核和其他训练数据的二进制序列化。在本系列的第一篇文章中,提到将使用PythonOpenCV库与Caffe。这意味着什么?应该安装OpenCV和Caffe两个框架吗?幸运的是,不需要,只需要OpenCV库。这个框架包括DNN模块,它直接支持使用TensorFlow、Caffe、Torch、Darknet等开发的网络模型。所以——很幸运!——OpenCV框架允许同时使用计算机视觉算法和深度神经网络。这就是所需要的。

让从两个实用类开始Python代码:

import cv2 import numpy as np import os class CaffeModelLoader: @staticmethod def load(proto, model): net = cv2.dnn.readNetFromCaffe(proto, model) return net class FrameProcessor: def __init__(self, size, scale, mean): self.size = size self.scale = scale self.mean = mean def get_blob(self, frame): img = frame (h, w, c) = frame.shape if w > h: dx = int((w - h) / 2) img = frame[0:h, dx:dx + h] resized = cv2.resize(img, (self.size, self.size), cv2.INTER_AREA) blob = cv2.dnn.blobFromImage(resized, self.scale, (self.size, self.size), self.mean) return blob

CaffeModelLoader类有一个静态方法,用于从磁盘加载Caffe模型。FrameProcessor类旨在将图像数据转换为DNN所需的特定格式。该类的构造函数接收三个参数。size参数定义了DNN处理的输入数据大小。图像处理的卷积网络几乎总是使用正方形图像作为输入,所以只为宽度和高度指定一个值。scale和mean参数用于将数据缩放到训练SSD时使用的值范围。该类的唯一方法get_blob接收一个图像并返回一个blob——一种特殊的神经网络处理结构。要获取blob,首先将图像调整到指定的正方形大小。然后使用OpenCV的DNN模块中的blobFromImage方法以及指定的scale、size和mean值从调整大小的图像创建blob。

请注意get_blob方法开头的代码。这段代码实现了一个小“技巧”:修剪非正方形图像,只获取图像中心正方形部分,如下图所示:

这个技巧的目的是保持图像的纵横比不变。如果宽高比发生变化,图像将发生扭曲,物体检测的精度将降低。这个技巧的一个缺点是,只能在图像的中心正方形部分(上图中以蓝色显示)检测到人。

现在让看看使用SSD模型进行人体检测的主类:

class SSD: def __init__(self, frame_proc, ssd_net): self.proc = frame_proc self.net = ssd_net def detect(self, frame): blob = self.proc.get_blob(frame) self.net.setInput(blob) detections = self.net.forward() k = detections.shape[2] obj_data = [] for i in np.arange(0, k): obj = detections[0, 0, i, :] obj_data.append(obj) return obj_data def get_object(self, frame, data): confidence = int(data[2] * 100.0) (h, w, c) = frame.shape r_x = int(data[3] * h) r_y = int(data[4] * h) r_w = int((data[5] - data[3]) * h) r_h = int((data[6] - data[4]) * h) if w > h: dx = int((w - h) / 2) r_x = r_x + dx obj_rect = (r_x, r_y, r_w, r_h) return (confidence, obj_rect) def get_objects(self, frame, obj_data, class_num, min_confidence): objects = [] for (i, data) in enumerate(obj_data): obj_class = int(data[1]) obj_confidence = data[2] if obj_class == class_num and obj_confidence >= min_confidence: obj = self.get_object(frame, data) objects.append(obj) return objects

上述类的构造函数有两个参数:frame_proc用于将图像转换为blob,ssd_net用于检测物体。主要方法detect接收一个帧(图像)作为输入,并使用指定的帧处理器从帧中获取一个blob。该blob用作网络的输入,使用forward方法获取检测结果。这些检测结果呈现为4维数组(张量)。不会分析整个张量;只需要数组的第2维。将从检测结果中提取它并返回结果——一个包含物体数据的列表。

物体数据是一个实数数组。这里有一个例子:

[array([0., 15., 0.90723044, 0.56916684, 0.6017439, 0.68543154, 0.93739873], dtype=float32)]

数组包含七个数字:检测到的物体数量、其类别编号、物体属于给定类别的置信度、物体ROI的四个左上角和右下角坐标(坐标相对于blob大小)。

类的第二个方法将检测数据转换为更简单的格式以供进一步使用。它将相对置信度转换为百分比值,并将相对ROI坐标转换为整数数据——原始图像中的像素坐标。该方法考虑到blob数据是从原始帧的中心正方形提取的。

最后,get_objects方法从检测数据中提取指定类别和足够置信度的物体。由于DNN模型可以检测20种不同类别的物体,必须过滤掉只有person类的检测结果,以确保检测到的物体确实是人类,所以指定一个高置信度阈值。

还有一个实用类——用于将检测到的物体绘制到图像中以可视化结果:

class Utils: @staticmethod def draw_object(obj, label, color, frame): (confidence, (x1, y1, w, h)) = obj x2 = x1 + w y2 = y1 + h cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2) y3 = y1 - 12 text = label + " " + str(confidence) + "%" cv2.putText(frame, text, (x1, y3), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 1, cv2.LINE_AA) @staticmethod def draw_objects(objects, label, color, frame): for (i, obj) in enumerate(objects): Utils.draw_object(obj, label, color, frame)

现在可以编写代码来启动人体检测算法:

proto_file = r"C:\PI_RPD\mobilenet.prototxt" model_file = r"C:\PI_RPD\mobilenet.caffemodel" ssd_net = CaffeModelLoader.load(proto_file, model_file) print("Caffe model loaded from: " + model_file) proc_frame_size = 300 # frame processor for MobileNet ssd_proc = FrameProcessor(proc_frame_size, 1.0 / 127.5, 127.5) person_class = 15 ssd = SSD(ssd_proc, ssd_net) im_dir = r"C:\PI_RPD\test_images" im_name = "woman_640x480_01.png" im_path = os.path.join(im_dir, im_name) image = cv2.imread(im_path) print("Image read from: " + im_path) obj_data = ssd.detect(image) persons = ssd.get_objects(image, obj_data, person_class, 0.5) person_count = len(persons) print("Person count on the image: " + str(person_count)) Utils.draw_objects(persons, "PERSON", (0, 0, 255), image) res_dir = r"C:\PI_RPD\results" res_path = os.path.join(res_dir, im_name) cv2.imwrite(res_path, image) print("Result written to: " + res_path)

代码实现了一个帧处理器,size=300,因为使用的模型使用300 x 300像素大小的图像。scale和mean参数与MobileNet模型训练时使用的值相同。这些值必须始终分配给模型的训练值,否则模型的精度会降低。person_class值是15,因为在模型上下文中,human是第15类。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485