使用 OpenCV 进行矩形检测/跟踪
Rectangle detection / tracking using OpenCV
我需要的
我目前正在开发一款类似增强现实的游戏。游戏使用的控制器(我在这里谈论的是物理输入设备)是一张单色的矩形纸。我必须在相机的捕获流中检测该矩形的位置、旋转和大小。检测应该在尺度上不变,在沿 X 和 Y 轴的旋转上不变。
在用户将纸张移开或移向相机的情况下,需要尺度不变性。我不需要知道矩形的距离,因此比例不变性转化为大小不变性。
如果用户沿其局部 X 和/或 Y 轴倾斜矩形,则需要旋转不变性。这样的旋转将纸张的形状从矩形变为梯形。在这种情况下,可以使用面向对象的边界框来测量纸张的尺寸。
我做了什么
一开始有一个校准步骤。 window 显示相机源,用户必须单击矩形。单击时,鼠标指向的像素的颜色将作为参考颜色。将帧转换为 HSV 颜色 space 以提高颜色区分度。我有 6 个滑块,用于调整每个通道的上限和下限阈值。这些阈值用于对图像进行二值化(使用 opencv 的 inRange
函数)。
在那之后,我正在侵蚀和扩张二值图像以去除噪声并联合 nerby 块(使用 opencv 的 erode
和 dilate
函数)。
下一步是在二进制图像中查找轮廓(使用 opencv 的 findContours
函数)。这些轮廓用于检测最小的定向矩形(使用 opencv 的 minAreaRect
函数)。作为最终结果,我使用了面积最大的矩形。
程序的简短结论:
- 抓帧
- 将该帧转换为 HSV
- 对其进行二值化(使用用户选择的颜色和滑块的阈值)
- 应用变形操作(腐蚀和膨胀)
- 寻找轮廓
- 获取每个轮廓的最小定向边界框
- 取最大的边界框作为结果
您可能已经注意到,我没有利用有关纸张实际形状的知识,只是因为我不知道如何正确使用这些信息。
我也想过用opencv的跟踪算法。但是有三个原因阻止我使用它们:
- 比例不变性:据我了解一些算法,有些算法不支持对象的不同比例。
- 运动预测:一些算法使用运动预测以获得更好的性能,但我正在跟踪的对象完全随机移动,因此无法预测。
- 简单:我只是在图像中寻找一个单色矩形,没有什么比汽车或人物跟踪更花哨的了。
这是一个 - 相对 - 好的捕捉(腐蚀和膨胀后的二值图像)
这是一个糟糕的
问题
我如何改进检测,尤其是更能抵抗光照变化?
更新
Here是一些用于测试的原始图像。
就不能用厚一点的material吗?
是的,我可以而且我已经这样做了(不幸的是我目前无法访问这些作品)。但是,问题仍然存在。即使我像纸板一样使用 material。不像纸那么容易弯曲,但还是可以弯曲的。
如何获取矩形的大小、旋转和位置?
opencv的minAreaRect
函数returns一个RotatedRect
对象。该对象包含我需要的所有数据。
备注
因为矩形是单色的,所以不可能区分上下或左右。这意味着旋转始终在 [0, 180]
范围内,这对我的目的来说非常合适。矩形两侧的比例总是w:h > 2:1
。如果矩形是正方形,则旋转范围将变为[0, 90]
,但这在这里可以认为是无关紧要的。
按照评论中的建议,我将尝试使用直方图均衡化来减少亮度问题,并查看 ORB、SURF 和 SIFT。
我会更新进度。
HSV中的H通道space是Hue,对光线变化不敏感。红色范围大约在 [150,180].
根据上述信息,我做了以下工作。
- 换成HSVspace,拆分H通道,阈值化,归一化。
- 应用变形操作(打开)
- 查找轮廓,按某些属性(宽度、高度、面积、比率等)进行过滤。
PS。由于网络原因,我无法获取您上传到保管箱的图片。所以,我只使用 crop the right side of your second image 作为输入。
imgname = "src.png"
img = cv2.imread(imgname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
## Split the H channel in HSV, and get the red range
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
h[h<150]=0
h[h>180]=0
## normalize, do the open-morp-op
normed = cv2.normalize(h, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8UC1)
kernel = cv2.getStructuringElement(shape=cv2.MORPH_ELLIPSE, ksize=(3,3))
opened = cv2.morphologyEx(normed, cv2.MORPH_OPEN, kernel)
res = np.hstack((h, normed, opened))
cv2.imwrite("tmp1.png", res)
现在,我们得到这样的结果(h,标准化,打开):
然后找到轮廓并过滤它们。
contours = cv2.findContours(opened, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
print(len(contours))[-2]
bboxes = []
rboxes = []
cnts = []
dst = img.copy()
for cnt in contours:
## Get the stright bounding rect
bbox = cv2.boundingRect(cnt)
x,y,w,h = bbox
if w<30 or h < 30 or w*h < 2000 or w > 500:
continue
## Draw rect
cv2.rectangle(dst, (x,y), (x+w,y+h), (255,0,0), 1, 16)
## Get the rotated rect
rbox = cv2.minAreaRect(cnt)
(cx,cy), (w,h), rot_angle = rbox
print("rot_angle:", rot_angle)
## backup
bboxes.append(bbox)
rboxes.append(rbox)
cnts.append(cnt)
结果是这样的:
rot_angle: -2.4540319442749023
rot_angle: -1.8476102352142334
因为源图中的蓝色矩形标签,卡片被分成了两边。但是干净的图像是没有问题的。
我知道我问这个问题已经有一段时间了。我最近继续讨论这个话题并解决了我的问题(虽然不是通过矩形检测)。
变化
- 使用木头来强化我的控制器("rectangles"),如下所示。
- 在每个控制器上放置了 2 ArUco 个标记。
工作原理
- 将帧转换为灰度,
- 对其进行下采样(以提高检测期间的性能),
- 使用
cv::equalizeHist
、 均衡直方图
- 使用
cv::aruco::detectMarkers
、 查找标记
- 关联标记(如果有多个控制器),
- 分析标记(位置和旋转),
- 计算结果并应用一些错误更正。
事实证明,标记检测对光照变化和不同视角非常稳健,这让我可以跳过任何校准步骤。
我在每个控制器上放置了 2 个标记,以进一步提高检测的稳健性。两种标记只需检测一次(以衡量它们的相关性)。之后,每个控制器只找到一个标记就足够了,因为另一个标记可以从先前计算的相关性中推断出来。
这里是明亮环境下的检测结果:
在较暗的环境中:
当隐藏其中一个标记时(蓝点表示推断的标记位置):
失败
我实施的初始形状检测效果不佳。它对照明变化非常脆弱。此外,它需要一个初始校准步骤。
在形状检测方法之后,我尝试结合使用 SIFT 和 ORB 以及蛮力和 knn 匹配器来提取和定位帧中的特征。事实证明,单色对象并没有提供太多关键点(真令人惊讶)。无论如何,SIFT 的性能很糟糕(大约 10 fps @ 540p)。
我在控制器上画了一些线和其他形状,这导致更多的关键点可用。然而,这并没有带来巨大的改进。
我需要的
我目前正在开发一款类似增强现实的游戏。游戏使用的控制器(我在这里谈论的是物理输入设备)是一张单色的矩形纸。我必须在相机的捕获流中检测该矩形的位置、旋转和大小。检测应该在尺度上不变,在沿 X 和 Y 轴的旋转上不变。
在用户将纸张移开或移向相机的情况下,需要尺度不变性。我不需要知道矩形的距离,因此比例不变性转化为大小不变性。
如果用户沿其局部 X 和/或 Y 轴倾斜矩形,则需要旋转不变性。这样的旋转将纸张的形状从矩形变为梯形。在这种情况下,可以使用面向对象的边界框来测量纸张的尺寸。
我做了什么
一开始有一个校准步骤。 window 显示相机源,用户必须单击矩形。单击时,鼠标指向的像素的颜色将作为参考颜色。将帧转换为 HSV 颜色 space 以提高颜色区分度。我有 6 个滑块,用于调整每个通道的上限和下限阈值。这些阈值用于对图像进行二值化(使用 opencv 的 inRange
函数)。
在那之后,我正在侵蚀和扩张二值图像以去除噪声并联合 nerby 块(使用 opencv 的 erode
和 dilate
函数)。
下一步是在二进制图像中查找轮廓(使用 opencv 的 findContours
函数)。这些轮廓用于检测最小的定向矩形(使用 opencv 的 minAreaRect
函数)。作为最终结果,我使用了面积最大的矩形。
程序的简短结论:
- 抓帧
- 将该帧转换为 HSV
- 对其进行二值化(使用用户选择的颜色和滑块的阈值)
- 应用变形操作(腐蚀和膨胀)
- 寻找轮廓
- 获取每个轮廓的最小定向边界框
- 取最大的边界框作为结果
您可能已经注意到,我没有利用有关纸张实际形状的知识,只是因为我不知道如何正确使用这些信息。
我也想过用opencv的跟踪算法。但是有三个原因阻止我使用它们:
- 比例不变性:据我了解一些算法,有些算法不支持对象的不同比例。
- 运动预测:一些算法使用运动预测以获得更好的性能,但我正在跟踪的对象完全随机移动,因此无法预测。
- 简单:我只是在图像中寻找一个单色矩形,没有什么比汽车或人物跟踪更花哨的了。
这是一个 - 相对 - 好的捕捉(腐蚀和膨胀后的二值图像)
这是一个糟糕的
问题
我如何改进检测,尤其是更能抵抗光照变化?
更新
Here是一些用于测试的原始图像。
就不能用厚一点的material吗?
是的,我可以而且我已经这样做了(不幸的是我目前无法访问这些作品)。但是,问题仍然存在。即使我像纸板一样使用 material。不像纸那么容易弯曲,但还是可以弯曲的。
如何获取矩形的大小、旋转和位置?
opencv的minAreaRect
函数returns一个RotatedRect
对象。该对象包含我需要的所有数据。
备注
因为矩形是单色的,所以不可能区分上下或左右。这意味着旋转始终在 [0, 180]
范围内,这对我的目的来说非常合适。矩形两侧的比例总是w:h > 2:1
。如果矩形是正方形,则旋转范围将变为[0, 90]
,但这在这里可以认为是无关紧要的。
按照评论中的建议,我将尝试使用直方图均衡化来减少亮度问题,并查看 ORB、SURF 和 SIFT。
我会更新进度。
HSV中的H通道space是Hue,对光线变化不敏感。红色范围大约在 [150,180].
根据上述信息,我做了以下工作。
- 换成HSVspace,拆分H通道,阈值化,归一化。
- 应用变形操作(打开)
- 查找轮廓,按某些属性(宽度、高度、面积、比率等)进行过滤。
PS。由于网络原因,我无法获取您上传到保管箱的图片。所以,我只使用 crop the right side of your second image 作为输入。
imgname = "src.png"
img = cv2.imread(imgname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
## Split the H channel in HSV, and get the red range
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
h[h<150]=0
h[h>180]=0
## normalize, do the open-morp-op
normed = cv2.normalize(h, None, 0, 255, cv2.NORM_MINMAX, cv2.CV_8UC1)
kernel = cv2.getStructuringElement(shape=cv2.MORPH_ELLIPSE, ksize=(3,3))
opened = cv2.morphologyEx(normed, cv2.MORPH_OPEN, kernel)
res = np.hstack((h, normed, opened))
cv2.imwrite("tmp1.png", res)
现在,我们得到这样的结果(h,标准化,打开):
然后找到轮廓并过滤它们。
contours = cv2.findContours(opened, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
print(len(contours))[-2]
bboxes = []
rboxes = []
cnts = []
dst = img.copy()
for cnt in contours:
## Get the stright bounding rect
bbox = cv2.boundingRect(cnt)
x,y,w,h = bbox
if w<30 or h < 30 or w*h < 2000 or w > 500:
continue
## Draw rect
cv2.rectangle(dst, (x,y), (x+w,y+h), (255,0,0), 1, 16)
## Get the rotated rect
rbox = cv2.minAreaRect(cnt)
(cx,cy), (w,h), rot_angle = rbox
print("rot_angle:", rot_angle)
## backup
bboxes.append(bbox)
rboxes.append(rbox)
cnts.append(cnt)
结果是这样的:
rot_angle: -2.4540319442749023
rot_angle: -1.8476102352142334
因为源图中的蓝色矩形标签,卡片被分成了两边。但是干净的图像是没有问题的。
我知道我问这个问题已经有一段时间了。我最近继续讨论这个话题并解决了我的问题(虽然不是通过矩形检测)。
变化
- 使用木头来强化我的控制器("rectangles"),如下所示。
- 在每个控制器上放置了 2 ArUco 个标记。
工作原理
- 将帧转换为灰度,
- 对其进行下采样(以提高检测期间的性能),
- 使用
cv::equalizeHist
、 均衡直方图
- 使用
cv::aruco::detectMarkers
、 查找标记
- 关联标记(如果有多个控制器),
- 分析标记(位置和旋转),
- 计算结果并应用一些错误更正。
事实证明,标记检测对光照变化和不同视角非常稳健,这让我可以跳过任何校准步骤。
我在每个控制器上放置了 2 个标记,以进一步提高检测的稳健性。两种标记只需检测一次(以衡量它们的相关性)。之后,每个控制器只找到一个标记就足够了,因为另一个标记可以从先前计算的相关性中推断出来。
这里是明亮环境下的检测结果:
在较暗的环境中:
当隐藏其中一个标记时(蓝点表示推断的标记位置):
失败
我实施的初始形状检测效果不佳。它对照明变化非常脆弱。此外,它需要一个初始校准步骤。
在形状检测方法之后,我尝试结合使用 SIFT 和 ORB 以及蛮力和 knn 匹配器来提取和定位帧中的特征。事实证明,单色对象并没有提供太多关键点(真令人惊讶)。无论如何,SIFT 的性能很糟糕(大约 10 fps @ 540p)。 我在控制器上画了一些线和其他形状,这导致更多的关键点可用。然而,这并没有带来巨大的改进。