【问题标题】:Extract object from the image of a box having object从具有对象的框的图像中提取对象
【发布时间】:2017-05-11 17:34:27
【问题描述】:

我有一个盒子,前面是透明的,我将相机放在前面的透明面板上以捕捉内部的图像,大多数时候盒子是空的,但假设有人在这个盒子里放了一个物体,那么我必须从捕获的图像中提取这个对象。

(我的真正目的是识别盒子内的物体,但第一步是提取物体,然后提取特征以生成训练模型,目前我只专注于从图像中提取物体)

我是 OpenCV 新手,将它与 Python 一起使用,我发现很少有 OpenCV 函数可以帮助我。

  • GrabCut,这对我来说非常有效,我可以只提取 对象,前提是,我在对象上标记矩形,但作为 对象可以在框内的任何位置,因此无法绘制 对象的确切大小矩形,如果有办法请给我建议。
  • 图像的差异,因为我有空腔框图像并且当 对象存在,我可以使用 cv2.absdiff 函数来计算 图像之间的差异,但这在大多数情况下都无法正常工作 在这种情况下,因为它使用逐像素差异计算,以及 由于这个结果很奇怪,加上光照条件的变化 让事情变得困难。
  • 背景减法,我读了几篇关于这个的帖子,看起来这是 我需要什么,但我得到的例子是视频,我没有 了解如何让它只处理两张图片,一张是空框,另一张是对象。

背景减法的代码如下,即使它在短距离上也不能正常工作

cap = cv2.VideoCapture(0)
fgbg = cv2.createBackgroundSubtractorMOG2()
fgbg2 = cv2.createBackgroundSubtractorKNN()

while True:
    ret, frame = cap.read()
    cv2.namedWindow('Real', cv2.WINDOW_NORMAL)
    cv2.namedWindow('MOG2', cv2.WINDOW_NORMAL)
    cv2.namedWindow('KNN', cv2.WINDOW_NORMAL)
    cv2.namedWindow('MOG2_ERODE', cv2.WINDOW_NORMAL)
    cv2.namedWindow('KNN_ERODE', cv2.WINDOW_NORMAL)
    cv2.imshow('Real', frame)
    fgmask = fgbg.apply(frame)
    fgmask2 = fgbg2.apply(frame)
    kernel = np.ones((3,3), np.uint8)
    fgmask_erode = cv2.erode(fgmask,kernel,iterations = 1)
    fgmask2_erode = cv2.erode(fgmask2,kernel,iterations = 1)

    cv2.imshow('MOG2',fgmask)
    cv2.imshow('KNN',fgmask2)
    cv2.imshow('MOG2_ERODE',fgmask_erode)
    cv2.imshow('KNN_ERODE',fgmask2_erode)
    k = cv2.waitKey(30) & 0xff
    if k == 27:
        break
cap.release()
cv2.destroyAllWindows()

任何人都可以在这个主题上提供帮助,以及如何修改上面的代码以仅使用两个图像,当我尝试时我得到空白图像。提前致谢

来自相机的示例图像如下: (我使用的是 8MP 相机,所以图像尺寸很大,我缩小了尺寸,然后在这里上传)

【问题讨论】:

  • 为了提高 GrabCut 的性能,执行边缘检测以获取框的边缘。获得后,您可以拟合一个矩形,然后执行 GrabCut
  • 如果您不提供图像,您希望人们如何帮助您完成图像处理任务?
  • @JeruLuke:谢谢,我刚刚更新了一些示例图片,并会尝试您的建议。
  • @m3h0w:我刚刚更新了三张图片,一张是空盒子,另外两张是两个午餐盒。
  • @xpress_embedo 所以您主要询问图像差异和背景减法,然后您接受使用边缘的答案? Jeru Luke 的解决方案既酷又有趣,但它真的回答了您的问题并提供了您需要的可靠性吗?

标签: python opencv image-processing


【解决方案1】:

您提到了减法,我相信在这种情况下它是最好的方法。我已经实现了一个非常简单的算法来处理您提供给我们的案例。我用 cmets 解释了代码。在图片上,我展示了您遇到问题的最重要的步骤 - 算法的线索。

图像之间的差异:

差分阈值倒数:

以上两者结合:

结果 1:

结果 2:

带解释的代码:

import cv2
import numpy as np

# load the images
empty = cv2.imread("empty.jpg")
full = cv2.imread("full_2.jpg")

# save color copy for visualization
full_c = full.copy()

# convert to grayscale
empty_g = cv2.cvtColor(empty, cv2.COLOR_BGR2GRAY)
full_g = cv2.cvtColor(full, cv2.COLOR_BGR2GRAY)

