使用Python OpenCV进行粒子检测

5

我正在寻找一种适当的解决方案,以便在这张图片中计算粒子数量并测量它们的大小:

这张图片

最终我需要得到粒子坐标和面积的列表。经过在互联网上的一些搜索后,我意识到有3种粒子检测方法:

  1. 斑点
  2. 轮廓
  3. connectedComponentsWithStats

由于不同项目中使用了不同的方法,因此我进行了混合编写代码。

import pylab
import cv2
import numpy as np

高斯模糊和阈值处理
original_image = cv2.imread(img_path)
img = original_image
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = cv2.GaussianBlur(img, (5, 5), 0)
img = cv2.blur(img, (5, 5))
img = cv2.medianBlur(img, 5)
img = cv2.bilateralFilter(img, 6, 50, 50)

max_value = 255
adaptive_method = cv2.ADAPTIVE_THRESH_GAUSSIAN_C
threshold_type = cv2.THRESH_BINARY
block_size = 11
img_thresholded = cv2.adaptiveThreshold(img, max_value, adaptive_method, threshold_type, block_size, -3)

过滤小物体

min_size = 4
nb_components, output, stats, centroids = cv2.connectedComponentsWithStats(img, connectivity=8)
sizes = stats[1:, -1]
nb_components = nb_components - 1

# for every component in the image, you keep it only if it's above min_size
for i in range(0, nb_components):
    if sizes[i] < min_size:
       img[output == i + 1] = 0

生成轮廓以填充空洞和测量。 pos_listsize_list 是我们正在寻找的内容。
contours, hierarchy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
pos_list = []
size_list = []
for i in range(len(contours)):
    area = cv2.contourArea(contours[i])
    size_list.append(area)
    (x, y), radius = cv2.minEnclosingCircle(contours[i])
    pos_list.append((int(x), int(y)))

对于自检,如果我们将这些坐标绘制在原始图像上

pts = np.array(pos_list)
pylab.figure(0)
pylab.imshow(original_image)
pylab.scatter(pts[:, 0], pts[:, 1], marker="x", color="green", s=5, linewidths=1)
pylab.show()

我们可能会得到以下内容:

like that

我对结果并不满意。一些明显可见的颗粒未被计算,而某些强度的疑惑波动被计算在内。我现在正在尝试使用不同的滤镜设置,但感觉它是错误的。
如果有人知道如何改进我的解决方案,请分享。

你无法逃避贝叶斯错误率。 - user1196549
如果我们在使用k-means之前应用阈值会怎样呢? - William Davis
1
@WilliamDavis 你可以在应用k-means之前设置阈值,但可能会失去轮廓精度。我建议先使用k-means是因为它将图像分割成主要颜色,因为颗粒可能具有不同的灰度/黑色阴影。先进行阈值处理会丢失细节,但这取决于你。 - nathancy
1个回答

6
由于粒子为白色,背景为黑色,我们可以使用Kmeans颜色量化将图像分成两组(cluster=2),这样就可以轻松区分粒子和背景。由于粒子可能非常小,我们应该尽量避免模糊、膨胀或任何可能改变粒子轮廓的形态学操作。以下是一种方法:
  1. Kmeans颜色量化。 我们对图像进行Kmeans颜色量化处理(cluster=2),然后使用Otsu阈值法得到二进制图像。
  2. 过滤掉超小噪点。 接下来,我们找到轮廓,在轮廓面积过滤时去除噪声,并收集每个粒子的(x, y)坐标及其面积。通过“填充”这些轮廓来有效地删除二进制掩模上的微小粒子。
  3. 将掩模应用在原始图像上。 现在,我们将筛选好的掩模与原始图像进行按位与运算,以突出显示粒子集群。

Kmeans聚类时使用clusters=2的效果如下:

enter image description here

结果展示如下:

enter image description here

Number of particles: 204
Average particle size: 30.537

代码

import cv2
import numpy as np
import pylab

# Kmeans 
def kmeans_color_quantization(image, clusters=8, rounds=1):
    h, w = image.shape[:2]
    samples = np.zeros([h*w,3], dtype=np.float32)
    count = 0

    for x in range(h):
        for y in range(w):
            samples[count] = image[x][y]
            count += 1

    compactness, labels, centers = cv2.kmeans(samples,
            clusters, 
            None,
            (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10000, 0.0001), 
            rounds, 
            cv2.KMEANS_RANDOM_CENTERS)

    centers = np.uint8(centers)
    res = centers[labels.flatten()]
    return res.reshape((image.shape))

# Load image
image = cv2.imread('1.png')
original = image.copy()

# Perform kmeans color segmentation, grayscale, Otsu's threshold
kmeans = kmeans_color_quantization(image, clusters=2)
gray = cv2.cvtColor(kmeans, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Find contours, remove tiny specs using contour area filtering, gather points
points_list = []
size_list = []
cnts, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2:]
AREA_THRESHOLD = 2
for c in cnts:
    area = cv2.contourArea(c)
    if area < AREA_THRESHOLD:
        cv2.drawContours(thresh, [c], -1, 0, -1)
    else:
        (x, y), radius = cv2.minEnclosingCircle(c)
        points_list.append((int(x), int(y)))
        size_list.append(area)

# Apply mask onto original image
result = cv2.bitwise_and(original, original, mask=thresh)
result[thresh==255] = (36,255,12)

# Overlay on original
original[thresh==255] = (36,255,12)

print("Number of particles: {}".format(len(points_list)))
print("Average particle size: {:.3f}".format(sum(size_list)/len(size_list)))

# Display
cv2.imshow('kmeans', kmeans)
cv2.imshow('original', original)
cv2.imshow('thresh', thresh)
cv2.imshow('result', result)
cv2.waitKey()

3
喜欢使用K-means的想法。 - Jeru Luke

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