OpenCV & Python - 如何使用卡尔曼滤波器从 OpenCV 检测到的不规则多边形中过滤噪声?

OpenCV & Python - How does one filter noise from an irregular shaped polygon detected by OpenCV using a Kalman filter?

我正在进行一个小型跟踪项目。我有逐帧检测方案设置和工作。当我 运行 时,即使场景是静态的,我也会在提取的多边形中得到相当多的噪声。因为我想要这个 运行 实时,卡尔曼滤波似乎是解决这个问题的最好方法;但是实施细节很少。我通过 google 看到了一些示例,但它们通常处理边界框或规则形状,仅使用少量信息进行描述。我不确定这种方法是否有效。

我有兴趣跟踪下面更不规则几何体的演变。描述多边形需要大约 100 个点或更多。我如何调整 OpenCV 卡尔曼工具来处理此任务?

提前致谢。

** 更新 **

所以额外的细节。我需要准确了解 object 以进行下游分析,因此不能选择边界框。我的相机可以每秒 30 帧的速度生成帧,但我不需要处理得那么快,尽管我也不想每秒只处理 1 帧。进行快速 de-noising 操作太慢了。我的图像是 4024x3036 单色图像。我附上了我场景的六个镜头的 jpeg 版本。样本是图像底部三分之一处两个板块中心的小块。我还附上了我想要从每一帧中拉出的不规则多边形,该多边形与形状的二维轮廓准确匹配。我更喜欢准确性和稳定性而不是速度,但我想每秒处理几帧。

我会去拍摄一些有代表性的图片或小电影,很快就会post。

提前致谢。

示例图片

目标

你可以试试这个解决方案,看看轮廓是否仍然跳动,让卡尔曼先生安息:)下面的代码只会产生部分属于你的对象的轮廓,部分属于物体的上侧和下侧盘子。您将需要做更多的处理来连接两条线以获得整个对象的轮廓。代码中的假设是子图像将始终包含 ROI。顺便说一句,我非常怀疑你可以在这里使用卡尔曼,因为你没有固定的 trackable/identifiable 轮廓点。处理速度应该相当高效,以便您每秒可以处理多个图像。

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。这是一个简单的解决方案,但希望能达到您的期望。

概念

请注意在图像的列中,紫色线应该去的列怎么有最黑的?我们可以通过首先检测具有至少一定数量黑色的第一列和最后一列来检测 ROI (感兴趣区域)。然后检测 2 个检测到的列之间的行,其中白色首先在 2 列开始和结束。

代码

import cv2
import numpy as np

files = [f"img{i}.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

输出

程序针对您提供的每张不同图像将输出以下内容:

解释

  1. 导入必要的库:
import cv2
import numpy as np
  1. 既然你有相机,显然你要使用cv2.VideoCapture()方法。因为我只有你提供的图片,所以我会让程序在每张图片中读取。因此,将每个图像文件名存储到列表 (我有 img1.jpgimg1.jpg、... img5.jpg,遍历名称并读取在每张图片中:
files = [f"img{i}.jpg" for i in range(1, 6)]

for file in files:
    img = cv2.imread(file)
  1. 将每张图都转成灰度图,使用cv2.threshold()方法将灰度图转成只有2个值; 0 小于或等于 127 的每个像素,255 大于 127 的每个像素:
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
  1. 为了找到最多 0 的列 (这意味着最多黑色),我们需要找到每一列的总和,其中最小的总和来自 0 最多的列。有了每列的总和,我们可以使用 np.where() 方法找到阈值图像中每一列的索引,这些索引总和接近检测到的最小和的数字。然后,我们可以将检测到的列的第一个索引和最后一个索引作为 ROI x1x2(以及 50 像素的填充):
    sum_cols = thresh.sum(0)
    indices = np.where(sum_cols < sum_cols.min() + 40000)[0]
    x1, x2 = indices[0] - 50, indices[-1] + 50
  1. 为了找到顶部行的 y1y2,我们需要检测第一次出现从 0 到 [= 的变化的索引25=] 在检测到的列的第一个边缘和检测到的列的最后一个边缘。同样,为了找到底线的 y1y2,我们需要检测第一次出现从 2550 的变化的索引在检测到的列的第一个边缘和检测到的列的最后一个边缘。最后,利用我们的 4 个 y 坐标,我们可以通过获取第一行中最小的 y 坐标和最大的 y1y2 我们的 ROI第二行的y坐标:
    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)
  1. 现在我们有了投资回报率。我们可以使用 Canny 边缘检测器检测 out 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)
  1. 最后,我们可以在非二值图像上绘制线条和轮廓,并显示图像:
    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