You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用OpenCV结合分水岭算法区分聚集木屑并测量尺寸

基于OpenCV分水岭算法分割聚集木屑并测量尺寸

当前使用OpenCV结合硬币参考物测量图像中木屑尺寸的方案,在木屑聚集时会被findContours识别为单个轮廓。以下是原实现代码,以及针对该问题的分水岭算法优化方案,实现聚集物体的精准分割并计算每个物体的相对尺寸。

原实现代码

# 读取图像并预处理(转为灰度图)
image = cv2.imread(img_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255,
    cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# 使用高斯核模糊去除不必要的边缘
# blur = cv2.GaussianBlur(gray, (9, 9), 0)
# blur = cv2.medianBlur(gray, 7)
blur = cv2.bilateralFilter(thresh,11, 125, 125)
# nbw = cv2.fastNlMeansDenoising(image, None, 10, 7, 21) 
# Canny边缘检测
edged = cv2.Canny(blur, 50, 100)
edged = cv2.dilate(edged, None, iterations=2)
edged = cv2.erode(edged, None, iterations=2)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (6,6))
closing = cv2.morphologyEx(edged, cv2.MORPH_CLOSE, kernel)
# show_images([blur, edged])

# 查找轮廓
cnts = cv2.findContours(closing.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

# 从左到右排序轮廓(最左侧为参考物)
(cnts, _) = contours.sort_contours(cnts)

# 过滤过小的轮廓,去除噪声
cnts = [x for x in cnts if cv2.contourArea(x) > 400]
# 参考物尺寸设置
# 此处使用2cm x 2cm的正方形作为参考(实际应为硬币,需调整对应尺寸)
ref_object = cnts[0]
box = cv2.minAreaRect(ref_object)
box = cv2.boxPoints(box)
box = np.array(box, dtype="int")
box = perspective.order_points(box)
(tl, tr, br, bl) = box
dist_in_pixel = euclidean(tl, tr)
dist_in_cm = 2
pixel_per_cm = dist_in_pixel/dist_in_cm

# 绘制轮廓并标注尺寸
measurement=[]
for cnt in cnts:
    box = cv2.minAreaRect(cnt)
    box = cv2.boxPoints(box)
    box = np.array(box, dtype="int")
    box = perspective.order_points(box)
    (tl, tr, br, bl) = box
    cv2.drawContours(image, [box.astype("int")], -1, (0, 0, 255), 2)
    mid_pt_horizontal = (tl[0] + int(abs(tr[0] - tl[0])/2), tl[1] + int(abs(tr[1] - tl[1])/2))
    mid_pt_verticle = (tr[0] + int(abs(tr[0] - br[0])/2), tr[1] + int(abs(tr[1] - br[1])/2))
    wid = euclidean(tl, tr)/pixel_per_cm
    ht = euclidean(tr, br)/pixel_per_cm
    measurement.append((wid,ht))
    cv2.putText(image, "{:.1f}cm".format(wid), (int(mid_pt_horizontal[0] - 15), int(mid_pt_horizontal[1] - 10)), 
        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 2)
    cv2.putText(image, "{:.1f}cm".format(ht), (int(mid_pt_verticle[0] + 10), int(mid_pt_verticle[1])), 
        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 2)
  
cv2_imshow(image)
cv2.waitKey(0)
cv2.destroyAllWindows()

分水岭算法优化实现

分水岭算法通过标记确定前景、确定背景和未知边界区域,实现粘连物体的精准分割。核心步骤及完整代码如下:

核心步骤

  1. 预处理调整:先对灰度图去噪,再进行二值化,用形态学开运算去除小噪声点
  2. 标记确定前景:通过距离变换获取物体中心区域,再阈值处理得到精准前景标记
  3. 标记确定背景:对二值图进行膨胀操作,确保包裹所有物体的区域为背景
  4. 计算未知区域:用背景区域减去前景区域,得到需要分割的边界区域
  5. 标记连通组件:用cv2.connectedComponents标记前景连通区域,调整标记规则适配分水岭算法
  6. 应用分水岭:调用cv2.watershed完成分割,提取每个物体的轮廓并计算尺寸

完整优化代码

import cv2
import numpy as np
import imutils
from scipy.spatial import distance as dist
from imutils import perspective
from imutils import contours

def euclidean(a, b):
    return dist.euclidean(a, b)

# 读取图像(替换为你的图像路径)
img_path = "your_image_path.jpg"
image = cv2.imread(img_path)
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

# 预处理:去噪 + 二值化(反向二值化让物体为白色)
blur = cv2.medianBlur(gray, 5)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# 形态学开运算去除小噪声
kernel = np.ones((3,3), np.uint8)
opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2)

