轮廓检测:降低图像中的反光问题-opencv/python

3
我正在处理下面这张图片的轮廓检测,但由于光线条件不好,在图像显示耀斑处检测不完整。我正在尝试去除它们,以获得更好的轮廓检测结果。
以下是原始图片:

image with glare

这是灰度+二值化图像,通过cv2.connectedComponentsWithStats运行以检测物体。我已经标出需要减少曝光的区域。(因为我使用反向THRESH_BINARY_INV滤波器,这些区域会变成黑色)。

grayed + thresholded image

正如您在下面所见,检测到的物体区域是不完整的,cv2.connectedComponentsWithStats将无法检测出物体的完整区域。

Object areas

当然,轮廓本身也是错误的,因为它是在裁剪的轮廓组件上计算的。

Cropped outlined

当然,轮廓本身就是错误的:

Wrong contour due to glare

这是我到目前为止所做的:

def getFilteredContours(image, minAreaFilter=20000) -> np.array:
    ret = []
    ctrs,_ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    ctrs = sorted(ctrs, key=cv2.contourArea, reverse=True)
    for i, c in enumerate(ctrs):
        # Calculate the area of each contour
        area = cv2.contourArea(c)
        if area < minAreaFilter:
            break
        ret.append(c)
    return ret

birdEye = cv2.imread(impath)

gray = cv2.cvtColor(birdEye, cv2.COLOR_BGR2GRAY)
gray = cv2.GaussianBlur(gray, (5, 5), 0)
threshImg = cv2.threshold(gray, 180, 255, cv2.THRESH_BINARY_INV)[1]
(numLabels, labels, stats, centroids) = cv2.connectedComponentsWithStats(
    threshImg, 4, cv2.CV_32S)

#then for each identified component we extract the component and get the contour

filteredIdx = getFilteredLabelIndex(stats)

for labelId in filteredLabelId:
    componentMask = (labels == i).astype("uint8") * 255
    ctrs, _ = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    ctrs = sorted(ctrs, key=cv2.contourArea, reverse=True)
    ctr = max(ctrs, key=cv2.contourArea)    
    cv2.drawContours(birdEye, [cntrs], -1, (255, 0, 255), 3)

cv2.imshow("original contour", birdEye)

cv2.waitKey(0)
cv2.destroyAllWindows()

任何建议都将受到欢迎,
谢谢,
Pat

轮廓检测函数以二进制图像作为输入。你知道如何获得二进制图像吗? - Burak
是的,我确实更新了我的问题。 - user2097439
我建议您研究一下漫反射光照,这样您的图像就可以减少眩光。 - fmw42
2个回答

1

您可以使用 floodFill 来先填充背景。

cv2.floodFill 应用于您的示例图像会产生良好的结果。
结果很好,因为背景相对均匀。
floodFill 使用颜色信息,而不是其他算法仅使用亮度。
背景具有轻微的亮度梯度,"泛洪填充"算法处理得很好。

您可以按照以下步骤进行操作:

  • 将所有(暗色)值(例如低于10)替换为10-避免在对象内部存在黑色像素的问题。
  • 使用cv2.floodFill填充背景为黑色。
    以左上角为“背景”种子颜色(假设像素[10,10]不在一个对象中)。
  • 转换为灰度。
  • 应用阈值-将所有高于零的像素转换为255。
  • 使用开运算(形态学操作)去除小的异常值。
  • 查找轮廓。

代码示例:

import cv2
import numpy as np

birdEye = cv2.imread(r"C:\Rotem\tools.jpg")

# Replace all (dark) values below 10 with 10 - avoiding issues where there are black pixels inside an object
birdEye = np.maximum(birdEye, 10)

foreground = birdEye.copy()

seed = (10, 10)  # Use the top left corner as a "background" seed color (assume pixel [10,10] is not in an object).

# Use floodFill for filling the background with black color
cv2.floodFill(foreground, None, seedPoint=seed, newVal=(0, 0, 0), loDiff=(5, 5, 5, 5), upDiff=(5, 5, 5, 5))

# Convert to Grayscale
gray = cv2.cvtColor(foreground, cv2.COLOR_BGR2GRAY)

# Apply threshold
thresh = cv2.threshold(gray, 1, 255, cv2.THRESH_BINARY)[1]

# Use opening for removing small outliers
thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)))

# Find contours
cntrs, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

# Draw contours
cv2.drawContours(birdEye, cntrs, -1, (255, 0, 255), 3)

# Show images for testing
cv2.imshow('foreground', foreground)
cv2.imshow('gray', gray)
cv2.imshow('thresh', thresh)
cv2.imshow('birdEye', birdEye)

cv2.waitKey()
cv2.destroyAllWindows()

foreground

foreground

birdEye 输出:

birdEye output


那看起来很棒,有没有办法使轮廓更加平滑?它们看起来有点粗糙。你的图像右下方的寄生轮廓怎么样,我能过滤掉它吗? - user2097439
嗯,这里有些奇怪,我按原样运行了你的代码,但我的前景图像看起来并不是黑色的。你有什么遗漏吗? - user2097439
奇怪...有时候发布的图片会被修改。尝试从您的帖子中下载该图像,并将其用作输入。 - Rotem
寄生轮廓在原始图像中是黑色的。我们可以将其裁剪掉或遮罩它。 - Rotem
平滑边缘可能会很具有挑战性。形态学操作可能会有所帮助。 - Rotem
我的错,我使用的图像有一个外部边框。现在一切都像你建议的那样工作。再次感谢。 - user2097439

0

我的建议是在cv2中使用膨胀和腐蚀函数(或闭合函数)。

如果您使用cv2.dilate函数,则白色区域比现在更大。

相反,如果您使用cv2.erode函数,则白色区域比现在更小。

这个迭代过程可以消除黑色区域的噪声。

闭合函数是膨胀后跟随腐蚀。

请参见https://docs.opencv.org/master/d9/d61/tutorial_py_morphological_ops.html


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