如何在PIL中选择与图像边缘连续的所有黑色像素?

时间:2019-04-09 17:30:11

标签: python opencv image-processing computer-vision python-imaging-library

我有一组图像培养皿,不幸的是它们的质量不是最高的(下面的示例,轴不是图像的一部分)。 dish1 我正在尝试选择背景,并使用以下内容计算其面积(以像素为单位):

image = Image.open(path)
black_image = 1 * (np.asarray(image.convert('L')) < 12)
black_region = black_image.sum()

这将产生以下内容:

enter image description here

如果我对黑色像素的选择更加严格,则会错过其他图像中的像素,如果我比较松散,则最终会选择太多的培养皿本身。有一种方法我只能选择亮度值小于12并且与边缘连续的像素吗?我也愿意接受openCV解决方案。

4 个答案:

答案 0 :(得分:3)

如果您将图像的最上面的一行/行和最下面的一行/行进行阈值处理,您将得到此图,其中我将第一行放在顶部,将底部行放在底部,原始图像的限制-无需您这样做,我只是在说明这种技术。

enter image description here

现在查看线条从黑色变为白色,然后由白色变为黑色(顶部以红色圈出)的位置。不幸的是,您的图像带有注释和轴,我必须对其进行修剪,以便您的编号不会完全相同。在最上面一行/行上,我的图像在第319列从黑色变为白色,然后在第648列变为黑色。如果将它们相加,则得到966并除以2,则x轴上的图像中心在483列

看底线/行的过渡(用红色圆圈表示)在第234和736列,它们加起来等于970,这在平均时为485,所以我们知道圆心在垂直图像列483-485上或说484。

然后,您现在应该可以算出图像的中心和半径并遮盖图像以准确计算背景。

答案 1 :(得分:3)

希望我并没有过分简化这个问题,但是从我的角度来看,使用具有简单阈值,形态学运算和findContours的OpenCV应该可以解决问题。

请参见以下代码:

import cv2
import numpy as np

# Input
input = cv2.imread('images/x0ziO.png', cv2.IMREAD_COLOR)

# Input to grayscale
gray = cv2.cvtColor(input, cv2.COLOR_BGR2GRAY)

# Binary threshold
_, gray = cv2.threshold(gray, 20, 255, cv2.THRESH_BINARY)

# Morphological improvements of the mask
gray = cv2.morphologyEx(gray, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)))
gray = cv2.morphologyEx(gray, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (11, 11)))

# Find contours
cnts, _ = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

# Filter large size contours; at the end, there should only be one left
largeCnts = []
for cnt in cnts:
    if (cv2.contourArea(cnt) > 10000):
        largeCnts.append(cnt)

# Draw (filled) contour(s)
gray = np.uint8(np.zeros(gray.shape))
gray = cv2.drawContours(gray, largeCnts, -1, 255, cv2.FILLED)

# Calculate background pixel area
bgArea = input.shape[0] * input.shape[1] - cv2.countNonZero(gray)

# Put result on input image
input = cv2.putText(input, 'Background area: ' + str(bgArea), (20, 30), cv2.FONT_HERSHEY_COMPLEX_SMALL, 1.0, (255, 255, 255))

cv2.imwrite('images/output.png', input)

中间的“蒙版”图像如下所示:

Mask

然后,最终输出看起来像这样:

Output

答案 2 :(得分:2)

尝试实验性floodfill()方法。 https://pillow.readthedocs.io/en/5.1.x/reference/ImageDraw.html?highlight=floodfill#PIL.ImageDraw.PIL.ImageDraw.floodfill

如果您的所有图像都与示例相同,则只需选择图像的两个或四个角来填充粉红色,然后算上即可。

另请参见Image Segmentation with Watershed Algorithm,它非常类似于泛洪填充,但不依赖于单一的唯一颜色。

答案 3 :(得分:2)

  

由于您愿意接受OpenCV方法,因此可以使用   SimpleBlobDetector

显然,我得到的结果也不完美,因为要设置的超参数很多。超参数使它非常灵活,因此它是一个不错的起点。

这就是检测器的功能(请参见详细here):

  1. 阈值:通过使用以minThreshold开始的阈值对源图像进行阈值处理,将源图像转换为多个二进制图像。这些阈值将增加thresholdStep直到maxThreshold。因此,第一个阈值为minThreshold,第二个阈值为minThreshold + thresholdStep,第三个阈值为minThreshold + 2 x thresholdStep,依此类推。
  2. 分组:在每个二进制图像中,连接的白色像素被分组在一起。我们称这些二进制blob。
  3. 合并:计算二进制图像中二进制blob的中心,并合并比minDistBetweenBlobs更近的blob。

  4. 中心和半径计算:计算并返回新合并的Blob的中心和半径。

找到下面的代码。

Output Image

# Standard imports
import cv2
import numpy as np

# Read image
im = cv2.imread("petri.png", cv2.IMREAD_COLOR)

# Setup SimpleBlobDetector parameters.
params = cv2.SimpleBlobDetector_Params()

# Change thresholds
params.minThreshold = 0
params.maxThreshold = 255

# Set edge gradient
params.thresholdStep = 5

# Filter by Area.
params.filterByArea = True
params.minArea = 10

# Set up the detector with default parameters.
detector = cv2.SimpleBlobDetector_create(params)

# Detect blobs.
keypoints = detector.detect(im)

# Draw detected blobs as red circles.
# cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures the size of the circle corresponds to the size of blob
im_with_keypoints = cv2.drawKeypoints(im, keypoints, np.array([]), (0, 0, 255),
                                      cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

# Show keypoints
cv2.imshow("Keypoints", im_with_keypoints)
cv2.waitKey(0)