【问题标题】:How to count different grains in an image using cv2?如何使用 cv2 计算图像中的不同颗粒?
【发布时间】:2021-07-20 11:14:28
【问题描述】:

我有一张图片,其中包含以下谷物:

图片有:

  • 3 颗核桃
  • 3 个葡萄干
  • 3 颗南瓜子
  • 27 种外观相似的谷物

我希望使用 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

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

有人可以帮忙吗?

【问题讨论】:

  • 首先,使用放置在相机位置附近的某个光源重新拍摄图像。这些由左侧光线引起的沉重阴影使事情变得复杂了 100 倍。阈值设置将很容易,并且可以通过轮廓大小(核桃)、颜色/饱和度(葡萄干、南瓜子 [也许])来区分单个对象类别,然后剩下的部分是谷物。

标签: python opencv image-processing object-detection opencv3.0


【解决方案1】:

您的光线不好,正如 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 操作的blob:

# 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。我们还可以过滤小区域的 blob,并将每个边界矩形存储在一个列表中:

# 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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-10
    • 2012-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多