玉米粒的数字图像处理

SMD*_*SMD 13 python opencv image-processing detection computer-vision

我正在尝试从优质或健康的玉米粒中识别和计数受昆虫感染的玉米粒。我已经完成阈值处理,直到在图像中所有玉米粒周围绘制轮廓。

虫子出没(有洞,颜色褪黄)和好的玉米粒

仅供参考,受昆虫侵扰的玉米粒有洞并且颜色褪色。我应该如何从包含受感染玉米粒和良好玉米粒的图像中获取受感染玉米粒的百分比?我也愿意接受其他建议。

Tom*_*eva 12

我将提供一个解决方案,它实现图像处理最基本的思想之一,即对象的特征表示。在下面的示例中,我将展示我们如何做到:

\n
    \n
  1. 去掉玉米粒的背景
  2. \n
  3. 使用格林定理提取每个玉米粒的质心位置
  4. \n
  5. 将每个玉米粒从感兴趣的 RGB 区域转换为直方图
  6. \n
  7. 使用每个内核的直方图表示和 k 均值算法将相似的标签分配给相似的内核。
  8. \n
\n

我将逐步介绍算法的各个阶段以及每个阶段的结果,代码将附在最后

\n

项目基础设施

\n

我们的小项目将很方便地组织在CornClassifier班级下。第一阶段是导入所需的库并设置__init__()方法。

\n

本节中定义的每个参数都__init__()将在实现过程中使用。

\n

首先,为了方便起见,我们将首先读取图像并将其保存在本地参数下CornClassifier,无论是彩色还是灰度。因此,我们将编写该load_image函数,这将使我们的类基础结构如下所示:

\n
class CornClassifier:\n    def __init__(self, image):\n        self.path  = image\n        # Image\n        self.image           = None\n        self.image_grayscale = None\n        # Masking parameters\n        self.ret  = None\n        self.mask = None\n        self.masked_image     = None\n        self.masked_image_lab = None\n        # Corn centroid parameters\n        self.centroid_tuples = []\n        self.centroid_x      = []\n        self.centroid_y      = []\n        self.contours        = []  # Saving the contours for the histogram computations\n        # Corn histograms\n        self.corn_histograms = []\n\n    def load_image(self, show=False):\n        """\n        :param show: Plotting the image to screen\n        :return: loading the image from the path to the attribute `image`\n        """\n        self.image           = cv2.imread(self.path, cv2.IMREAD_COLOR)\n        self.image_grayscale = cv2.imread(self.path, cv2.IMREAD_GRAYSCALE)\n        if show:\n            plt.imshow(self.image[:,:,[2,1,0]])  # cv2.imread flips the channel order\n            plt.show()\n
Run Code Online (Sandbox Code Playgroud)\n

背景去除

\n

在本节中,我们将删除背景,从而更好地区分“好”和“坏”玉米粒。这是通过利用背景是黑色而玉米粒不是的事实来完成的。本节的主要步骤是:

