如何使用Python OpenCV对苹果进行图像分割?

6
我有一些苹果片的照片,这些苹果片已经被泡在碘溶液中了。我的目标是将苹果分成单独的兴趣区域,并评估每个区域的淀粉水平。这是一个学校项目,所以我的目标是测试不同的分割方法,并客观地找到最佳解决方案,无论是单一技术还是多种技术的组合。
问题在于,到目前为止,我只接近完成了一种方法。该方法是使用HoughCircles。我最初计划使用Watershed方法、形态学操作或简单的阈值处理。当我无法修改任何一个使其工作时,这个计划失败了。
原始图像看起来类似于这样,苹果颜色的深浅不同
我尝试使用HSV值的cv2.inRange消除背景托盘,但它与较暗的苹果配合效果不佳。
下面是HoughCircles在应用了灰度和中值模糊后对原始图像产生的效果,同时还尝试了遮罩托盘。
如果可以提供代码,那将非常感谢。
编辑1:添加一些代码并澄清问题
谢谢你们的回答。我真正的问题是这种情况是否适合其他分割方法?我想尝试几种不同的方法,并比较大量照片的结果。我接下来要尝试的是使用k-means分割。此外,我将添加一些代码,展示我到目前为止所尝试的内容。
颜色过滤采用HSV。
import cv2
import numpy as np

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

# Set minimum and max HSV values to display
lower = np.array([0, 0, 0])
upper = np.array([105, 200, 255])

# Create HSV Image and threshold into a range.
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, lower, upper)
maskedImage = cv2.bitwise_and(image, image, mask=mask)

# Show Image
cv2.imshow('HSV Mask', image)
cv2.waitKey(0)

霍夫圆变换(Hough Circles)
# import the necessary packages
import numpy as np
import argparse
import cv2
import os

directory = os.fsencode('Photos\\Sample N 100')

for file in os.listdir(directory):

    filename = os.fsdecode(file)

    if filename.endswith('.jpg'):
        # Load the image
        image = cv2.imread('Photos\\Sample N 100\\' + filename)

        # Calculate scale
        scale_factor = 800 / image.shape[0]
        width = int(image.shape[1] * scale_factor)
        height = 800
        dimension = (width, height)
        min_radius = int((width / 10) * .8)
        max_radius = int((width / 10) * 1.2)

        # Resize image
        image = cv2.resize(image, dimension, interpolation=cv2.INTER_AREA)

        # Copy Image 
        output = image.copy()

        # Grayscale Image
        gray = cv2.medianBlur(cv2.cvtColor(image, cv2.COLOR_BGR2GRAY), 5)

        # Detect circles in image
        circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, min_radius * 2, 4, 60, 20,  min_radius, max_radius)

        # ensure at least some circles were found
        if circles is not None:
            # convert the (x, y) coordinates and radius of the circles to integers
            circles = np.round(circles[0, :]).astype("int")
            # loop over the (x, y) coordinates and radius of the circles
            for (x, y, r) in circles:
                # draw the circle in the output image, then draw a rectangle
                # corresponding to the center of the circle
                cv2.circle(output, (x, y), r, (0, 255, 0), 4)
                cv2.rectangle(output, (x - 5, y - 5), (x + 5, y + 5), (0, 128, 255), -1)
                cv2.putText(output, '(' + str(x) + ',' + str(y) + ',' + str(r) + ')', (x, y),
                        cv2.FONT_HERSHEY_COMPLEX_SMALL, 1, 255)


            # show the output image
            cv2.imshow("output", np.hstack([image, output, maskedImage]))
            cv2.waitKey(0)
        continue
    else:
        continue