# blur to account for small camera movement
# you could try if maybe different values will maybe
# more reliable for broader cases
empty_g = cv2.GaussianBlur(empty_g, (41, 41), 0)
full_g = cv2.GaussianBlur(full_g, (41, 41), 0)

# get the difference between full and empty box
diff = full_g - empty_g
cv2.imwrite("diff.jpg", diff)

# inverse thresholding to change every pixel above 190
# to black (that means without the bag)
_, diff_th = cv2.threshold(diff, 190, 255, 1)
cv2.imwrite("diff_th.jpg", diff_th)

# combine the difference image and the inverse threshold
# will give us just the bag
bag = cv2.bitwise_and(diff, diff_th, None)
cv2.imwrite("just_the_bag.jpg", bag)

# threshold to get the mask instead of gray pixels
_, bag = cv2.threshold(bag, 100, 255, 0)

# dilate to account for the blurring in the beginning
kernel = np.ones((15, 15), np.uint8)
bag = cv2.dilate(bag, kernel, iterations=1)

# find contours, sort and draw the biggest one
_, contours, _ = cv2.findContours(bag, cv2.RETR_TREE,
                                  cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:3]
cv2.drawContours(full_c, [contours[0]], -1, (0, 255, 0), 3)

# show and save the result
cv2.imshow("bag", full_c)
cv2.imwrite("result2.jpg", full_c)
cv2.waitKey(0)

现在,当然可以改进算法,并且必须根据您必须处理的任何条件进行调整。例如,您已经提到了照明的差异 - 您必须处理它以确保减去图像的背景相似。为此,您可能需要查看一些对比度增强算法,如果相机移动,可能要进行注册 - 这可能是一个完全独立的问题。

我还会考虑 JeruLuke 提到的 GrabCut,它带有我的方法找到的轮廓的边界矩形。为确保对象包含在其中,只需展开矩形即可。

【讨论】:

  • 我正在用类似的方法解决另一个问题。我面临的问题是,我无法获得接近的轮廓。我的这个问题可能有助于我想说的stackoverflow.com/questions/54955610/… 我正在尝试从图像中提取叶子。如果图像中有 10 片叶子,我想要 10 张新图像,每张都有一片叶子。 @m3h0w
【解决方案2】:

我有一个粗略的解决方案。如果您想更进一步,您必须对其进行改进以满足您的需求。

首先,我使用cv2.Canny()对灰度图像的模糊版本执行边缘检测

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) #---convert image to gray---
blur = cv2.GaussianBlur(gray, (5, 5), 0)   #---blurred the image---
edges = cv2.Canny(blur, lower, upper)   #---how to find perfect edges see link below---

我扩大了边缘以使它们更明显:

kernel = np.ones((3, 3), np.uint8)
dilated = cv2.morphologyEx(edges, cv2.MORPH_DILATE, kernel)

接下来,我发现边缘检测图像上存在轮廓。

_, contours, hierarchy = cv2.findContours(king, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)  

注意我使用cv2.RETR_EXTERNAL 只获取外部轮廓

然后我找到了面积最大的轮廓,在它周围放了一个边界框。

现在我使用 GrabCut 算法 来分割午餐盒。为此,我从THIS LINK HERE得到了我需要的所有帮助

我使用在找到轮廓后获得的边界矩形的坐标作为 GrabCut 算法的输入

最终输出:

如您所见,它并不完美,但这是我能做到的最好的。

希望对您有所帮助。如果您有更好的解决方案,请发布!!!! :D

【讨论】:

  • 为了获得更好的边缘检测SEE THIS ANSWER I POSTED
  • 不错!我觉得“弱点”在“最大轮廓检测”。因为如果您的对象轮廓没有被 Canny 检测关闭,那么最大的轮廓很可能是框轮廓之一。也许如果您的对象永远不是“盒色”,您还可以进行颜色分割以检测计数?它可能仍然很吵,你必须选择最大的轮廓,但如果 Canny + findContour 失败,可能是另一种方法。
  • @Soltius 我也想过同样的事情。在这种情况下,K-Means 聚类会有所帮助。由于背景是恒定的,我也想到了自适应阈值。
  • 有趣的解决方案。不过我认为,在这种情况下,不以任何形式使用背景减法都是一种浪费。基于边缘的方法可能被证明是不可靠的。
  • @m3h0w 我考虑了没有参考图像的事实。在这种情况下你会如何处理?
猜你喜欢
  • 2019-02-17
  • 1970-01-01
  • 1970-01-01
  • 2021-01-31
  • 1970-01-01
  • 2021-05-08
  • 1970-01-01
  • 2013-04-13
相关资源
最近更新 更多