\n
    \n
  1. 对灰度图像进行高斯模糊。这将使玉米粒稍微模糊,同时使黑色表面的白色闪光变得更暗。这将有助于将背景与玉米粒分开。

    \n
  2. \n
  3. 使用 Otsu 的方法对模糊图像执行阈值处理,在我们有黑色背景和白色前景的情况下,这是更好的选择,就像灰度图像的情况一样(您可以在此处阅读更多信息

    \n
  4. \n
  5. 假设玉米粒清晰分离,我们将在阶段(2)的二值图像输出中找到所有不同的轮廓。对于每个轮廓,我们将填充每个形状的内容,以便更好地掩盖每个玉米粒。

    \n
  6. \n
  7. 创建遮罩后,我们将应用遮罩并将颜色空间从 RGB 更改为可以更好地将“好”内核与“坏”内核分开的颜色空间。在尝试了一些颜色空间之后,我发现最好的一个是 LAB 空间,它包括:

    \n
      \n
    • 亮度(强度)
    • \n
    • A - 颜色分量范围从绿色到洋红色
    • \n
    • B - 从蓝色到黄色的颜色分量
    • \n
    \n
  8. \n
\n

您可以在此处阅读有关可用色彩空间的更多信息

\n

这将在函数中实现remove_background(参见下面的代码)。如果使用以下蒙版,则此背景删除的结果:\n在此输入图像描述

\n

结果蒙版图像(RGB)将是:\n在此输入图像描述

\n

让我们注意,仍然有一些小工件将在以下函数中处理。

\n

将玉米与剩余的文物分离

\n

在本节中,我们将删除任何残留的工件。我们将通过观察来实现这一点,即背景去除后所得到的蒙版图像几乎是完美的,并且任何剩余的伪影都很小,并且可以用具有少量面(或角)的多边形来表示。因此,我们将创建isolating_corn函数来做到这一点。20该函数迭代掩模中的所有轮廓,并丢弃在表示的多边形中不具有多个角的轮廓。通过测试的多边形保存在CornClassifier countours参数中,每个玉米粒的质心是使用每个轮廓的矩计算的(使用格林定理,其背后的理论有点复杂但可以理解,您可以在此处阅读更多内容

\n

应用此函数后,我们可以看到所有工件都已被丢弃,如下图所示。是否还有任何残留物,我们会看到一个没有任何玉米粒的质心。\n在此输入图像描述

\n

玉米粒表示

\n

在本节中,该项目最重要的部分正在发生,我们将把每个玉米粒表示为等效的像素直方图。由于 LAB 颜色空间有 3 个颜色通道,因此 na\xc3\xafve 方法会将每个玉米粒表示为向量(255*3)X1 = 765X1(不包括 LAB 颜色空间的黑色等效分量以忽略背景)。下面给出了一些直方图的示例。我们可以看到绿色和蓝色直方图有些相似,红色直方图与其他两个不同。\n在此输入图像描述

\n

尽管如此,我们可以做得更好。玉米粒不是纯朗伯表面(您可以在此处阅读有关朗伯反射的更多信息),我们将假设它们是纯朗伯表面。这意味着每个内核的旋转和每个内核的形状都会引起照明的变化,从而导致稍微不同的反射和稍微不同的颜色。因此,我们将接近的颜色分组在一起,并将每个通道中的 bin 总数从 减少25616,从而得到 (15*3)X1 = 45X1直方图向量。现在,相同的玉米粒将在以下直方图中表示:\n在此输入图像描述\n每个直方图将被保存以供将来在聚类算法中使用。这个的实现将在函数中compute_histograms(参见下面的代码)。我们可以看到,表示玉米粒的直方图可以进一步改进,因为我们可以看到某些箱在所有玉米粒中的值为零,但现在我们可以保留这一点。

\n

聚类

\n

到目前为止,我们已经为主要活动搭建好了舞台!现在我们有了玉米粒的表示,我们可以将它们分为不同的组。由于我们知道想要拥有的组数(2 表示“好”和“坏”),因此我们可以使用 K-means 算法K=2。关于这个算法有很多解释,所以我不会在这里留下参考。其实施如下。corn_histograms我们使用我们的参数并使用 来拟合 k 均值模型n_clusters=2。然后,我们提取每个直方图的匹配标签,并用不同颜色将每个簇的质心分散在原始图片上。这将在(参见下面的代码)中实现classify_corn\n结果如下所示:\n在此输入图像描述

\n

我们可以看到,玉米粒被分成两簇,其中一簇(红色质心)显示良好的玉米粒,另一簇(蓝色质心)显示红色玉米粒。聚类之后我们就有了一个labels向量,将每个核分配给一个就是K-means算法发现的两个聚类。计算两组中每一组的百分比可以如下完成:

\n
print(f\'Total corn kernels detected: {len(labels)}\')\nprint(f\'Number of "Blue" group kernels: {np.sum(labels == 1)} ; Percentage: {np.around(100 * np.sum(labels == 1) / len(labels), 2)} %\')\nprint(f\'Number of  "Red" group kernels: {np.sum(labels == 0)} ; Percentage: {100 - np.around(100 * np.sum(labels == 1) / len(labels), 2)} % \')\n
Run Code Online (Sandbox Code Playgroud)\n

导致:

\n
Total corn kernels detected: 70\nNumber of "Blue" group kernels: 27 ; Percentage: 38.57 %\nNumber of  "Red" group kernels: 43 ; Percentage: 61.43 % \n
Run Code Online (Sandbox Code Playgroud)\n

概括

\n

该项目总结了计算机视觉中两个非常重要的方面:

\n
    \n
  1. 对象的特征表示,在本例中是玉米粒的直方图表示
  2. \n
  3. 使用 k-means 算法通过特征表示对对象进行聚类
  4. \n
\n

为了方便起见,完整的CornClassifier类以及函数的调用如下:

\n
import cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom sklearn.cluster import KMeans\n\nclass CornClassifier:\n    def __init__(self, image):\n        self.path  = image\n        # Image\n        self.image           = None\n        self.image_grayscale = None\n        # Masking parameters\n        self.ret  = None\n        self.mask = None\n        self.masked_image     = None\n        self.masked_image_lab = None\n        # Corn centroid parameters\n        self.centroid_tuples = []\n        self.centroid_x      = []\n        self.centroid_y      = []\n        self.contours        = []  # Saving the contours for the histogram computations\n        # Corn histograms\n        self.corn_histograms = []\n\n    def load_image(self, show=False):\n        """\n        :param show: Plotting the image to screen\n        :return: loading the image from the path to the attribute `image`\n        """\n        self.image           = cv2.imread(self.path, cv2.IMREAD_COLOR)\n        self.image_grayscale = cv2.imread(self.path, cv2.IMREAD_GRAYSCALE)\n        if show:\n            plt.imshow(self.image[:,:,[2,1,0]])  # cv2.imread flips the channel order\n            plt.show()\n\n    def remove_background(self, show=False):\n        """\n        :param show: Plotting the mask to screen\n        :return:\n        1. Performing gaussian filtering to blur the noise of the black background\n        2. Performing Otsu\'s thresholding - practical example is given in:\n         https://opencv24-python-tutorials.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html#otsus-binarization\n        3. Fill contour to better mask the image\n        4. Mask the image ans change the colorspace\n        """\n        image         = self.image_grayscale.copy()\n        # Step 1\n        blurred_image = cv2.GaussianBlur(image, (5,5), 0)\n        # Step 2\n        self.ret, self.mask = cv2.threshold(blurred_image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)\n        # Step 3 - Filling holes in the corn kernel\n        contours, hierarchies = cv2.findContours(self.mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n        for c in contours:\n            cv2.fillPoly(self.mask, pts=[c], color=(255, 255, 255))\n        # Step 4\n        self.masked_image     = cv2.bitwise_and(self.image, self.image, mask=self.mask)\n        self.masked_image_lab = cv2.cvtColor(self.masked_image, cv2.COLOR_BGR2LAB)\n        if show:\n            plt.figure()\n            plt.imshow(self.mask, cmap=\'gray\')  # cv2.imread flips the channel order\n            plt.figure()\n            plt.imshow(self.masked_image[:,:,[2,1,0]])\n            plt.show()\n\n    def isolating_corn(self, show=False):\n        """\n        :param show:\n        :return: Extracting the coordinates of each corn object, assuming all corn kernels\n        are seperated from each other. We compute the centroids my computing the moments of each\n        corn kernel (Green\'s theorem)\n        https://learnopencv.com/find-center-of-blob-centroid-using-opencv-cpp-python/\n        """\n        mask = self.mask.copy()\n        # Finding the different contours\n        contours, hierarchies  = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n        for c in contours:\n            # removing small contours\n            if c.shape[0] < 20:\n                continue\n            # calculate moments for each contour\n            M = cv2.moments(c)\n            # calculate x,y coordinate of center\n            try:\n                cX = int(M["m10"] / M["m00"])\n                cY = int(M["m01"] / M["m00"])\n                self.centroid_tuples.append((cX, cY))\n                self.centroid_x.append(cX)\n                self.centroid_y.append(cY)\n                self.contours.append(c)\n            except ZeroDivisionError:\n                pass\n        if show:\n            plt.figure()\n            plt.imshow(self.masked_image[:,:,[2,1,0]])\n            plt.scatter(self.centroid_x, self.centroid_y)\n            plt.show()\n\n    def compute_histograms(self, show=False):\n        """\n        :param show:\n        :return: Computing the histogram for each corn kernel\n        """\n        for c in self.contours:\n            # Creating an image with just that filled contour\n            temp_mask = np.zeros_like(self.image)\n            cv2.fillPoly(temp_mask, pts=[c], color=(255, 255, 255))\n            single_corn = cv2.bitwise_and(self.masked_image_lab, temp_mask)\n            # Generating histograms, avoiding the 0 values\n            hist0 = cv2.calcHist([single_corn],[0],None,[16],[0,256])\n            hist1 = cv2.calcHist([single_corn],[1],None,[16],[0,256])\n            hist2 = cv2.calcHist([single_corn],[2],None,[16],[0,256])\n            total_hist = np.squeeze(np.vstack((hist0[1:], hist1[1:], hist2[1:])))\n            self.corn_histograms.append(total_hist / sum(total_hist))\n        if show:\n            plt.figure()\n            plt.stem(self.corn_histograms[10], markerfmt=\'b\', basefmt=\'b\')\n            plt.stem(self.corn_histograms[1], markerfmt=\'r\', basefmt=\'r\')\n            plt.stem(self.corn_histograms[-1], markerfmt=\'g\', basefmt=\'g\')\n            plt.show()\n\n    def classify_corn(self):\n        kmeans = KMeans(n_clusters=2, init=\'k-means++\', random_state=0).fit(self.corn_histograms)\n        labels = kmeans.labels_\n        print(f\'Total corn kernels detected: {len(labels)}\')\n        print(f\'Number of "Blue" group kernels: {np.sum(labels == 1)} ; Percentage: {np.around(100 * np.sum(labels == 1) / len(labels), 2)} %\')\n        print(f\'Number of  "Red" group kernels: {np.sum(labels == 0)} ; Percentage: {100 - np.around(100 * np.sum(labels == 1) / len(labels), 2)} % \')\n        plt.imshow(self.image[:,:,[2,1,0]])\n        plt.scatter(np.array(self.centroid_x)[labels.astype(bool)], np.array(self.centroid_y)[labels.astype(bool)], c=\'b\')\n        plt.scatter(np.array(self.centroid_x)[~labels.astype(bool)], np.array(self.centroid_y)[~labels.astype(bool)], c=\'r\')\n        plt.show()\n\nif __name__ == \'__main__\':\n    corn = CornClassifier(\'./corn.jpg\')\n    corn.load_image(False)\n    corn.remove_background(False)\n    corn.isolating_corn(False)\n    corn.compute_histograms(True)\n    corn.classify_corn()\n
Run Code Online (Sandbox Code Playgroud)\n


nat*_*ncy 8

这是一种使用 HSV 颜色阈值来区分良好内核和受感染内核的方法。由于我们知道良好的内核是黄色的,受感染的内核是灰色的,因此我们可以使用较低/较高的颜色范围来分割所需的对象。

  1. 计算谷粒总数并计算平均面积。我们加载图像转换为灰度大津阈值以获得二值图像,然后执行形态学操作以去除噪声。接下来,我们找到轮廓并跟踪总数number_of_kernels并计算average_area每个内核的 。

  2. HSV 颜色阈值以隔离良好/受感染的谷粒。接下来,我们将图像转换为 HSV,然后执行 HSV 颜色阈值以隔离好的内核。我们执行额外的形态学操作来消除噪音。

  3. 计算好颗粒的数量和感染的百分比。我们的想法是,我们可以使用一些任意的面积阈值比率,比如0.7575%,如果内核面积小于这个阈值面积,我们就可以断定它被感染了。换句话说,任何单个内核都需要具有至少average_area*的面积area_threshold才能被识别为好内核。我们迭代轮廓并保留一个代表通过过滤器的内核的计数器。从这里我们计算受感染内核的数量及其百分比。


这是管道的可视化

二值图像->形态学运算

Number of kernels: 70
Average kernel area: 6854.864
Run Code Online (Sandbox Code Playgroud)

接下来,我们使用 HSV 颜色范围来设置 HSV 颜色阈值来隔离好的内核

lower = np.array([0, 70, 97])
upper = np.array([179, 255, 255])
Run Code Online (Sandbox Code Playgroud)

良好的内核->检测到的内核

最后我们可以计算出受感染的内核数量及其百分比

Number of good kernels: 36
Number of infected: 34
Percentage infected: 48.571%
Run Code Online (Sandbox Code Playgroud)

您可以调整 HSV 下/上范围和area_threshold比率来微调您的结果

代码

import cv2
import numpy as np

# Load image, convert to grayscale, Otsu's threshold, morph operations to remove noise
image = cv2.imread('1.jpg')
original = image.copy()
result_mask = np.zeros(image.shape, dtype=np.uint8)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
erode = cv2.erode(thresh, kernel, iterations=2)
morph = cv2.morphologyEx(erode, cv2.MORPH_CLOSE, kernel, iterations=3)

# Count number of kernels and average kernel area
number_of_kernels = 0
average_area = 0
cnts = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    # Filter out tiny specs of noise
    area = cv2.contourArea(c)
    if area > 10:
        number_of_kernels += 1
        average_area += area

average_area /= number_of_kernels
print('Number of kernels:', number_of_kernels)
print('Average kernel area: {:.3f}'.format(average_area))

# Perform HSV color thresholding
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
lower = np.array([0, 70, 97])
upper = np.array([179, 255, 255])
mask = cv2.inRange(hsv, lower, upper)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3,3))
cleanup = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel, iterations=3)

