如何使用 Python OpenCV 对苹果进行图像分割?
How to perform image segmentation of apples using Python OpenCV?
我有在碘溶液中浸泡过的苹果片的照片。目标是将苹果分割成各个感兴趣的区域,并评估每个区域的淀粉含量。这是一个学校项目,所以我的目标是测试不同的分割方法并客观地找到最佳解决方案,无论是单一技术还是多种技术的组合。
问题是到目前为止我只接近于一种方法。该方法使用 HoughCircles。我原本计划使用 Watershed 方法、形态学操作或简单的阈值处理。当我无法修改它们中的任何一个时,该计划失败了。
原始图像看起来与此类似,苹果的暗度不同
我试过使用 cv2.inRange 和 HSV 值删除背景托盘,但它不适用于较黑的苹果。
这是 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)
HoughCircles
# 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
分割苹果的另一种方法是在阈值化之前执行 Kmeans 颜色分割,然后使用轮廓过滤来隔离苹果对象:
应用 Kmeans 颜色分割。 我们加载图像,使用 imutils.resize
缩小尺寸,然后应用 Kmeans 颜色分割。根据簇的数量,我们可以将图像分割成所需数量的颜色。
得到二值图像。接下来我们转换为灰度,高斯模糊和Otsu的阈值。
使用轮廓近似过滤。我们过滤掉非圆形轮廓和小噪声。
形态学操作。我们执行变形接近填充相邻轮廓
使用轮廓区域作为过滤器绘制最小封闭圆。我们找到轮廓并绘制近似圆。为此,我们使用两个部分,一个是有良好阈值的部分,另一个是我们近似半径的部分。
Kmeans clusters=3
和二进制图像
的颜色量化
变形关闭和结果
使用 cv2.minEnclosingCircle
自动计算半径的 "good" 等高线以绿色突出显示,而近似等高线以蓝绿色突出显示。这些近似轮廓没有从阈值处理中很好地分割,所以我们平均 "good" 轮廓半径并用它来绘制圆。
代码
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()
我有在碘溶液中浸泡过的苹果片的照片。目标是将苹果分割成各个感兴趣的区域,并评估每个区域的淀粉含量。这是一个学校项目,所以我的目标是测试不同的分割方法并客观地找到最佳解决方案,无论是单一技术还是多种技术的组合。
问题是到目前为止我只接近于一种方法。该方法使用 HoughCircles。我原本计划使用 Watershed 方法、形态学操作或简单的阈值处理。当我无法修改它们中的任何一个时,该计划失败了。
原始图像看起来与此类似,苹果的暗度不同
我试过使用 cv2.inRange 和 HSV 值删除背景托盘,但它不适用于较黑的苹果。
这是 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)
HoughCircles
# 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
分割苹果的另一种方法是在阈值化之前执行 Kmeans 颜色分割,然后使用轮廓过滤来隔离苹果对象:
应用 Kmeans 颜色分割。 我们加载图像,使用
imutils.resize
缩小尺寸,然后应用 Kmeans 颜色分割。根据簇的数量,我们可以将图像分割成所需数量的颜色。得到二值图像。接下来我们转换为灰度,高斯模糊和Otsu的阈值。
使用轮廓近似过滤。我们过滤掉非圆形轮廓和小噪声。
形态学操作。我们执行变形接近填充相邻轮廓
使用轮廓区域作为过滤器绘制最小封闭圆。我们找到轮廓并绘制近似圆。为此,我们使用两个部分,一个是有良好阈值的部分,另一个是我们近似半径的部分。
Kmeans clusters=3
和二进制图像
变形关闭和结果
使用 cv2.minEnclosingCircle
自动计算半径的 "good" 等高线以绿色突出显示,而近似等高线以蓝绿色突出显示。这些近似轮廓没有从阈值处理中很好地分割,所以我们平均 "good" 轮廓半径并用它来绘制圆。
代码
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()