总览
介绍
我喜欢智慧城市的想法。关于自动化智能能源系统,电网,一键式访问端口的想法–这是一个令人着迷的概念!老实说,对于数据科学家来说,这是一个梦想,我很高兴世界各地的许多城市都在朝着变得更加智能化的方向迈进。
智慧城市的核心组件之一是自动交通管理。这让我开始思考–我是否可以使用数据科学知识来构建可以在智能交通管理中发挥作用的车辆检测模型?
考虑一下–如果您可以将车辆检测系统集成到交通信号灯摄像头中,则可以轻松地同时跟踪许多有用的东西:
我们人类可以轻松地从复杂场景中瞬间检测和识别物体。但是,将思维过程转化为机器,需要我们学习使用计算机视觉算法进行对象检测的技术。
因此,在本文中,我们将构建一个自动车辆检测器和计数器模型。
目录
对象检测是计算机视觉中一个引人入胜的领域。当我们处理视频数据时,它达到了一个全新的水平。复杂性提高了一个等级,但是奖励也是如此!
我们可以使用对象检测算法执行超有用的高价值任务,例如监视,交通管理,打击犯罪等。
我们可以在对象检测中执行许多子任务,例如计算对象数,找到对象的相对大小或找到对象之间的相对距离。所有这些子任务都很重要,因为它们有助于解决一些最棘手的现实问题。
如今,视频对象检测已广泛应用于各个行业。用例范围从视频监视到体育广播再到机器人导航。
这是个好消息–涉及视频对象检测和跟踪的未来用例时,可能性无穷无尽。在这里,我列出了一些有趣的应用程序:
在开始构建视频检测系统之前,您应该了解一些关键概念。一旦熟悉了这些基本概念,就可以针对您选择的任何用例构建自己的检测系统。
那么,您想如何检测视频中的运动物体?
我们的目标是捕获运动对象的坐标并在视频中突出显示该对象。考虑以下视频中的这一帧:
我们希望我们的模型能够检测视频中的运动对象,如上图所示。检测到正在行驶的汽车,并在汽车周围创建一个边界框。
有多种技术可以解决此问题。您可以训练用于对象检测的深度学习模型,也可以选择一个预先训练的模型并在数据上进行微调。但是,这些是有监督的学习方法,它们需要标记的数据来训练对象检测模型。
在本文中,我们将重点介绍视频中对象检测的无监督方式,即不使用任何标记数据的对象检测。我们将使用帧差分技术。让我们了解它是如何工作的!
视频是一组按正确顺序堆叠在一起的帧。因此,当我们看到一个对象在视频中移动时,这意味着该对象在每个连续的帧中都位于不同的位置。
如果我们假设除了那个物体,没有其他物体在一对连续的帧中移动,那么第一帧与第二帧的像素差异将突出显示移动物体的像素。现在,我们将获得运动对象的像素和坐标。大致来说,这就是帧差异方法的工作方式。
让我们举个例子。考虑视频中的以下两个帧:
您能发现两个框架之间的差异吗?
是的–是握笔的手的位置从第1帧更改为第2帧。其余对象完全没有移动。因此,正如我前面提到的,要定位运动对象,我们将执行帧差分。结果将如下所示:
您可以看到最初出现手的突出显示区域或白色区域。除此之外,记事本还沿其边缘突出了一点。这可能是由于手的运动引起的照明变化。建议摆脱对静止物体的不必要检测。因此,我们需要在帧上执行某些图像预处理步骤。
在该方法中,基于阈值为灰度图像的像素值分配代表黑色和白色的两个值之一。因此,如果像素的值大于阈值,则为其分配一个值,否则为其分配另一个值。
在我们的情况下,我们将在上一步中对差异帧的输出图像应用图像阈值处理:
您会看到不需要的突出显示区域的大部分已消失。记事本的突出显示的边缘不再可见。生成的图像也可以称为二进制图像,因为其中只有两种颜色。在下一步中,我们将看到如何捕获这些突出显示的区域。
轮廓用于识别图像中具有相同颜色或强度的区域的形状。轮廓就像是感兴趣区域周围的边界。因此,如果在阈值化步骤之后在图像上应用轮廓,则会得到以下结果:
白色区域被灰色边界包围,仅是轮廓。我们可以轻松获得这些轮廓的坐标。这意味着我们可以获取突出显示区域的位置。
请注意,存在多个突出显示的区域,每个区域都被轮廓包围。在我们的情况下,具有最大面积的轮廓是所需区域。因此,最好具有尽可能少的轮廓。
在上图中,白色区域仍然存在一些不必要的碎片。仍有改进的余地。想法是合并附近的白色区域以减少轮廓,为此,我们可以使用另一种称为图像扩张的技术。
这是对图像的卷积运算,其中内核(矩阵)在整个图像上传递。只是为了给您直观感,右边的图像是左边图像的放大版:
因此,让我们对图像进行图像放大,然后再次找到轮廓:
事实证明,很多碎片区域相互融合。现在,我们可以再次在该图像中找到轮廓:
在这里,我们只有四个候选轮廓,可以从中选择面积最大的轮廓。您还可以在原始框架上绘制这些轮廓,以查看轮廓围绕移动对象的程度:
我们都准备建立我们的车辆检测系统!在此实现中,我们将大量使用计算机视觉库OpenCV(版本4.0.0)。首先,导入所需的库和模块。
import osimport reimport cv2 # opencv libraryimport numpy as npfrom os.path import isfile, joinimport matplotlib.pyplot as plt
请从此链接下载原始视频的帧。
将框架保存在工作目录内名为“ frames”的文件夹中。从该文件夹中,我们将导入框架并将其保留在列表中:
# get file names of the framescol_frames = os.listdir('frames/') # sort file namescol_frames.sort(key=lambda f: int(re.sub('D', '', f))) # empty list to store the framescol_images=[] for i in col_frames: # read the frames img = cv2.imread('frames/'+i) # append the frames to the list col_images.append(img)
让我们显示两个连续的帧:
# plot 13th framei = 13 for frame in [i, i+1]: plt.imshow(cv2.cvtColor(col_images[frame], cv2.COLOR_BGR2RGB)) plt.title("frame: "+str(frame)) plt.show()
很难在这两个框架中找到任何区别,不是吗?如前所述,采用两个连续帧的像素值之差将有助于我们观察运动物体。因此,让我们在以上两个帧上使用该技术:
# convert the frames to grayscalegrayA = cv2.cvtColor(col_images[i], cv2.COLOR_BGR2GRAY)grayB = cv2.cvtColor(col_images[i+1], cv2.COLOR_BGR2GRAY) # plot the image after frame differencingplt.imshow(cv2.absdiff(grayB, grayA), cmap = 'gray')plt.show()
现在我们可以清楚地看到第13帧和第14帧中的运动对象。其他所有不动的东西都被减去。
让我们看看将阈值应用于上图之后会发生什么:
diff_image = cv2.absdiff(grayB, grayA) # perform image thresholdingret, thresh = cv2.threshold(diff_image, 30, 255, cv2.THRESH_BINARY) # plot image after thresholdingplt.imshow(thresh, cmap = 'gray')plt.show()
现在,运动物体(车辆)看起来更有希望,并且大部分噪音(不需要的白色区域)都消失了。但是,突出显示的区域有些零散。因此,我们可以对该图像应用图像膨胀:
# apply image dilationkernel = np.ones((3,3),np.uint8)dilated = cv2.dilate(thresh,kernel,iterations = 1) # plot dilated imageplt.imshow(dilated, cmap = 'gray')plt.show()
移动的对象具有更坚实的突出显示区域。希望框架中每个对象的轮廓数量不会超过三个。
但是,我们不会使用整个框架来检测运动中的车辆。我们将首先选择一个区域,如果车辆进入该区域,则只会检测到该区域。
因此,让我向您展示我们将与之合作的区域:
# plot vehicle detection zoneplt.imshow(dilated)cv2.line(dilated, (0, 80),(256,80),(100, 0, 0))plt.show()
水平线y = 80以下的区域是我们的车辆检测区域。我们将仅检测此区域中发生的任何移动。如果您想尝试一下该概念,则可以创建自己的检测区域。
现在,让我们在上述帧的检测区域中找到轮廓:
# find contourscontours, hierarchy = cv2.findContours(thresh.copy(),cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
上面的代码在整个图像中找到所有轮廓,并将它们保留在变量“ 轮廓”中。由于我们只需要查找检测区域中存在的轮廓,因此我们将对发现的轮廓进行几次检查。
第一个检查是轮廓的左上角y坐标是否应该> = 80(我还要再检查x坐标<= 200)。另一个检查是轮廓区域应大于等于25。您可以在cv2.contourArea()函数的帮助下找到轮廓区域。
valid_cntrs = [] for i,cntr in enumerate(contours): x,y,w,h = cv2.boundingRect(cntr) if (x <= 200) & (y >= 80) & (cv2.contourArea(cntr) >= 25): valid_cntrs.append(cntr) # count of discovered contours len(valid_cntrs)
接下来,让我们绘制轮廓以及原始框架:
dmy = col_images[13].copy() cv2.drawContours(dmy, valid_cntrs, -1, (127,200,0), 2)cv2.line(dmy, (0, 80),(256,80),(100, 255, 255))plt.imshow(dmy)plt.show()
仅显示位于检测区域内的那些车辆的轮廓。这就是我们将如何检测所有帧中的车辆。
现在是时候在所有帧上应用相同的图像转换和预处理操作,并找到所需的轮廓了。重申一下,我们将按照以下步骤操作:
# kernel for image dilationkernel = np.ones((4,4),np.uint8) # font stylefont = cv2.FONT_HERSHEY_SIMPLEX # directory to save the ouput framespathIn = "contour_frames_3/" for i in range(len(col_images)-1): # frame differencing grayA = cv2.cvtColor(col_images[i], cv2.COLOR_BGR2GRAY) grayB = cv2.cvtColor(col_images[i+1], cv2.COLOR_BGR2GRAY) diff_image = cv2.absdiff(grayB, grayA) # image thresholding ret, thresh = cv2.threshold(diff_image, 30, 255, cv2.THRESH_BINARY) # image dilation dilated = cv2.dilate(thresh,kernel,iterations = 1) # find contours contours, hierarchy = cv2.findContours(dilated.copy(), cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE) # shortlist contours appearing in the detection zone valid_cntrs = [] for cntr in contours: x,y,w,h = cv2.boundingRect(cntr) if (x <= 200) & (y >= 80) & (cv2.contourArea(cntr) >= 25): if (y >= 90) & (cv2.contourArea(cntr) < 40): break valid_cntrs.append(cntr) # add contours to original frames dmy = col_images[i].copy() cv2.drawContours(dmy, valid_cntrs, -1, (127,200,0), 2) cv2.putText(dmy, "vehicles detected: " + str(len(valid_cntrs)), (55, 15), font, 0.6, (0, 180, 0), 2) cv2.line(dmy, (0, 80),(256,80),(100, 255, 255)) cv2.imwrite(pathIn+str(i)+'.png',dmy)
在这里,我们为所有框架中的所有移动车辆添加了轮廓。现在是时候堆叠帧并创建视频了:
#指定视频名称pathOut ='vehicle_detection_v3.mp4'#每秒指定帧fps = 14.0
接下来,我们将阅读列表中的最后几帧:
frame_array = []files = [f for f in os.listdir(pathIn) if isfile(join(pathIn, f))]
files.sort(key=lambda f: int(re.sub('D', '', f))) for i in range(len(files)): filename=pathIn + files[i] #read frames img = cv2.imread(filename) height, width, layers = img.shape size = (width,height) #inserting the frames into an image array frame_array.append(img)
最后,我们将使用以下代码制作目标检测视频:
out = cv2.VideoWriter(pathOut,cv2.VideoWriter_fourcc(*'DIVX'), fps, size) for i in range(len(frame_array)): # writing to a image array out.write(frame_array[i]) out.release()
祝贺您建立了自己的车辆目标检测!