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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在PIL中选择与图像边缘相邻的所有黑色像素?相关的知识,希望对你有一定的参考价值。

我有一套图像培养皿,遗憾的是它们不是最高质量的(下面的例子,轴不是图像的一部分)。 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解决方案持开放态度。

答案

希望我没有过分简化这个问题,但从我的观点来看,使用OpenCV进行简单的阈值处理,形态运算和findContours应该可以胜任。

请看下面的代码:

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

另一答案

如果你拍摄图像的最顶行/行和最底线/行和阈值,你会得到这个图,我把顶行排在顶部,底行排在底部,就在极限之外原始图像 - 你没有必要这样做,我只是在说明这项技术。

enter image description here

现在看看线条从黑色变为白色然后从白色变为黑色(在顶部用红色圈出)。不幸的是,你的图像有注释和轴,我不得不修剪,所以你的数字不会完全相同。在顶行/行,我的图像在第319行从黑色变为白色,在第648行变回黑色。如果我将它们加在一起,我得到966并除以2,x轴上的图像中心位于第483列。

查看底线/行,过渡(以红色圈出)位于列234和736,它们加起来为970,当平均时为485,因此我们知道圆心位于垂直图像列483-485或484。

那么你现在应该能够计算出图像中心和半径,并掩盖图像以准确计算背景。

另一答案

由于您对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)
另一答案

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

如果您的所有图像都像示例,只需选择图像的两个或四个角来填充,例如粉红色并计算。

另请参阅Image Segmentation with Watershed Algorithm,这与洪水填充非常相似,但不依赖于单一的独特颜色。

以上是关于如何在PIL中选择与图像边缘相邻的所有黑色像素?的主要内容,如果未能解决你的问题,请参考以下文章

用 PIL 修剪扫描的图像?

如何使用 PIL 使所有白色像素透明?

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

如何使用python从图像中去除边界边缘噪声?

使用 OpenCV 计算图像中所有不同区域的像素

在 Python 中为图像中的每个像素选择 7*7 相邻像素的最快方法