OpenCV 和 Python - 如何使用卡尔曼滤波器从 OpenCV 检测到的不规则多边形中过滤噪声?
Posted
技术标签:
【中文标题】OpenCV 和 Python - 如何使用卡尔曼滤波器从 OpenCV 检测到的不规则多边形中过滤噪声?【英文标题】:OpenCV & Python - How does one filter noise from an irregular shaped polygon detected by OpenCV using a Kalman filter? 【发布时间】:2021-05-20 16:01:50 【问题描述】:我正在处理一个小型跟踪项目。我有我的逐帧检测方案设置和工作。当我跑步时,即使场景是静态的,我在提取的多边形中也会出现相当多的噪音。由于我希望实时运行,因此卡尔曼滤波似乎是解决此问题的最佳方法;但是实现细节很少。我通过 google 看到了一些示例,但它们通常处理边界框或规则形状,仅用少量信息进行描述。我不确定这种方法是否可行。
我有兴趣跟踪下面更不规则几何的演变。描述多边形需要大约 100 个点或更多。如何调整 OpenCV 卡尔曼工具来处理这项任务?
提前致谢。
** 更新 **
所以额外的细节。我需要对对象进行准确的分析以进行下游分析,因此不能选择边界框。我的相机可以以 30 fps 的速度产生帧,但我不需要处理那么快,尽管我也不想每秒只处理 1 个。进行快速去噪操作太慢了。我的图像是 4024x3036 单色图像。我附上了我的场景的六个镜头的 jpeg 版本。样本是图像底部三分之一的两个盘子中心的小块。我还附上了我希望从每一帧中提取的不规则多边形,该多边形与形状的 2d 轮廓准确匹配。我更喜欢准确性和稳定性而不是速度,但我希望每秒处理几帧。
我会去拍一些有代表性的图片或小电影,稍后会发布。
提前致谢。
示例图片
目标
【问题讨论】:
以减少您在技术上尝试平滑/过滤点的噪音。 kalman 或 OneEuro 过滤器需要固定数量的点,并且它们必须始终以相同的顺序呈现,所以如果 kalman 在不断变化的多边形上会有所帮助。我不是 100% 确定您要做什么或为什么,所以我不能提出任何替代方案 @Ta946 嗯,相同的点顺序会很困难。我正在跟踪的轮廓将变形,顶部和底部挤压中间的对象。所以我对基于我当前帧的形状有一个粗略的了解,我希望测量结果不那么跳跃。我尝试从整个帧中消除噪音,但这会大大减慢处理速度。它有所帮助,但我仍然有一些动作。我读到跟踪更有效,但它只适用于边界框。我想这就是原因。 请发布一些原始框架示例供我们使用,谢谢。 @AnnZen 我用更多细节和一些图片更新了帖子。 所以基本上,你想找到没有太多噪音的多边形轮廓,保持点的顺序并保持相同的点数? 【参考方案1】:概念
请注意,在图像的列中,紫色线应该去的列是最黑的吗?我们可以通过首先检测具有至少一定量黑色的第一列和最后一列来检测 ROI(感兴趣区域)。然后检测检测到的 2 列之间的行,其中白色首先开始并首先结束于 2 列。
代码
import cv2
import numpy as np
files = [f"imgi.jpg" for i in range(1, 6)]
for file in files:
img = cv2.imread(file)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
sum_cols = thresh.sum(0)
indices = np.where(sum_cols < sum_cols.min() + 40000)[0]
x1, x2 = indices[0] - 50, indices[-1] + 50
diff1, diff2 = np.diff(thresh[:, [x1, x2]].T, 1)
y1_1, y2_1 = np.where(diff1)[0][:2]
y1_2, y2_2 = np.where(diff2)[0][:2]
y1, y2 = min(y1_1, y1_2), max(y2_1, y2_2)
img_canny = cv2.Canny(thresh[y1: y2, x1: x2], 50, 50)
contours, _ = cv2.findContours(img_canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.line(img, (x1, y1_1), (x2, y1_2), (255, 0, 160), 5)
cv2.line(img, (x1, y2_1), (x2, y2_2), (255, 0, 160), 5)
cv2.drawContours(img[y1: y2, x1: x2], contours, -1, (0, 0, 255), 10)
cv2.imshow("Image", img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
输出
以下是程序将为您提供的每个不同图像输出的内容:
解释
-
导入必要的库:
import cv2
import numpy as np
-
既然你有摄像头,显然你将使用
cv2.VideoCapture()
方法。由于我只有您提供的图像,因此我将让程序读取每张图像。因此,将每个图像文件名存储到一个列表中(我有img1.jpg
、img1.jpg
、...img5.jpg
),遍历名称并读取每个图像:
files = [f"imgi.jpg" for i in range(1, 6)]
for file in files:
img = cv2.imread(file)
-
将每张图片转换为灰度图,使用
cv2.threshold()
方法将灰度图转换为只有2个值; 0
表示小于或等于127
的每个像素,255
表示每个大于127
的像素:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
- 为了找到
0
s 最多的列(这意味着黑色最多),我们需要找到每一列的总和,其中最小的总和来自0
s 最多的列。通过每列的总和,我们可以使用np.where()
方法来查找阈值图像中每一列的索引,其总和接近于检测到的最小总和。然后,我们可以将检测到的列的第一个索引和最后一个索引分别作为我们 ROI 的x1
和x2
(以及50
像素的填充)::李>
sum_cols = thresh.sum(0)
indices = np.where(sum_cols < sum_cols.min() + 40000)[0]
x1, x2 = indices[0] - 50, indices[-1] + 50
-
为了找到顶行的
y1
和y2
,我们需要在检测到的第一个边缘检测从0
到255
的第一次变化的索引列和检测到的列的最后一个边缘。同样,为了找到底线的y1
和y2
,我们需要在检测到的列的第一边缘检测从255
到0
的第一次变化的索引并在检测到的列的最后一个边缘。最后,通过我们的 4 个y
坐标,我们可以通过获取第一行中最小的y
坐标和最大的y
坐标来获得ROI 的y1
和y2
第二行:
diff1, diff2 = np.diff(thresh[:, [x1, x2]].T, 1)
y1_1, y2_1 = np.where(diff1)[0][:2]
y1_2, y2_2 = np.where(diff2)[0][:2]
y1, y2 = min(y1_1, y1_2), max(y2_1, y2_2)
-
现在我们有了投资回报率。我们可以使用 Canny 边缘检测器检测出 ROI 内物体的边缘,并使用
cv2.findContours()
方法检测边缘的轮廓:
img_canny = cv2.Canny(thresh[y1: y2, x1: x2], 50, 50)
contours, _ = cv2.findContours(img_canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
-
最后,我们可以在非二值图像上绘制线条和轮廓,并显示图像:
cv2.line(img, (x1, y1_1), (x2, y1_2), (255, 0, 160), 5)
cv2.line(img, (x1, y2_1), (x2, y2_2), (255, 0, 160), 5)
cv2.drawContours(img[y1: y2, x1: x2], contours, -1, (0, 0, 255), 10)
cv2.imshow("Image", img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
【讨论】:
这很漂亮,我的代码大约是行数的 6 倍,但速度很慢而且很吵。我非常感谢您逐行分解代码。在我结束这个之前有几个问题。 1)您查看 col 或 row 值的方法是识别图像的最快方法吗?我的代码反复寻找轮廓,我认为这不是最好的方法 2)为什么在轮廓步骤之前使用 Canny 边缘检测?我担心,因为 Canny 以前从未为我工作过,但它似乎很好,但很高兴知道它为什么在这里工作。 3) 你是怎么想出 Canny 参数的? @TheCodeNovice 很高兴。解决您的问题:1)如果我知道一种更快的方式,我会发布它,据我所知,是的。 2)在检测轮廓之前使用Canny边缘检测器大大降低了噪声。 3) 这对我来说有点本能,但每当我需要微调值时,我使用 cv2 trackbars,它允许交互式值调整。这是一个示例脚本:i.stack.imgur.com/zhKgT.png 及其作用:i.stack.imgur.com/IUoJk.gif。【参考方案2】:您可以尝试这个解决方案,看看轮廓是否仍然跳跃,让卡尔曼先生安息:) 下面的代码将只生成部分属于您的对象的轮廓,部分属于板块的上侧和下侧。您将不得不做更多的处理来连接两条线以获得整个对象轮廓。代码中的假设是子图像将始终包含 ROI。顺便说一句,我非常怀疑您是否可以在这里使用卡尔曼,因为您没有固定的可跟踪/可识别轮廓点。处理速度应该相当高效,以便您每秒可以多张图像。
gw1,gs1,gw2,gs2 = (5,1,7,3)
rgb = cv2.imread('/your/test/image/so_kalman.jpeg')
gray = cv2.cvtColor(rgb, cv2.COLOR_BGR2GRAY)
wk_img = gray[2000:2500, 500:2000] # Work on a sub-image
min_ctr_area = 30000
max_ctr_area = 70000
g1 = cv2.GaussianBlur(wk_img, (gw1, gw1), gs1)
g2 = cv2.GaussianBlur(wk_img, (gw2, gw2), gs2)
ret, th = cv2.threshold(g2-g1, 200, 255, cv2.THRESH_BINARY)
h, w = th.shape
cv2.rectangle(th, (0, 0), (w, h), 255, 5)
contours, hier = cv2.findContours(th.copy(),cv2.RETR_CCOMP,cv2.CHAIN_APPROX_SIMPLE)
out_img = rgb[2000:2500, 500:2000].copy()
for i in range(len(contours)):
if hier[0][i][3] == -1:
continue
x,y,w,h = cv2.boundingRect(contours[i])
if min_ctr_area < w*h < max_ctr_area:
cv2.drawContours(out_img, [contours[i]], -1, (255, 0, 0), 2)
plt.imshow(out_img)
这是您的一张测试图片上的result。这是一个简单的解决方案,但希望能满足您的期望。
【讨论】:
以上是关于OpenCV 和 Python - 如何使用卡尔曼滤波器从 OpenCV 检测到的不规则多边形中过滤噪声?的主要内容,如果未能解决你的问题,请参考以下文章
使用 Opencv C++ 中给定对象的 X,Y 进行卡尔曼滤波器跟踪