# Find number of good kernels using an area threshold ratio relative to average kernel area
cnts = cv2.findContours(cleanup, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
area_threshold = 0.75
good_kernels = 0
for c in cnts:
    area = cv2.contourArea(c)
    if area > area_threshold * average_area:
        cv2.drawContours(image, [c], -1, (36,255,12), 4)
        cv2.drawContours(result_mask, [c], -1, (255,255,255), -1)
        good_kernels += 1

# Calculate number of infected kernels
result_mask = cv2.cvtColor(result_mask, cv2.COLOR_BGR2GRAY)
result = cv2.bitwise_and(original, original, mask=result_mask)
number_of_infected = number_of_kernels - good_kernels

print('Number of good kernels:', good_kernels)
print('Number of infected:', number_of_infected)
print('Percentage infected: {:.3f}%'.format((number_of_infected/number_of_kernels) * 100)) 

cv2.imshow('image', image)
cv2.imshow('thresh', thresh)
cv2.imshow('morph', morph)
cv2.imshow('result', result)
cv2.waitKey()
Run Code Online (Sandbox Code Playgroud)

这是一个简单的 HSV 颜色阈值脚本,用于使用轨迹栏确定下/上颜色范围。只需更改图像路径即可。

import cv2
import numpy as np

def nothing(x):
    pass

# Load image
image = cv2.imread('1.jpg')

# Create a window
cv2.namedWindow('image')

# Create trackbars for color change
# Hue is from 0-179 for Opencv
cv2.createTrackbar('HMin', 'image', 0, 179, nothing)
cv2.createTrackbar('SMin', 'image', 0, 255, nothing)
cv2.createTrackbar('VMin', 'image', 0, 255, nothing)
cv2.createTrackbar('HMax', 'image', 0, 179, nothing)
cv2.createTrackbar('SMax', 'image', 0, 255, nothing)
cv2.createTrackbar('VMax', 'image', 0, 255, nothing)

# Set default value for Max HSV trackbars
cv2.setTrackbarPos('HMax', 'image', 179)
cv2.setTrackbarPos('SMax', 'image', 255)
cv2.setTrackbarPos('VMax', 'image', 255)

# Initialize HSV min/max values
hMin = sMin = vMin = hMax = sMax = vMax = 0
phMin = psMin = pvMin = phMax = psMax = pvMax = 0

while(1):
    # Get current positions of all trackbars
    hMin = cv2.getTrackbarPos('HMin', 'image')
    sMin = cv2.getTrackbarPos('SMin', 'image')
    vMin = cv2.getTrackbarPos('VMin', 'image')
    hMax = cv2.getTrackbarPos('HMax', 'image')
    sMax = cv2.getTrackbarPos('SMax', 'image')
    vMax = cv2.getTrackbarPos('VMax', 'image')

    # Set minimum and maximum HSV values to display
    lower = np.array([hMin, sMin, vMin])
    upper = np.array([hMax, sMax, vMax])

    # Convert to HSV format and color threshold
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(hsv, lower, upper)
    result = cv2.bitwise_and(image, image, mask=mask)

    # Print if there is a change in HSV value
    if((phMin != hMin) | (psMin != sMin) | (pvMin != vMin) | (phMax != hMax) | (psMax != sMax) | (pvMax != vMax) ):
        print("(hMin = %d , sMin = %d, vMin = %d), (hMax = %d , sMax = %d, vMax = %d)" % (hMin , sMin , vMin, hMax, sMax , vMax))
        phMin = hMin
        psMin = sMin
        pvMin = vMin
        phMax = hMax
        psMax = sMax
        pvMax = vMax

    # Display result image
    cv2.imshow('image', result)
    if cv2.waitKey(10) & 0xFF == ord('q'):
        break

cv2.destroyAllWindows()
Run Code Online (Sandbox Code Playgroud)


Ann*_*Zen 5

这个概念

首先检测图像中的内核总数。考虑到深色背景,在将图像传递到方法并获得结果的长度之前,一个简单的二进制阈值和一个区域滤波器(以滤除噪声)就足以进行处理。cv2.findContours()

接下来,使用颜色范围来掩盖受感染的玉米粒。下面的程序使用 LAB 颜色掩码,下限范围为np.array([0, 0, 150]),上限范围为np.array([255, 255, 255])。这不会完全掩盖受感染的玉米粒,但使用区域过滤器将允许我们根据其在掩模中减少的面积将其过滤掉。

来自维基百科关于CIELAB 色彩空间的页面:

CIELAB 色彩空间,也称为 L a b* ,是国际照明委员会(缩写为 CIE)于 1976 年定义的色彩空间。

最后,我们将能够绘制好谷粒的轮廓,并计算受感染谷粒占总谷粒的百分比。

代码:

import cv2
import numpy as np

def large(cnt):
    return cv2.contourArea(cnt) > 5000

def get_contours(img):
    return cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]