你对HoughCircles的表现满意吗?如果是,那么下一步是什么?分析每个感兴趣区域吗? - Warpstar22
也许可以使用 kmeans 处理来分割仅有少量颜色的图像,然后再进行阈值处理。请按照该论坛的建议展示您的代码。请阅读帮助文档,尤其是有关展示最小化、可验证和可重现的代码的部分。 - fmw42
我仍然认为我的HoughCircles变换还不够完善,但它比我想象中要接近得多。是的,我的下一步是分析每个感兴趣区域的相对暗度。不过,我还没有研究或测试任何相关代码。感谢@Warpstar22的回复。 - Caleb Rath
这是我下一步尝试的计划。唯一的问题是,我需要知道苹果的数量,对吗?@fmw42 - Caleb Rath
不,kmeans只需要你告诉它你想保留多少种颜色。请参见@nathancy的解决方案。 - fmw42
是的,我意识到了我的误解。哎呀!谢谢你的建议! - Caleb Rath
1个回答

6
一种分割苹果的替代方法是在阈值处理之前执行Kmeans颜色分割,然后使用轮廓过滤来隔离苹果对象:
  1. 应用Kmeans颜色分割。 我们加载图像,使用imutils.resize调整大小,然后应用Kmeans颜色分割。 根据聚类数,我们可以将图像分段为所需数量的颜色。

  2. 获取二进制图像。 接下来我们将其转换为灰度图像、高斯模糊和大津阈值。

  3. 使用轮廓逼近进行过滤。 我们过滤掉非圆形轮廓和小噪声。

  4. 形态学运算。 我们执行形态学闭合以填充相邻轮廓

  5. 使用轮廓面积作为过滤器绘制最小外接圆。 我们找到轮廓并绘制逼近的圆。为此,我们使用两个部分,一个具有良好阈值,另一个是我们近似半径的部分。


Kmeans颜色量化,clusters=3和二进制图像

形态学闭合和结果

使用cv2.minEnclosingCircle自动计算半径的“好”轮廓以绿色突出显示,而近似的轮廓以青色突出显示。这些逼近的轮廓在阈值处理过程中分割不良,因此我们平均“好”轮廓的半径并使用该半径来绘制圆。

代码

import cv2
import numpy as np
import imutils

# Kmeans color segmentation
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, resize smaller, perform kmeans, grayscale
# Apply Gaussian blur, Otsu's threshold
image = cv2.imread('1.jpg')
image = imutils.resize(image, width=600)
kmeans = kmeans_color_quantization(image, clusters=3)
gray = cv2.cvtColor(kmeans, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (9,9), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]

# Filter out contours not circle
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.04 * peri, True)
    if len(approx) < 4:
        cv2.drawContours(thresh, [c], -1, 0, -1)

# Morph close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=3)

# Find contours and draw minimum enclosing circles 
# using contour area as filter
approximated_radius = 63
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    area = cv2.contourArea(c)
    x,y,w,h = cv2.boundingRect(c)
    # Large circles
    if area > 6000 and area < 15000:
        ((x, y), r) = cv2.minEnclosingCircle(c)
        cv2.circle(image, (int(x), int(y)), int(r), (36, 255, 12), 2)
    # Small circles
    elif area > 1000 and area < 6000:
        ((x, y), r) = cv2.minEnclosingCircle(c)
        cv2.circle(image, (int(x), int(y)), approximated_radius, (200, 255, 12), 2)

cv2.imshow('kmeans', kmeans)
cv2.imshow('thresh', thresh)
cv2.imshow('close', close)
cv2.imshow('image', image)
cv2.waitKey()     

太好了!我会考虑在所有情况下使用它。我唯一可能遇到的问题是我不知道每个托盘有多少苹果。不过我可以让用户输入这些信息。思路清晰,解释详细,感激不尽! - Caleb Rath
你可以通过计算绘制的圆圈数量来找到图像中苹果的数量,无需用户输入数字。 - nathancy
再次阅读后,这很有道理。我原以为Kmeans需要所需的分段对象数量,而不是所需的颜色分段数量。非常好的答案,这对我帮助很大! - Caleb Rath
Yup Kmeans 用于将图像分割成 X 种颜色。因此,如果您设置 clusters=5,它将找到最显著的 5 种颜色并仅显示这些颜色。很高兴能帮助到您,祝好运! - nathancy

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