在隔离期间,我花时间在github上探索Tensorflow的大量预训练模型。这样做时,我偶然发现了一个包含25 个带有性能和速度指标的预训练对象检测模型的存储库。拥有一些计算机视觉知识并给出了实际的背景知识,我认为使用其中之一来构建社交隔离应用程序可能会很有趣。
更重要的是,上学期在计算机视觉课程中向我介绍了OpenCV,并意识到在执行许多小型项目时它的功能多么强大。其中之一包括执行图片的鸟瞰转换。一个鸟瞰图是一个基本场景的自上而下的表示。这是在构建自动驾驶汽车应用程序时经常执行的任务。
车载摄像头鸟瞰系统的实现
这使我意识到,将这种技术应用于我们想要监视社会距离的场景可以提高其质量。本文介绍了我如何使用深度学习模型以及计算机视觉方面的一些知识来构建强大的社交距离探测器。
本文的结构如下:
- 选型
- 人物检测
- 鸟瞰图转换
- 社会距离测量
- 结果与改进
以下所有代码以及安装说明都可以在我的github存储库中找到。
1.选型
Tensorflow对象检测模型Zoo中可用的所有模型均已在COCO数据集(COntext中的通用对象)上进行了训练。该数据集包含120,000张图像,这些图像中总共有880,000个带标签的对象。这些模型经过训练可以检测此数据集中标记的90种不同类型的对象。所有这些不同对象的完整列表可在github repo的data部分访问的存储库的data部分中找到。这些对象包括汽车,牙刷,香蕉和人。
可用型号的非详尽清单
根据模型的速度,它们具有不同的性能。为了确定如何根据预测速度来利用模型的质量,我进行了一些测试。由于此应用程序的目标不是能够执行实时分析,因此我最终选择了fast_rcnn_inception_v2_coco ,它的mAP(验证集上的检测器性能)为28,非常强大,执行速度为58 ms 。
2.人员检测
要使用这种模型,为了检测人员,必须完成一些步骤:
- 将包含模型的文件加载到张量流图中。并定义您要从模型获得的输出。
- 对于每一帧,将图像通过图形以获取所需的输出。
- 过滤掉不需要检测的弱预测和对象。
加载并启动模型
设计张量流模型的工作方式是使用图形。第一步意味着将模型加载到张量流图中。该图将包含为了获得所需检测而将要执行的不同操作。下一步是创建一个会话,该会话是负责执行上图中定义的操作的实体。有关图形和会话的更多说明,请参见此处。我决定实现一个类,以将与张量流图有关的所有数据保持在一起。
class Model: """ Class that contains the model and all its functions """ def __init__(self, model_path): """ Initialization function @ model_path : path to the model """ # Declare detection graph self.detection_graph = tf.Graph() # Load the model into the tensorflow graph with self.detection_graph.as_default(): od_graph_def = tf.compat.v1.GraphDef() with tf.io.gfile.GFile(model_path, 'rb') as file: serialized_graph = file.read() od_graph_def.ParseFromString(serialized_graph) tf.import_graph_def(od_graph_def, name='') # Create a session from the detection graph self.sess = tf.compat.v1.Session(graph=self.detection_graph) def predict(self,img): """ Get the predicition results on 1 frame @ img : our img vector """ # Expand dimensions since the model expects images to have shape: [1, None, None, 3] img_exp = np.expand_dims(img, axis=0) # Pass the inputs and outputs to the session to get the results (boxes, scores, classes) = self.sess.run([self.detection_graph.get_tensor_by_name('detection_boxes:0'), self.detection_graph.get_tensor_by_name('detection_scores:0'), self.detection_graph.get_tensor_by_name('detection_classes:0')],feed_dict={self.detection_graph.get_tensor_by_name('image_tensor:0'): img_exp}) return (boxes, scores, classes)
通过模型传递每一帧
对于需要处理的每个帧,都会启动一个新会话。这是通过调用run()函数来完成的。这样做时必须指定一些参数。其中包括模型所需的输入类型以及我们要从中获取的输出。在我们的情况下,所需的输出如下:
- 每个对象的边界框坐标
- 每个预测的置信度(0到1)
- 预测等级(0到90)
过滤掉弱预测和不相关的对象
人物检测结果
模型检测到的许多类之一是人。与一个人关联的类别为1。
为了排除弱预测(阈值:0.75)和除人以外的所有其他类别的对象,我使用了if语句,将这两个条件组合在一起以排除任何其他对象以进行进一步计算。
if int(classes[i]) == 1 and scores[i] > 0.75
但是,由于这些模型已经过于训练,因此不可能仅检测此类。因此,这些模型要花很长时间才能运行,因为它们试图识别场景中所有90种不同类型的对象。
3.鸟瞰图转换
如引言中所述,执行鸟瞰图转换可为我们提供场景的俯视图。值得庆幸的是,OpenCV具有强大的内置功能,可以将该方法应用于图像,以便将所拍摄的图像从透视图角度转换为顶视图。我使用了伟大的Adrian Rosebrock 的教程来了解如何做到这一点。
第一步涉及在原始图像上选择4个点,这些点将成为要转换的计划的拐角点。这一点必须形成一个矩形,其中至少两个相对的侧面平行。如果不这样做,则转换发生时的比例将不同。我已经在我的存储库中实现了一个脚本,该脚本使用OpenCV 的setMouseCallback()函数来获取这些坐标。计算变换矩阵的函数还需要使用图像的image.shape属性来计算图像的尺寸。
width, height, _ = image.shape
这将返回宽度,高度和其他不相关的彩色像素值。让我们看看它们如何用于计算转换矩阵:
def compute_perspective_transform(corner_points,width,height,image):""" Compute the transformation matrix@ corner_points : 4 corner points selected from the image@ height, width : size of the imagereturn : transformation matrix and the transformed image"""# Create an array out of the 4 corner pointscorner_points_array = np.float32(corner_points)# Create an array with the parameters (the dimensions) required to build the matriximg_params = np.float32([[0,0],[width,0],[0,height],[width,height]])# Compute and return the transformation matrixmatrix = cv2.getPerspectiveTransform(corner_points_array,img_params) img_transformed = cv2.warpPerspective(image,matrix,(width,height))return matrix,img_transformed
请注意,我选择还返回矩阵,因为下一步将使用该矩阵来计算每个检测到的人的新坐标。其结果是帧中每个人的“ GPS”坐标。这是更为准确使用这些不是使用原来的地面点,因为在透视图中,距离是不一样的,当人们都在不同的计划,而不是在从相机相同的距离。与使用原始框架中的点相比,这可以大大改善社会距离度量。
对于检测到的每个人,将返回构建边界框所需的2个点。这些点是框的左上角和右下角。从这些中,我通过获取它们之间的中间点来计算盒子的质心。使用此结果,我计算了位于框底部中心的点的坐标。我认为,这一点(我称为基点)是图像中人的坐标的最佳表示。
然后,我使用变换矩阵为每个检测到的地面点计算变换后的坐标。在检测到人之后,使用cv2.perspectiveTransform()在每一帧上完成此操作。这就是我实现此任务的方式:
def compute_point_perspective_transformation(matrix,list_downoids):""" Apply the perspective transformation to every ground point which have been detected on the main frame.@ matrix : the 3x3 matrix @ list_downoids : list that contains the points to transformreturn : list containing all the new points"""# Compute the new coordinates of our pointslist_points_to_detect = np.float32(list_downoids).reshape(-1, 1, 2)transformed_points = cv2.perspectiveTransform(list_points_to_detect, matrix)# Loop over the points and add them to the list that will be returnedtransformed_points_list = list()for i in range(0,transformed_points.shape[0]):transformed_points_list.append([transformed_points[i][0][0],transformed_points[i][0][1]])return transformed_points_list
4.衡量社会距离
在每帧上调用此函数后,将返回一个包含所有新转换点的列表。从这个列表中,我不得不计算每对点之间的距离。我使用了来自itertools库的function Combines ()函数,该函数允许在列表中获取所有可能的组合而无需保留双精度。在此堆栈溢出问题上对此进行了很好的解释。剩余部分是简单的数学运算:使用python中的math.sqrt()函数很容易实现两点之间的距离。选择的阈值为120像素,因为它在我们的场景中大约等于2英尺。
# Check if 2 or more people have been detected (otherwise no need to detect) if len(transformed_downoids) >= 2: # Iterate over every possible 2 by 2 between the points combinations list_indexes = list(itertools.combinations(range(len(transformed_downoids)), 2)) for i,pair in enumerate(itertools.combinations(transformed_downoids, r=2)): # Check if the distance between each combination of points is less than the minimum distance chosen if math.sqrt( (pair[0][0] - pair[1][0])**2 + (pair[0][1] - pair[1][1])**2 ) < int(distance_minimum): # Change the colors of the points that are too close from each other to red change_color_topview(pair) # Get the equivalent indexes of these points in the original frame and change the color to red index_pt1 = list_indexes[i][0] index_pt2 = list_indexes[i][1] change_color_originalframe(index_pt1,index_pt2)
一旦确定两个点之间的距离太近,标记该点的圆圈的颜色将从绿色更改为红色,并且与原始帧上的边界框的颜色相同。
5.结果
让我回复一下该项目的工作方式:
- 首先获取计划的4个角点,然后应用透视变换获得该计划的鸟瞰图并保存变换矩阵。
- 获取原始帧中检测到的每个人的边界框。
- 计算此框的最低点。这是位于双脚之间的点。
- 使用转换矩阵对这些点中的每一个获取每个人的真实“ GPS”坐标。
- 使用itertools.combinations()测量框架中每个点到所有其他点的距离。
- 如果检测到社交距离冲突,请将边界框的颜色更改为红色。