def get_mask(img):
    img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
    lower = np.array([0, 0, 150])
    upper = np.array([255, 255, 255])
    return cv2.inRange(img_lab, lower, upper)

img = cv2.imread("corn.jpg")
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(img_gray, 100, 255, cv2.THRESH_BINARY)
total = len(list(filter(large, get_contours(thresh))))

mask = get_mask(img)
contours = list(filter(large, map(cv2.convexHull, get_contours(mask))))
cv2.drawContours(img, contours, -1, (0, 255, 0), 3)

infested = total - len(contours)
print(f"Total Kernels: {total}")
print(f"Infested Kernels: {infested}")
print(f"Infested Percentage: {round(infested / total * 100)}%")

cv2.imshow("Result", cv2.resize(img, (700, 700)))
cv2.waitKey(0)
Run Code Online (Sandbox Code Playgroud)

输出:

Total Kernels: 70
Infested Kernels: 27
Infested Percentage: 39%
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

说明

  1. 导入必要的库:
import cv2
import numpy as np
Run Code Online (Sandbox Code Playgroud)
  1. 定义一个函数 ,large()它将接受轮廓并True在轮廓面积大于时返回5000 (在处理不同尺寸的图像时相应地调整该值)
def large(cnt):
    return cv2.contourArea(cnt) > 5000
