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自动适应旋转角度,无需额外校正
- 多颗螺丝同框:当前实现取最大轮廓,仅适用于单颗螺丝场景;多颗需增加轮廓分离逻辑