图像处理:实时联邦快递标识检测的算法改进
图像处理:实时联邦快递标识检测的算法改进
我一直在做一个涉及图像处理的Logo检测项目。具体来说,目标是开发一个实时快递物流的自动化系统,该系统从IP相机流中读取画面并在检测到物流车和Logo时发送通知。以下是系统的一些操作实例,其中包含了在绿色矩形框内识别出来的Logo。
该项目的一些限制:
- 仅使用原生的OpenCV(无深度学习、AI或经过训练的神经网络)
- 图像背景可能会噪声干扰
- 图像亮度可能会有很大变化(清晨、下午、晚上)
- 快递物流车/Logo可以具有任何比例、旋转或方向,因为它可能停在人行道的任何地方
- Logo可能会有不同的阴影和模糊程度,具体取决于时间的变化
- 在同一帧中可能会有许多其他具有相似大小或颜色的车辆
- 实时检测(IP相机的帧率为25 FPS)
- IP相机处于固定位置,快递物流车将始终处于相同的方向位置(永远不会倒置或倒立)
- 快递车将始终是“红色”版本,而不是“绿色”版本
当前实现/算法
我有两个线程:
- 线程 #1 - 使用
cv2.VideoCapture()
捕获IP相机的帧并调整帧大小以便进一步处理。决定在单独的线程中处理抓取帧以通过减少I / O延迟来提高FPS,因为cv2.VideoCapture()
是阻塞式的。通过为捕获帧专门分配独立的线程,这将允许主处理线程始终有一个可用的帧来执行检测。 - 线程#2 -主要处理/检测线程,以使用颜色阈值和轮廓检测检测FedEx标志。
总体伪算法
For each frame: Find bounding box for purple color of logo Find bounding box for red/orange color of logo If both bounding boxes are valid/adjacent and contours pass checks: Combine bounding boxes Draw combined bounding boxes on original frame Play sound notification for detected logo
用于标志检测的颜色阈值
对于颜色阈值,我为紫色和红色定义了HSV(低,高)阈值以检测标志。
colors = { 'purple': ([120,45,45], [150,255,255]), 'red': ([0,130,0], [15,255,255]) }
要查找每种颜色的边界框坐标,我遵循以下算法:
- 对帧进行模糊处理
- 使用核对帧进行腐蚀和膨胀以去除背景噪声
- 将帧从BGR转换为HSV颜色格式
- 使用设置的颜色阈值在帧上执行掩码
- 在蒙版中查找最大轮廓并获取边界坐标
执行掩码后,我获得了标志的这些分离的紫色(左侧)和红色(右侧)部分。
虚假阳性检查
现在我有了这两个掩码,我执行检查以确保找到的边界框实际上形成一个标志。为此,我使用cv2.matchShapes()
进行比较并返回显示相似性的度量标准。结果越低,匹配度越高。此外,我还使用cv2.pointPolygonTest()
,这会找到图像中的点与轮廓之间的最短距离以进行额外的验证。我的虚假阳性处理过程包括:
- 检查边框是否有效
- 根据它们的相对距离确保两个边界框是相邻的
如果边界框通过了相邻性和相似性度量测试,则将边界框合并,并触发 FedEx 通知。
结果
这个检查算法并不是非常稳健,因为有很多误报和未检测到的情况。例如,这些误报被触发了。
虽然这种颜色阈值和轮廓检测方法在标志清晰的基本案例中起作用,但在一些领域严重不足:
- 每一帧都要计算边界框,存在延迟问题
- 有时会误检测,当标志不在场时
- 亮度和时间对检测精度有很大影响
- 当徽标处于倾斜角度时,颜色阈值检测起作用,但由于检查算法而无法检测到徽标。
是否有人能帮助我改进我的算法或建议替代检测策略?是否有其他方法可以执行这种检测,因为颜色阈值高度依赖于精确的校准?如果可能,我希望摆脱颜色阈值和多层过滤器,因为它不太稳健。任何见解或建议都将不胜感激!
你可以通过对图像进行预处理来帮助探测器,这样就不需要那么多的训练图像了。
首先我们减少桶形畸变。
import cv2 img = cv2.imread('fedex.jpg') margin = 150 # add border as the undistorted image is going to be larger img = cv2.copyMakeBorder( img, margin, margin, margin, margin, cv2.BORDER_CONSTANT, 0) import numpy as np width = img.shape[1] height = img.shape[0] distCoeff = np.zeros((4,1), np.float64) k1 = -4.5e-5; k2 = 0.0; p1 = 0.0; p2 = 0.0; distCoeff[0,0] = k1; distCoeff[1,0] = k2; distCoeff[2,0] = p1; distCoeff[3,0] = p2; cam = np.eye(3, dtype=np.float32) cam[0,2] = width/2.0 # define center x cam[1,2] = height/2.0 # define center y cam[0,0] = 12. # define focal length x cam[1,1] = 12. # define focal length y dst = cv2.undistort(img, cam, distCoeff)
然后我们以一种方式转换图像,就像摄像机正对着联邦快递卡车一样。也就是说,无论卡车停放在路缘的哪个位置,联邦快递标志的大小和方向几乎相同。
# use four points for homography estimation, coordinated taken from undistorted image # 1. top-left corner of F # 2. bottom-left corner of F # 3. top-right of E # 4. bottom-right of E pts_src = np.array([[1083, 235], [1069, 343], [1238, 301],[1201, 454]]) pts_dst = np.array([[1069, 235],[1069, 320],[1201, 235],[1201, 320]]) h, status = cv2.findHomography(pts_src, pts_dst) im_out = cv2.warpPerspective(dst, h, (dst.shape[1], dst.shape[0]))
您可能想要查看特征匹配。目标是在两个图像(一个模板图像和一个嘈杂图像)中查找特征并将其匹配。这将允许您在嘈杂图像(相机图像)中找到模板(logo)。
特征本质上是人类在图像中感到有趣的事物,例如角落或开放的空间。我建议使用尺度不变特征变换(SIFT)作为特征检测算法。我建议使用SIFT的原因是它不受图像平移、缩放和旋转的影响,对光照变化部分具有不变性并且对局部几何畸变具有强大的鲁棒性。这符合您的规格要求。
我使用修改自OpenCV文档关于SIFT特征检测的代码生成了上面的图像:
import numpy as np import cv2 from matplotlib import pyplot as plt img = cv2.imread('main.jpg',0) # target Image # Create the sift object sift = cv2.xfeatures2d.SIFT_create(700) # Find keypoints and descriptors directly kp, des = sift.detectAndCompute(img, None) # Add the keypoints to the final image img2 = cv2.drawKeypoints(img, kp, None, (255, 0, 0), 4) # Show the image plt.imshow(img2) plt.show()
当您进行此操作时,您将注意到大量的特征落在联邦快递的标志上(上面的图像)。
接下来我尝试将视频源中的特征与联邦快递标志的特征匹配。我使用了FLANN特征匹配器来实现这一点。您可以选择许多方法(包括暴力方法),但由于您正在使用视频源,这可能是您最好的选择。下面的代码受到OpenCV文档关于特征匹配的启示:
import numpy as np import cv2 from matplotlib import pyplot as plt logo = cv2.imread('logo.jpg', 0) # query Image img = cv2.imread('main2.jpg',0) # target Image # Create the sift object sift = cv2.xfeatures2d.SIFT_create(700) # Find keypoints and descriptors directly kp1, des1 = sift.detectAndCompute(img, None) kp2, des2 = sift.detectAndCompute(logo,None) # FLANN parameters FLANN_INDEX_KDTREE = 1 index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5) search_params = dict(checks=50) # or pass empty dictionary flann = cv2.FlannBasedMatcher(index_params,search_params) matches = flann.knnMatch(des1,des2,k=2) # Need to draw only good matches, so create a mask matchesMask = [[0,0] for i in range(len(matches))] # ratio test as per Lowe's paper for i,(m,n) in enumerate(matches): if m.distance < 0.7*n.distance: matchesMask[i]=[1,0] # Draw lines draw_params = dict(matchColor = (0,255,0), singlePointColor = (255,0,0), matchesMask = matchesMask, flags = 0) # Display the matches img3 = cv2.drawMatchesKnn(img,kp1,logo,kp2,matches,None,**draw_params) plt.imshow(img3, ) plt.show()
使用这种方法,我成功将下面看到的特征匹配。您会注意到有离群值。但大多数特征匹配:
最后一步就是在图像周围简单地绘制边界框。我会链接您到另一个stackoverflow问题,它使用ORB探测器进行类似操作。这是通过OpenCV文档的另一种方法获得边界框。
希望这有所帮助!