Run Code Online (Sandbox Code Playgroud)
  1. 定义一个函数 ,get_contours()它将接受二值图像并返回图像的轮廓:
def get_contours(img):
    return cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
Run Code Online (Sandbox Code Playgroud)
  1. 定义一个函数 ,它将接收图像,将其转换为 LAB 颜色空间,并返回具有下限范围和上限范围get_mask()的图像的掩码:0, 0, 150255, 255, 255
def get_mask(img):
    img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
    lower = np.array([0, 0, 150])
    upper = np.array([255, 255, 255])
    return cv2.inRange(img_lab, lower, upper)
Run Code Online (Sandbox Code Playgroud)
  1. 读入图像文件。要查找图像中的内核总数,请将图像转换为灰度图像,对其进行阈值处理以遮盖背景,然后使用get_contours()我们定义的函数查找轮廓。另外,使用我们定义为第一个参数的函数filter(),通过内置函数过滤掉任何噪音。large()这样,我们就可以使用内置len()函数来获取图像中的内核总数:
img = cv2.imread("corn.jpg")
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(img_gray, 100, 255, cv2.THRESH_BINARY)
total = len(list(filter(large, get_contours(thresh))))
Run Code Online (Sandbox Code Playgroud)

thresh图像结果corn.jpg

在此输入图像描述

  1. 使用我们定义的图像获取图像的掩模get_mask(),获取掩模的轮廓,并使用该函数滤除任何噪声large()。使用过滤后的轮廓,调用该cv2.drawContours()方法来突出显示好的内核(纯粹用于可视化)
