如何裁剪轮廓的内部区域?

Posted

技术标签:

【中文标题】如何裁剪轮廓的内部区域?【英文标题】:How to crop the internal area of a contour? 【发布时间】:2015-04-29 19:43:18 【问题描述】:

我正在研究视网膜眼底图像。该图像由黑色背景上的圆形视网膜组成。使用 OpenCV,我设法获得了围绕整个圆形 Retina 的轮廓。我需要的是从黑色背景中裁剪出圆形视网膜。

【问题讨论】:

裁剪是什么意思?图像始终是矩形的,因此您可以做的是创建一个蒙版并仅对蒙版像素执行以下操作(处理/渲染)。或者您可以裁剪图像,使其由视网膜的边界框表示,该边界框可能比原始图像小得多,但仍会有一些黑色部分(因为视网膜不是矩形)。或者您可以裁剪到视网膜内的最大矩形区域,该区域不会留下任何黑色背景像素,但也会移除部分视网膜。你要哪一个? 如果是最后一种情况请尝试***.com/questions/21410449/… 【参考方案1】:

这是一个非常简单的方法。用透明度遮盖图像。

    阅读图片 制作灰度版本。 大津阈值 将打开和关闭的形态学应用到阈值图像作为蒙版 将遮罩放入输入的 alpha 通道 保存输出

输入

代码

import cv2
import numpy as np


# load image as grayscale
img = cv2.imread('retina.jpeg')

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# threshold input image using otsu thresholding as mask and refine with morphology
ret, mask = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU) 
kernel = np.ones((9,9), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)

# put mask into alpha channel of result
result = img.copy()
result = cv2.cvtColor(result, cv2.COLOR_BGR2BGRA)
result[:, :, 3] = mask

# save resulting masked image
cv2.imwrite('retina_masked.png', result)

输出

【讨论】:

这只会删除背景 - 这不会按照原始问题进行裁剪 是的,你是对的。我当时一定是看错了问题。【参考方案2】:

您的问题不清楚您是要实际裁剪轮廓内定义的信息还是屏蔽与所选轮廓无关的信息。我将探讨在这两种情况下该怎么做。


掩盖信息

假设您在图像上运行cv2.findContours,您将收到一个列出图像中所有可用轮廓的结构。我还假设您知道用于围绕您想要的对象的轮廓的 index。假设它存储在idx 中,首先使用cv2.drawContours 将此轮廓的填充 版本绘制到空白图像上,然后使用此图像索引到您的图像中以提取对象。这种逻辑掩盖任何不相关的信息,只保留重要的信息 - 在您选择的轮廓内定义。执行此操作的代码如下所示,假设您的图像是存储在img 中的灰度图像:

import numpy as np
import cv2
img = cv2.imread('...', 0) # Read in your image
# contours, _ = cv2.findContours(...) # Your call to find the contours using OpenCV 2.4.x
_, contours, _ = cv2.findContours(...) # Your call to find the contours
idx = ... # The index of the contour that surrounds your object
mask = np.zeros_like(img) # Create mask where white is what we want, black otherwise
cv2.drawContours(mask, contours, idx, 255, -1) # Draw filled contour in mask
out = np.zeros_like(img) # Extract out the object and place into output image
out[mask == 255] = img[mask == 255]

# Show the output image
cv2.imshow('Output', out)
cv2.waitKey(0)
cv2.destroyAllWindows()

如果你真的想裁剪...

如果要裁剪图像,则需要定义轮廓定义的区域的最小跨越边界框。您可以找到边界框的左上角和右下角,然后使用索引来裁剪出您需要的内容。代码会和之前一样,但是会有一个额外的裁剪步骤:

import numpy as np
import cv2
img = cv2.imread('...', 0) # Read in your image
# contours, _ = cv2.findContours(...) # Your call to find the contours using OpenCV 2.4.x
_, contours, _ = cv2.findContours(...) # Your call to find the contours
idx = ... # The index of the contour that surrounds your object
mask = np.zeros_like(img) # Create mask where white is what we want, black otherwise
cv2.drawContours(mask, contours, idx, 255, -1) # Draw filled contour in mask
out = np.zeros_like(img) # Extract out the object and place into output image
out[mask == 255] = img[mask == 255]

# Now crop
(y, x) = np.where(mask == 255)
(topy, topx) = (np.min(y), np.min(x))
(bottomy, bottomx) = (np.max(y), np.max(x))
out = out[topy:bottomy+1, topx:bottomx+1]

# Show the output image
cv2.imshow('Output', out)
cv2.waitKey(0)
cv2.destroyAllWindows()

裁剪代码的工作原理是,当我们定义掩码以提取轮廓定义的区域时,我们还会找到定义轮廓左上角的最小水平和垂直坐标。我们同样找到定义轮廓左下角的最大水平和垂直坐标。然后,我们使用这些坐标的索引来裁剪我们实际需要的内容。请注意,这会在 蒙版 图像上执行裁剪 - 该图像会删除除最大轮廓中包含的信息之外的所有内容。

OpenCV 3.x 的注意事项

需要注意的是,上面的代码假设您使用的是 OpenCV 2.4.x。请注意,在 OpenCV 3.x 中,cv2.findContours 的定义发生了变化。具体来说,输出是一个三元素元组输出,其中第一个图像是源图像,而其他两个参数与 OpenCV 2.4.x 中的相同。因此,只需将上述代码中的cv2.findContours 语句更改为忽略第一个输出即可:

_, contours, _ = cv2.findContours(...) # Your call to find contours

【讨论】:

@RedetGetachew - 不,坐标没有交换。 np.where 的输出提供x 中的非零行位置和y 中的列位置。因此,对数组的索引是正确的。请在提出修改建议之前实际测试您的更改。 在 OpenCV 中正好相反,x 代表列位置,y 代表行位置,这就是它们看起来被交换的原因。 @JoãoCartucho 如果您使用np.where,则不会。在这种情况下,您可以使用第一种方法,但第二种方法则不行。 如何将其应用于彩色图像? @DavidIbrahim 仍然在灰度转换后的图像上执行此操作,但您需要复制蒙版,以便有三个通道,每个通道具有相同的蒙版内容然后索引。我可以稍后修改我的帖子以解决这样做的颜色,但它几乎是相同的过程。【参考方案3】:

这是另一种裁剪矩形 ROI 的方法。主要思想是使用Canny边缘检测找到视网膜的边缘,找到轮廓,然后使用Numpy切片提取ROI。假设您有这样的输入图像:

提取的投资回报率

import cv2

# Load image, convert to grayscale, and find edges
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)[1]

# Find contour and sort by contour area
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)

# Find bounding box and extract ROI
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    ROI = image[y:y+h, x:x+w]
    break

cv2.imshow('ROI',ROI)
cv2.imwrite('ROI.png',ROI)
cv2.waitKey()

【讨论】:

以上是关于如何裁剪轮廓的内部区域?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 OpenCV 从垫子上裁剪任意形状?

用少量可用点裁剪感兴趣的区域

opencv如何读取多边形区域内的像素值?

如何在不使用内部相机裁剪的情况下裁剪图像

如何在 OpenCV - Python 中获得没有冗余的内部轮廓点

使用OpenCV(Python)找到具有最大封闭区域的轮廓