# 确定背景区域:膨胀操作
sure_bg = cv2.dilate(opening, kernel, iterations=3)

# 确定前景区域:距离变换 + 阈值(0.2可根据图像调整)
dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
ret, sure_fg = cv2.threshold(dist_transform, 0.2*dist_transform.max(), 255, 0)
sure_fg = np.uint8(sure_fg)

# 计算未知边界区域
unknown = cv2.subtract(sure_bg, sure_fg)

# 标记连通组件
ret, markers = cv2.connectedComponents(sure_fg)
# 所有标记加1,背景设为1(避免0被分水岭视为未知区域)
markers = markers + 1
# 未知区域标记为0
markers[unknown == 255] = 0

# 应用分水岭算法,分割边界标记为-1
markers = cv2.watershed(image, markers)
image[markers == -1] = [255, 0, 0]  # 分割边界设为红色

# 提取所有分割后的物体轮廓(排除背景和边界)
cnts = []
for label in np.unique(markers):
    if label == 1 or label == -1:
        continue
    # 创建掩码提取当前物体
    mask = np.zeros(gray.shape, dtype="uint8")
    mask[markers == label] = 255
    # 查找当前物体的轮廓
    cnt = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnt = imutils.grab_contours(cnt)
    if len(cnt) > 0:
        cnts.append(cnt[0])

# 过滤过小的轮廓
cnts = [x for x in cnts if cv2.contourArea(x) > 400]

# 排序轮廓,确定参考物(假设最左侧为硬币)
(cnts, _) = contours.sort_contours(cnts)
ref_object = cnts[0]

# 计算像素与厘米的比例(替换为你的硬币实际直径,如1元硬币为2.5cm)
box = cv2.minAreaRect(ref_object)
box = cv2.boxPoints(box)
box = np.array(box, dtype="int")
box = perspective.order_points(box)
(tl, tr, br, bl) = box
dist_in_pixel = euclidean(tl, tr)
dist_in_cm = 2.5  # 硬币实际直径(单位:cm)
pixel_per_cm = dist_in_pixel / dist_in_cm

# 绘制轮廓并标注尺寸
measurement = []
for cnt in cnts:
    # 计算最小外接矩形
    box = cv2.minAreaRect(cnt)
    box = cv2.boxPoints(box)
    box = np.array(box, dtype="int")
    box = perspective.order_points(box)
    (tl, tr, br, bl) = box
    
    # 绘制轮廓
    cv2.drawContours(original, [box.astype("int")], -1, (0, 0, 255), 2)
    
    # 计算宽度和高度
    wid = euclidean(tl, tr) / pixel_per_cm
    ht = euclidean(tr, br) / pixel_per_cm
    measurement.append((wid, ht))
    
    # 标注尺寸
    mid_pt_horizontal = (tl[0] + int(abs(tr[0]-tl[0])/2), tl[1] + int(abs(tr[1]-tl[1])/2))
    mid_pt_vertical = (tr[0] + int(abs(tr[0]-br[0])/2), tr[1] + int(abs(tr[1]-br[1])/2))
    cv2.putText(original, "{:.1f}cm".format(wid), 
                (int(mid_pt_horizontal[0]-15), int(mid_pt_horizontal[1]-10)),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,0), 2)
    cv2.putText(original, "{:.1f}cm".format(ht), 
                (int(mid_pt_vertical[0]+10), int(mid_pt_vertical[1])),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,0), 2)

# 显示结果
cv2.imshow("Segmented and Measured", original)
cv2.waitKey(0)
cv2.destroyAllWindows()

关键调整说明

  • 参考物适配:代码默认硬币直径为2.5cm(1元硬币),请根据实际使用的硬币修改dist_in_cm值
  • 前景阈值:0.2*dist_transform.max()可根据图像调整,值越高前景标记越靠近物体中心,分割越保守;值越低则前景范围越大,分割更彻底
  • 噪声过滤:通过形态学开运算和轮廓面积过滤,去除图像中的小噪点干扰

内容的提问来源于stack exchange,提问作者Marten Meckelburg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 14:05:47