如何使用 cv2 计算图像中的不同颗粒?

How to count different grains in an image using cv2?

我有一张图片,下面有麦片:

图片有:

想用opencv分别统计,不想识别。到目前为止,我已经定制了 AdaptiveThreshold 方法来计算所有种子,但不确定如何单独进行。这是我的脚本:

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('/Users/vaibhavsaxena/Desktop/Screen Shot 2021-04-27 at 12.22.46.png', 0)
#img = cv2.fastNlMeansDenoisingColored(img,None,10,10,7,21)
windowSize = 31
windowConstant = 40
mask = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, windowSize, windowConstant)
plt.imshow(mask)

stats = cv2.connectedComponentsWithStats(mask, 8)[2]
label_area = stats[1:, cv2.CC_STAT_AREA]

min_area, max_area = 345, max(list(label_area))  # min/max for a single circle
singular_mask = (min_area < label_area) & (label_area <= max_area)
circle_area = np.mean(label_area[singular_mask])

n_circles = int(np.sum(np.round(label_area / circle_area)))

print('Total circles:', n_circles)

36

但这一个似乎很难编码。例如,如果我放大或缩小图像,它会产生不同的计数。

有人可以帮忙吗?

按照 HansHirse 的建议,您的光线不好,请尝试将拍摄照片的条件标准化。然而,有一种方法可以稍微规范化照明并使其尽可能均匀。该方法称为增益划分。这个想法是您尝试构建背景模型,然后通过该模型对每个输入像素进行加权。在大部分图像期间,输出增益应该相对恒定。让我们试一试:

# imports:
import cv2
import numpy as np

# Reading an image in default mode:
inputImage = cv2.imread(path + fileName)
# Deep copy for results:
inputImageCopy = inputImage.copy()

# Get local maximum:
kernelSize = 30
maxKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
localMax = cv2.morphologyEx(inputImage, cv2.MORPH_CLOSE, maxKernel, None, None, 1, cv2.BORDER_REFLECT101)

# Perform gain division
gainDivision = np.where(localMax == 0, 0, (inputImage/localMax))

# Clip the values to [0,255]
gainDivision = np.clip((255 * gainDivision), 0, 255)

# Convert the mat type from float to uint8:
gainDivision = gainDivision.astype("uint8")

必须小心这些数据类型及其转换。这是结果:

如您所见,现在大部分背景都是均匀的,这非常棒,因为现在我们可以应用简单的阈值处理方法。让我们尝试 Otsu's Thresholding 来获得一个漂亮的元素二进制掩码:

# Convert RGB to grayscale:
grayscaleImage = cv2.cvtColor(gainDivision, cv2.COLOR_BGR2GRAY)

# Get binary image via Otsu:
_, binaryImage = cv2.threshold(grayscaleImage, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

产生这个二进制掩码:

蒙版可以通过应用 morphology 来改善,让我们尝试使用温和的 closing 操作加入那些斑点:

# Set kernel (structuring element) size:
kernelSize = 3
# Set morph operation iterations:
opIterations = 2

# Get the structuring element:
morphKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))

# Perform closing:
binaryImage = cv2.morphologyEx( binaryImage, cv2.MORPH_CLOSE, morphKernel, None, None, opIterations, cv2.BORDER_REFLECT101 )

这是结果:

好的,现在,为了完整起见,让我们尝试计算所有元素的 bounding rectangles。我们还可以过滤小面积的斑点并将每个边界矩形存储在列表中:

# Find the blobs on the binary image:
contours, hierarchy = cv2.findContours(binaryImage, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Store the bounding rectangles here:
rectanglesList = []

# Look for the outer bounding boxes (no children):
for _, c in enumerate(contours):

    # Get blob area:
    currentArea = cv2.contourArea(c)
    # Set a min area threshold:
    minArea = 100

    if currentArea > minArea:

        # Approximate the contour to a polygon:
        contoursPoly = cv2.approxPolyDP(c, 3, True)
        # Get the polygon's bounding rectangle:
        boundRect = cv2.boundingRect(contoursPoly)

        # Store rectangles in list:
        rectanglesList.append(boundRect)

        # Get the dimensions of the bounding rect:
        rectX = boundRect[0]
        rectY = boundRect[1]
        rectWidth = boundRect[2]
        rectHeight = boundRect[3]

        # Set bounding rect:
        color = (0, 0, 255)
        cv2.rectangle( inputImageCopy, (int(rectX), int(rectY)),
                   (int(rectX + rectWidth), int(rectY + rectHeight)), color, 2 )

        cv2.imshow("Rectangles", inputImageCopy)
        cv2.waitKey(0)

最终图片是这样的:

这是检测到的元素总数:

print("Elements found: "+str(len(rectanglesList)))
Elements found: 37

如您所见,存在误报。颗粒的一点阴影被检测为实际颗粒。也许调整最小面积可以解决这个问题。或者,如果你无论如何都要对每一种谷物进行分类,你可以过滤掉这种噪音。