【问题标题】:How can I extract image segment with specific color in OpenCV?如何在 OpenCV 中提取具有特定颜色的图像片段?
【发布时间】:2018-08-20 09:38:44
【问题描述】:

我使用徽标和其他简单图形,其中没有渐变或复杂图案。我的任务是从带有字母和其他元素的标志片段中提取出来。

为此,我定义了背景颜色,然后遍历图片以分割图像。这是我的代码以供更多理解:

MAXIMUM_COLOR_TRANSITION_DELTA = 100  # 0 - 765


def expand_segment_recursive(image, unexplored_foreground, segment, point, color):
    height, width, _ = image.shape
    # Unpack coordinates from point
    py, px = point

    # Create list of pixels to check
    neighbourhood_pixels = [(py, px + 1), (py, px - 1), (py + 1, px), (py - 1, px)]

    allowed_zone = unexplored_foreground & np.invert(segment)

    for y, x in neighbourhood_pixels:
        # Add pixel to segment if its coordinates within the image shape and its color differs from segment color no
        # more than MAXIMUM_COLOR_TRANSITION_DELTA
        if y in range(height) and x in range(width) and allowed_zone[y, x]:
            color_delta = np.sum(np.abs(image[y, x].astype(np.int) - color.astype(np.int)))
            print(color_delta)
            if color_delta <= MAXIMUM_COLOR_TRANSITION_DELTA:
                segment[y, x] = True
                segment = expand_segment_recursive(image, unexplored_foreground, segment, (y, x), color)
                allowed_zone = unexplored_foreground & np.invert(segment)

    return segment


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Pass image as the argument to use the tool")
        exit(-1)

    IMAGE_FILENAME = sys.argv[1]
    print(IMAGE_FILENAME)

    image = cv.imread(IMAGE_FILENAME)
    height, width, _ = image.shape

    # To filter the background I use median value of the image, as background in most cases takes > 50% of image area.
    background_color = np.median(image, axis=(0, 1))
    print("Background color: ", background_color)

    # Create foreground mask to find segments in it (TODO: Optimize this part)
    foreground = np.zeros(shape=(height, width, 1), dtype=np.bool)
    for y in range(height):
        for x in range(width):
            if not np.array_equal(image[y, x], background_color):
                foreground[y, x] = True

    unexplored_foreground = foreground

    for y in range(height):
        for x in range(width):
            if unexplored_foreground[y, x]:
                segment = np.zeros(foreground.shape, foreground.dtype)
                segment[y, x] = True
                segment = expand_segment_recursive(image, unexplored_foreground, segment, (y, x), image[y, x])

                cv.imshow("segment", segment.astype(np.uint8) * 255)

                while cv.waitKey(0) != 27:
                    continue

这是期望的结果:

在运行时结束时,我预计会提取 13 个分离的片段(对于这个特定的图像)。但相反,我得到了 RecursionError: maximum recursion depth exceeded,这并不奇怪,因为可以为图像的每个像素调用 expand_segment_recursive()。而且,即使使用 600x500 的小图像分辨率,我也能接到最多 300K 次调用。

我的问题是如何在这种情况下摆脱递归,并可能使用 Numpy 或 OpenCV 算法优化算法?

【问题讨论】:

  • 也许您可以提供另一个样本,另外,请提供您希望获得的“结果”
  • 哇,循环/递归看起来是个坏主意。您可以尝试进行二值化(阈值),然后连接组件?或者如果它像这个标志可能会转换为 HSV 颜色空间并执行 inRange 以选择蓝色部分?在这种特定情况下,您甚至可以只做一个阈值来获取“蓝色”部分的掩码
  • @api55 关键是我们有几个颜色相同的段。如果我对图像应用阈值,我将获得该颜色的所有片段。不合适,在这种情况下必须将段分开。连接的组件看起来很相关,你有使用它的好例子吗?
  • ahhhh 好的,现在它是有道理的,但是做阈值然后连接组件可能会成功。给我一分钟,我把它写下来作为答案
  • 这个问题也可能有用:使用cv::inRange (OpenCV) stackoverflow.com/questions/10948589/… 选择正确的 HSV 上下边界进行颜色检测

标签: python numpy opencv image-processing


【解决方案1】:

您实际上可以通过几个步骤使用thresholded 图像(二进制)和connectedComponents 来完成这项工作。此外,您可以使用findContours 或其他方法。

代码如下:

import numpy as np
import cv2

# load image as greyscale
img = cv2.imread("hp.png", 0)

# puts 0 to the white (background) and 255 in other places (greyscale value < 250)
_, thresholded = cv2.threshold(img, 250, 255, cv2.THRESH_BINARY_INV)

# gets the labels and the amount of labels, label 0 is the background
amount, labels = cv2.connectedComponents(thresholded)

# lets draw it for visualization purposes
preview = np.zeros((img.shape[0], img.shape[2], 3), dtype=np.uint8)

print (amount) #should be 3 -> two components + background

# draw label 1 blue and label 2 green
preview[labels == 1] = (255, 0, 0)
preview[labels == 2] = (0, 255, 0)

cv2.imshow("frame", preview)
cv2.waitKey(0)

最后,经过阈值处理的图像将如下所示:

预览图像(带有彩色部分的图像)将如下所示:

使用蒙版,您始终可以使用 numpy 函数来获取所需片段的坐标或为它们着色(就像我在预览中所做的那样)

更新

要获得不同颜色的片段,您可以尝试在片段之间创建“边框”。由于它们是纯色而不是渐变色,你可以尝试做一个像 canny 这样的边缘检测器,然后在图像中将其置于黑色......

import numpy as np
import cv2

img = cv2.imread("total.png", 0)

# background to black
img[img>=200] = 0
# get edges
canny = cv2.Canny(img, 60, 180)
# make them thicker
kernel = np.ones((3,3),np.uint8)
canny = cv2.morphologyEx(canny, cv2.MORPH_DILATE, kernel)
# apply edges as border in the image
img[canny==255] = 0

# same as before
amount, labels = cv2.connectedComponents(img)
preview = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
print (amount) #should be 14 -> 13 components + background

# color them randomly
for i in range(1, amount):
  preview[labels == i] = np.random.randint(0,255, size=3, dtype=np.uint8)

cv2.imshow("frame", preview )
cv2.waitKey(0)

结果是:

【讨论】:

  • 谢谢@api55 的例子。它为我解释了一个解决方案。因为我可能在图像上有不同颜色的片段(刚刚意识到这个例子没有涵盖任务的所有边缘情况)我将为每种颜色的几个 inRange() 矩阵执行 connectedComponents()。
  • 您应该放一个颜色更多的示例图像,以便我能够更好地帮助您...连接的组件有时很昂贵,因此在大图像中多次处理可能需要几秒钟的时间。 .也许另一种方法更好..你也可以试试分水岭算法
  • 我更新了示例。现在您可以看到图像实际上可以有不同颜色的片段,并且必须提取每个片段。
  • @Ruslan 有时,当新问题有所不同并且答案被接受时,最好打开一个新问题。无论如何,我更新了我的答案以匹配新徽标......它不像以前那么干净,因为徽标在彩色部分周围有一些非纯白色
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-07-01
  • 1970-01-01
  • 2019-04-30
  • 2019-05-27
  • 1970-01-01
  • 2016-02-15
  • 1970-01-01
相关资源
最近更新 更多