mask = get_mask(img)
contours = list(filter(large, map(cv2.convexHull, get_contours(mask))))
cv2.drawContours(img, contours, -1, (0, 255, 0), 3)
Run Code Online (Sandbox Code Playgroud)

mask图像结果corn.jpg

在此输入图像描述

我再次运行该程序并进行了一些编辑,以便在蒙版上绘制轮廓,以便更好地理解过滤过程:

在此输入图像描述

  1. 最后,我们可以计算受感染的谷粒占图像中谷粒总数的百分比,print结果:
infested = total - len(contours)
print(f"Total Kernels: {total}")
print(f"Infested Kernels: {infested}")
print(f"Infested Percentage: {round(infested / total * 100)}%")
Run Code Online (Sandbox Code Playgroud)

工具

如果您碰巧有其他图像想要应用相同的算法,但其他图像的阴影相当不同,您可以使用OpenCV Trackbars来调整颜色遮罩的下限和上限(以及任何可能需要调整的其他值)。这是一个程序,允许您通过轨迹栏更改 LAB 范围,并实时显示结果图像:

import cv2
import numpy as np

def large(cnt):
    return cv2.contourArea(cnt) > 5000

def get_contours(img):
    return cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]

def get_mask(img, l_min, l_max, a_min, a_max, b_min, b_max):
    img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
    lower = np.array([l_min, a_min, b_min])
    upper = np.array([l_max, a_max, b_max])
    return cv2.inRange(img_lab, lower, upper)

