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

使用OpenCV检测红色背景上哑光白色串状小球的问题求助

串状小球检测优化需求

我并非程序员,而是一名物理学家,需要检测如下串状排列的小球:
串状小球示例图
此前有人协助编写了如下检测算法:

import cv2
import numpy as np

def find_ball_positions(file_name):
    centers = []
    rrs = []

    # Read image
    img = cv2.imread(file_name)

    # Convert to HSV and keep the hue channel
    hue = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)[:, :, 0]

    # beads are red but on the blue side, so very bright hues
    # so threshold hue at 165 (out of 180)
    thresh = cv2.threshold(hue, 100, 255, cv2.THRESH_BINARY)[1]

    # apply morphology to fill beads and remove excess spots
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (6, 6))
    morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)

    # get contours and enclosing circles
    contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    contours = contours[0] if len(contours) == 2 else contours[1]
    circles = img.copy()
    num_circles = len(contours)

    rrs = np.zeros(num_circles, dtype=int)

    ave = 0
    for ii, cntr in enumerate(contours):
        center, radius = cv2.minEnclosingCircle(cntr)
        cx = int(round(center[0]))
        cy = int(round(center[1]))
        rr = int(round(radius))

        centers.append((cx, cy))
        rrs[ii] = rr
        # draw enclosing circle over beads
        cv2.circle(circles, (cx, cy), rr, (0, 255, 0), 2)
        # cumulate radii for average
        ave = ave + radius

    # print average radius
    ave_radius = ave / num_circles
    print("average radius:", ave_radius)
    print("number of circles:", num_circles)

    # save results
    cv2.imwrite('beads_thresh.jpg', thresh)
    cv2.imwrite('beads_morph.jpg', morph)
    cv2.imwrite('beads_circles.jpg', circles)

    cv2.imshow('thresh', thresh)
    cv2.imshow('morph', morph)
    cv2.imshow('circles', circles)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    return {
        "centers": centers,
        "rrs": rrs,
        "image":img
    }

原有算法受小球反光影响检测效果较差,失效示例如下:
反光检测失效示例图
未选择软件层面优化反光问题,改为将所有小球喷涂为哑光白色,现在遇到新问题:受背景噪声干扰,无法在红色背景上正常检测白色小球,相关样本如下:
白珠红背景样本1
白珠红背景样本2


优化方案

针对哑光白珠+红色背景的场景,对原有算法做三处核心修改即可实现稳定检测:

  • 调整HSV阈值逻辑:放弃原有的单H通道阈值,改用三通道组合筛选白色区域。白色的HSV特征为饱和度低、亮度高,红色背景的饱和度远高于白色,阈值范围设置为lower_white = (0, 0, 200)、upper_white = (180, 30, 255)即可完美分离白珠和红色背景。
  • 增加圆形度过滤:通过4 * np.pi * cv2.contourArea(cnt) / (cv2.arcLength(cnt, True)**2)计算轮廓的圆形度,仅保留圆形度大于0.8的轮廓,过滤背景不规则噪声。
  • 增加半径范围过滤:统计所有候选圆的半径平均值,仅保留半径与平均值差值小于20%的结果,排除尺寸异常的误检目标。

优化后完整代码

import cv2
import numpy as np

def find_ball_positions(file_name):
    centers = []
    rrs = []
    img = cv2.imread(file_name)
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    
    # 白珠阈值范围,可根据实际拍摄亮度微调V通道的下限200
    lower_white = (0, 0, 200)
    upper_white = (180, 30, 255)
    thresh = cv2.inRange(hsv, lower_white, upper_white)

    # 形态学操作保留原有逻辑
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
    morph = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
    kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (6, 6))
    morph = cv2.morphologyEx(morph, cv2.MORPH_CLOSE, kernel)

    contours = cv2.findContours(morph, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    contours = contours[0] if len(contours) == 2 else contours[1]
    circles = img.copy()
    
    # 第一轮筛选:圆形度过滤
    candidate_circles = []
    for cnt in contours:
        area = cv2.contourArea(cnt)
        if area < 10: # 过滤极小杂点
            continue
        perimeter = cv2.arcLength(cnt, True)
        circularity = 4 * np.pi * area / (perimeter ** 2)
        if circularity > 0.8:
            center, radius = cv2.minEnclosingCircle(cnt)
            candidate_circles.append((center, radius))
    
    # 第二轮筛选:半径过滤
    if len(candidate_circles) == 0:
        print("未检测到小球")
        return {"centers": [], "rrs": [], "image": img}
    radii = [r for (c, r) in candidate_circles]
    avg_r = np.mean(radii)
    valid_circles = [(c, r) for (c, r) in candidate_circles if abs(r - avg_r) < avg_r * 0.2]
    
    # 输出结果
    num_circles = len(valid_circles)
    rrs = np.zeros(num_circles, dtype=int)
    for ii, (center, radius) in enumerate(valid_circles):
        cx = int(round(center[0]))
        cy = int(round(center[1]))
        rr = int(round(radius))
        centers.append((cx, cy))
        rrs[ii] = rr
        cv2.circle(circles, (cx, cy), rr, (0, 255, 0), 2)

    ave_radius = np.mean(rrs)
    print("average radius:", ave_radius)
    print("number of circles:", num_circles)

    cv2.imwrite('beads_thresh.jpg', thresh)
    cv2.imwrite('beads_morph.jpg', morph)
    cv2.imwrite('beads_circles.jpg', circles)

    cv2.imshow('thresh', thresh)
    cv2.imshow('morph', morph)
    cv2.imshow('circles', circles)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    return {
        "centers": centers,
        "rrs": rrs,
        "image":img
    }

如果实际拍摄亮度有差异,微调阈值里的V通道下限(默认200)即可适配。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 09:39:01