图像处理:针对实时联邦快递标志检测的算法改进

20

我一直在从事涉及图像处理的项目,用于标志检测。具体来说,目标是开发一个实时FedEx卡车/标志检测器的自动化系统,该系统从IP摄像机流中读取帧并在检测到标志时发送通知。以下是系统运行的示例,识别出的标志被绿色矩形框起来。

original frame

Fedex logo

项目的一些限制:

  • 使用原始OpenCV(没有深度学习、人工智能或训练好的神经网络)
  • 图像背景可能嘈杂
  • 图像亮度可能会有很大变化(早晨、下午、晚上)
  • FedEx卡车/标志可以具有任何比例、旋转或方向,因为它可以停在人行道上的任何地方
  • 标志可能会因为时间不同而模糊或模糊,颜色也不同
  • 同一帧中可能会有许多其他大小或颜色相似的车辆
  • 实时检测(从IP摄像头约25 FPS)
  • IP摄像头处于固定位置,FedEx卡车始终处于相同的方向(不会倒置或颠倒)
  • Fedex卡车始终是“红色”版本,而不是“绿色”版本

当前的实现/算法

我有两个线程:
  • 线程 #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通知。

结果

enter image description here enter image description here

这个检查算法并不是很稳健,因为会有许多误报和检测失败。例如,触发了这些误报。

enter image description here enter image description here

虽然这种颜色阈值和轮廓检测方法在标志清晰的基本情况下有效,但在某些方面严重不足:
- 每帧都需要计算边界框,存在延迟问题 - 有时会在标志不存在时误检测 - 亮度和时间对检测准确性有很大影响 - 当标志呈倾斜角度时,颜色阈值检测可以工作,但由于检查算法而无法检测到标志。
请问是否有人能够帮助我改进我的算法或建议替代的检测策略?是否有其他方法进行此检测,因为颜色阈值高度依赖于精确校准?如果可能,我想避免使用颜色阈值和多层过滤器,因为它不太健壮。非常感谢任何见解或建议!

3
有一个想法可以通过形状匹配来过滤掉虚假轮廓,这意味着当你检测到紫色和红色轮廓时,你可以检查形状是否匹配(紫色与紫色匹配,红色与红色匹配),因为标志具有固定的形状,这有助于您轻松地检测标志。 - Bahramdun Adil
2
请查看以下链接:https://dev59.com/X2kw5IYBdhLWcg3wDWTL,这基本上是同样的问题。还请注意:https://stackoverflow.com/questions/24299500/can-sift-run-in-realtime。 - Piglet
2
颜色分割是一个不错的开始,但我认为你应该尝试使用自定义训练的Haar级联。Haar特征是人脸检测的支柱。你需要一些正样本和负样本来训练模型。 - ZdaR
2
IP摄像机是否处于固定位置并朝着单向街道?联邦快递卡车将在相同位置和相同方向上显示标志 - 卡车永远不会倒置或颠倒。这些限制大大简化了问题。 - Stephen Meschke
2
@StephenMeschke IP摄像头固定在一个位置,看着单行道就像第一张图片。是的,FedEx卡车总是以那个方向出现,它永远不会倒置或颠倒。此外,FedEx卡车将始终是像图片中的“红色”变体,而不是“绿色”地面卡车 - nathancy
显示剩余7条评论
2个回答

18

您可能想要了解特征匹配。其目标是在两张图片中找到特征,即模板图像和嘈杂的图像,并将它们进行匹配。这将允许您在嘈杂的图像(相机图像)中找到模板(标志)。

特征本质上是人类在图像中感兴趣的东西,例如角落或开放空间。我建议使用尺度不变特征变换(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()

使用此方法,我能够匹配以下特征,如下所示。您会注意到有一些离群值,但大多数特征都匹配:

Logo Matching

最后一步就是简单地在该图像周围绘制一个边界框。我将链接到另一个stackoverflow问题,该问题类似于orb检测器。 这是使用OpenCV文档获取边界框的另一种方法。

希望这可以帮助到您!


1
SIFT特征检测似乎是一个非常好的方法,特别是因为它似乎能够抵抗旋转和光照变化。我唯一担心的是使用这种方法处理每个帧的时间。我一定会尝试将这种方法集成到当前系统中。谢谢你的回答! - nathancy
1
非常好的观点,处理时间是一个问题。也许可以尝试使用速度更快的稳健特征(SURF)检测器,而不是使用SIFT。它本质上做的事情是一样的,但是速度要快得多(这里是原始论文,见表2)。我记得在某个地方看到过,OpenCV的SURF版本实现不太好(因此较慢),可能不是您要寻找的。以防万一,这里是原始源代码的链接代码。无论如何,这是一个非常有趣的问题,祝你好运! - Watchdog101
尝试使用ORB(https://docs.opencv.org/3.4/d1/d89/tutorial_py_orb.html)和FLANN(https://docs.opencv.org/3.4/d5/d6f/tutorial_feature_flann_matcher.html),这是专利SURF和SIFT方法的非常快速和开源的替代品。 - Jean-Paul

9
你可以通过对图像进行预处理来帮助检测器,这样就不需要太多的训练图像。

enter image description here

首先我们减少桶形畸变。

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)

enter image description here

然后,我们以一种方式转换图像,就好像摄像机正对着联邦快递卡车。也就是说,无论卡车停在路边的哪个位置,联邦快递标志的大小和方向几乎相同。
# 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]))

1
非常有趣的方法,可以确保标志始终面向前方。这种方法在检测之前肯定是一个很好的预处理阶段,可以解决涉及标志旋转的问题。 - nathancy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接