4. 基于尺寸的螺丝分拣

任务目标

构建一个基于尺寸的螺丝分拣视觉系统:通过工业相机获取螺丝图像,经图像处理测量螺丝的长度与直径,并根据预设规格阈值将螺丝分类到不同容器。本案例聚焦于视觉测量与分类算法的核心实现,不涉及物理分拣机构与PLC通信。

环境与依赖

  • Python 3.8+,OpenCV 4.5+,NumPy 1.20+
  • 输入图像:单颗螺丝水平放置于均匀背景(建议深色背景),图像分辨率不低于640×480
  • 标定关系:像素尺寸到毫米的换算比例(本案例提供示例比例,实际使用时需通过标定板获取)

实现方案

处理流程分为四步:图像预处理(灰度化、二值化、形态学去噪)→ 轮廓提取(获取螺丝外轮廓)→ 尺寸测量(最小外接矩形计算长度与直径)→ 规格分类(按阈值输出类别)。长度取最小外接矩形的长边,直径取短边(即螺丝杆部宽度)。

完整代码

import cv2
import numpy as np

# ========== 配置参数 ==========
PIXEL_TO_MM = 0.1          # 像素转毫米比例(需实际标定)
CLASS_THRESHOLDS = [        # (长度下限mm, 长度上限mm, 直径下限mm, 直径上限mm, 类别名)
    (10, 20, 3, 5,   "M3-短"),
    (10, 20, 5, 7,   "M5-短"),
    (20, 35, 3, 5,   "M3-长"),
    (20, 35, 5, 7,   "M5-长"),
]

def measure_screw(image_path):
    """测量螺丝长度与直径(单位mm),返回(长度, 直径)或None"""
    img = cv2.imread(image_path)
    if img is None:
        raise FileNotFoundError(f"无法读取图像: {image_path}")
    
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 深色背景+亮色螺丝 → 二值化(若背景为亮色,需反转)
    _, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
    
    # 形态学闭运算填充细小孔洞
    kernel = np.ones((5, 5), np.uint8)
    binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)
    
    contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if not contours:
        return None
    
    # 取最大轮廓(排除噪点)
    screw_contour = max(contours, key=cv2.contourArea)
    rect = cv2.minAreaRect(screw_contour)  # 返回((cx,cy),(w,h),angle)
    (w, h) = rect[1]
    
    # 长边为长度,短边为直径
    length_px = max(w, h)
    diameter_px = min(w, h)
    
    length_mm = length_px * PIXEL_TO_MM
    diameter_mm = diameter_px * PIXEL_TO_MM
    return length_mm, diameter_mm

def classify_screw(length_mm, diameter_mm):
    """按规格阈值分类,返回类别名称"""
    for lo_len, hi_len, lo_dia, hi_dia, name in CLASS_THRESHOLDS:
        if lo_len <= length_mm < hi_len and lo_dia <= diameter_mm < hi_dia:
            return name
    return "未分类"

def process_image(image_path):
    """单张图像处理入口:测量+分类,打印结果"""
    result = measure_screw(image_path)
    if result is None:
        print(f"{image_path}: 未检测到螺丝")
        return
    
    length_mm, diameter_mm = result
    category = classify_screw(length_mm, diameter_mm)
    print(f"图像: {image_path}")
    print(f"  长度: {length_mm:.2f} mm, 直径: {diameter_mm:.2f} mm")
    print(f"  分类: {category}")

# ========== 批量处理示例 ==========
if __name__ == "__main__":
    import sys
    if len(sys.argv) < 2:
        print("用法: python screw_sorter.py <图像路径1> [图像路径2] ...")
        sys.exit(1)
    for path in sys.argv[1:]:
        process_image(path)

运行方式

# 安装依赖
pip install opencv-python numpy

# 运行(可传入多张图像)
python screw_sorter.py screw1.jpg screw2.jpg screw3.jpg

验证方法

准备已知规格的螺丝图像进行测试,观察输出结果:

  • 测量准确性:输出长度/直径与真实卡尺测量值误差应在±0.5mm以内(取决于标定精度与图像分辨率)
  • 分类正确性:对照预设阈值表,确认输出类别与螺丝实际规格一致
  • 异常处理:无螺丝图像应输出"未检测到螺丝",不崩溃

预期输出示例(假设输入为M5长螺丝,实际长度30mm、直径5.5mm):

图像: screw_m5_long.jpg
  长度: 30.10 mm, 直径: 5.52 mm
  分类: M5-长

常见问题

  • 背景干扰:若背景与螺丝灰度接近,需调整二值化阈值或改用HSV色彩空间分离
  • 倾斜螺丝:minAreaRect自动适应旋转角度,无需额外校正
  • 多颗螺丝同框:当前实现取最大轮廓,仅适用于单颗螺丝场景;多颗需增加轮廓分离逻辑