def show(imgs, win="Image", scale=1):
    imgs = [cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) if len(img.shape) == 2 else img for img in imgs]
    img_concat = np.concatenate(imgs, 1)
    h, w = img_concat.shape[:2]
    cv2.imshow(win, cv2.resize(img_concat, (int(w * scale), int(h * scale))))

def put_text(img, text, y):
    cv2.putText(img, text, (20, y), cv2.FONT_HERSHEY_COMPLEX, 2, (255, 128, 0), 4)
    
d = {"L min": (0, 255),
     "L max": (255, 255),
     "A min": (0, 255),
     "A max": (255, 255),
     "B min": (150, 255),
     "B max": (255, 255)}

cv2.namedWindow("Track Bars")
for i in d:
    cv2.createTrackbar(i, "Track Bars", *d[i], id)

img = cv2.imread("corn.jpg")
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(img_gray, 100, 255, cv2.THRESH_BINARY)
total = len(list(filter(large, get_contours(thresh))))
while True:
    img_copy = img.copy()
    mask = get_mask(img, *(cv2.getTrackbarPos(i, "Track Bars") for i in d))
    contours = list(filter(large, map(cv2.convexHull, get_contours(mask))))
    cv2.drawContours(img_copy, contours, -1, (0, 255, 0), 3)
    infested = total - len(contours)
    put_text(img_copy, f"Total Kernels: {total}", 50)
    put_text(img_copy, f"Infested Kernels: {infested}", 120)
    put_text(img_copy, f"Infested Percentage: {round(infested / total * 100)}%", 190)
    show([img_copy, mask], "Results", 0.3)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break
Run Code Online (Sandbox Code Playgroud)

程序演示(速度x2)

在此输入图像描述