简介这份资源是一份技术文档型PDF内容为一篇关于基于机器视觉的硬币分拣系统设计的期刊论文适合自动化、机电一体化及机器视觉方向的本科生、研究生或工程技术人员参考。文档提出并验证了一套硬币自动分拣方案通过图像预处理、特征约束与模板匹配实现硬币识别与定位再借助坐标转换引导机械臂完成抓取分拣同时介绍了阈值处理、边缘检测、形状分析等关键视觉算法在系统中的应用逻辑。资源仅含1个PDF文件大小约1.04MB便于快速阅读与离线学习。文中还涉及系统功能模块设计、实验验证及结果分析对于理解视觉检测与机械臂协同、掌握硬币识别定位的实现思路具有较好的参考价值。目前已有266人学习下载适合用作课程设计、毕业设计或相关项目初期的方案调研参考。1. 机器视觉硬币分拣为什么先做模板匹配而不是直接上深度学习机器视觉硬币分拣系统这个题目听着像个毕业设计实际拆开是一条完整的软硬件链路图像采进来先预处理再靠颜色和直径筛掉一批最后用模板匹配确认图案然后把像素坐标换算给机械臂去抓。这套 PDF 资料走的是经典视觉路线不是深度学习因为要分的只有一元、五角、一角三种第五套人民币硬币加六张正反面模板这个样本量根本喂不饱训练集。论文给出的实验数据值得参考三种硬币各 30 枚混合后随机分布20 次分拣试验里准确率 100%漏检率压在 0.3% 以下这个水平对银行网点、商场零钱清分的需求是够用的。正在选课题方向的人、想搭小型自动分拣设备验证可行性的人都可以拿它当路线图后面坐标标定那套思路做机器人抓取项目时也能直接挪过去。2. 图像预处理与特征约束从磨砂黑背景到直径差 5 像素2.1 背景和光源怎么选黑色磨砂板就是第一道滤波器硬币分拣的第一道坎不是算法是图像干不干净。论文里特意选了磨砂材质的黑色背景板而不是亮面黑亚克力这个选择直接决定后面分割的难度。黑色背景的作用是把硬币边缘和背景的灰度对比拉大阈值分割时不需要费劲找自适应阈值磨砂材质的作用是打散光源的镜面反射避免硬币边缘出现一圈高光晕边。硬币本身有浮雕图案直射光打上去会产生局部过曝图案细节被削掉模板匹配阶段就会很被动。采集端的光源和背景选对了预处理算法可以简单好几个量级。这里有个代价磨砂黑背景板在光照下会产生椒盐噪声灰度化之后表现为随机散布的亮点和暗点而且硬币边缘越靠近背景噪声对轮廓提取的干扰越明显。所以论文的预处理流程是灰度化之后立刻接中值滤波正是冲着椒盐噪声去的。2.2 灰度化与中值滤波先把椒盐噪声按下去图像预处理的第一段代码OpenCV 里就是三行的事。注意这里用的是中值滤波而不是均值滤波中值滤波对椒盐类噪声的效果比高斯滤波稳得多。高斯滤波会把噪声点摊开到邻域里噪声是变淡了但边缘也跟着模糊中值滤波取邻域像素的中位数孤立的亮点或暗点会被直接替换掉边缘轮廓保留得更完整。import cv2 import numpy as np src cv2.imread(coin_scene.jpg, cv2.IMREAD_COLOR) gray cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) gray cv2.medianBlur(gray, ksize5)这段代码里cv2.cvtColor把三通道图像转成单通道灰度图后续的圆形检测、连通域分析都只依赖灰度信息不用考虑色彩噪声。cv2.medianBlur的 ksize 是滤波窗口大小官方要求必须是大于 1 的奇数。取 3 时去噪能力一般但硬币表面精细花纹保留得好取 5 时对密集椒盐噪声压制更彻底代价是硬币边缘轮廓会轻微圆角化。论文里背景是磨砂板实际采图时噪声点密度不低我一般先用 5 试一版如果硬币边缘被滤波磨掉太多再退回 3。这里有个判断技巧滤波后做一次 Canny 边缘检测看硬币外圆是不是闭合、有没有明显锯齿有锯齿就说明滤波窗口偏大把细节当噪声滤掉了。2.3 YCbCr 颜色分割与直径约束一张表三个判据预处理之后进入特征约束阶段。论文先用颜色把硬币分成两类五角是黄色一元和一角是白色。颜色空间选了 YCbCr 而不是 RGB原因是 YCbCr 把亮度分量 Y 单独拆出来Cr 和 Cb 两个色度分量对光照变化的敏感度比 RGB 低。固定光源下一元的白色钢芯和五角的黄色铜合金在 Cr 通道上有明显的数值分层用阈值就能切开。cv2.cvtColor直接支持COLOR_BGR2YCrCb转换分割代码不复杂ycbcr cv2.cvtColor(src, cv2.COLOR_BGR2YCrCb) cr ycbcr[:, :, 1] cb ycbcr[:, :, 2] # 黄色区域高 Cr、低 Cb对应五角硬币 _, yellow_bin cv2.threshold(cr, 135, 255, cv2.THRESH_BINARY) # 白色区域Cr 和 Cb 都在中间范围再用黄色掩码排除交界区 _, white_bin cv2.threshold(cr, 100, 255, cv2.THRESH_BINARY) white_bin cv2.bitwise_and(white_bin, cv2.bitwise_not(yellow_bin)) contours, _ cv2.findContours(white_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for c in contours: (cx, cy), radius cv2.minEnclosingCircle(c) diameter_px radius * 2.0 if diameter_px 50: # 过滤细小噪声区域 continue print(fcandidate at ({int(cx)}, {int(cy)}), diameter {diameter_px:.1f} px)这段代码里的 Cr 阈值 135 和 100 是现场标定出来的不要照抄。判断标准是把所有硬币摆进视野分别统计黄色币和白色币的 Cr 最小值、最大值阈值取两类数据的中值。如果五角硬币氧化发暗Cr 会掉到 120 以下135 这条线就切不开需要重新标定。拿到白色区域的连通域之后用cv2.minEnclosingCircle找出最小外接圆圆的直径就是硬币在像面上的像素直径。接下来是论文里一个容易忽略的硬指标一元直径 25mm一角直径 19.5mm两者相差 5.5mm但这 5.5mm 能不能在图像上体现出来取决于相机安装高度。论文给出的约束是一元和一角在像面上的像素直径至少相差 5 个像素因为镜头畸变、安装误差会让提取出的直径产生 1-2 个像素的随机偏差差 5 个像素才能保证两类硬币的直径分布区间不重叠。第五套人民币三种硬币的关键参数直接决定了特征约束的阈值设定面额直径 (mm)厚度 (mm)质量 (g)颜色一角19.51.673.5白色五角20.01.653.8黄色一元25.01.856.1白色五角和一元的直径只差 0.5mm单靠直径铁定分不开所以必须先颜色后直径顺序不能反过来。一元和一角颜色相近必须依赖直径。而直径差在像面上是否足够用论文的简化公式估算h Δx · d / f其中 d 是硬币真实直径mmf 是相机焦距mmΔx 是硬币在图像中的直径像素。严格说这个式子默认像元尺寸被约掉了工程上把像元尺寸补回来更稳# 计算指定工作距离下真实直径对应的像素直径 def pixel_diameter(real_d_mm, f_mm, h_mm, pixel_um2.2): real_d_mm: 硬币真实直径; f_mm: 镜头焦距; h_mm: 镜头到硬币距离; pixel_um: 传感器像元尺寸 pixel_mm pixel_um / 1000.0 # 像元尺寸换算成 mm image_d_mm f_mm * real_d_mm / h_mm # 薄透镜成像的像高 return image_d_mm / pixel_mm # 假设焦距 8mm工作距离 200mm像元 2.2um 时的换算结果 d20 pixel_diameter(20.0, 8.0, 200.0) d25 pixel_diameter(25.0, 8.0, 200.0) print(f20mm 硬币: {d20:.1f} px, 25mm 硬币: {d25:.1f} px, 差值: {d25 - d20:.1f} px)这个函数的作用是让你在装相机之前先在纸面上算一遍工作距离取多少一元和一角的像素直径差能超过 5 像素。如果算出来差不够就要把相机降低或者换更长焦距的镜头。现场装好后还要用真实硬币实测一次像素直径以实测值为准。3. 模板匹配七个 Hu 不变矩只用前四个已经够了3.1 为什么选 Hu 矩硬币是随便扔在桌面上的颜色和直径约束能解决“这像不像硬币”的问题解决不了“这是不是真币”的问题。混进来一枚尺寸和一元硬币差不多的游戏币直径和颜色都对得上靠特征约束就失灵了。论文在特征约束后加了一道模板匹配比对的不是整幅图的灰度而是硬币表面的图案特征。这里不能用普通的模板滑动匹配硬币在视野里是任意角度摆放的同一面硬币可能转 30 度也可能转 170 度逐角度遍历模板的计算量在分拣场景下扛不住。Hu 矩的定位就是处理这种旋转不确定性的。对图像函数 f(x, y)它的 (pq) 阶矩 m_pq 是对全图灰度分布的加权积分中心矩 μ_pq 把坐标原点挪到质心归一化中心矩 η_pq 再除以 μ_00 的幂次把尺度因素消掉。Hu 用二阶和三阶归一化中心矩组合出 7 个不变矩这 7 个量对平移、旋转、缩放都不敏感。也就是说一枚硬币转了多少度、在视野里放大缩小了多少算出来的 Hu 矩基本不变用一组模板就能覆盖所有摆放角度。这个特性正是模板滑动匹配给不了的。硬币的浮雕图案经过旋转后灰度分布完全变了像素级比对需要每个角度都做一次匹配7 个不变矩却是一次计算、终身受用。代价是 Hu 矩只描述灰度分布的整体统计形状对纹理细节不敏感所以它做第一层真伪判据够用但不能当唯一的判据。3.2 七个矩只用前四个论文表 2 背后的工程判断模板匹配子模块设计了六张模板三种面值的硬币正反面各一张。模板区域从被测硬币图像里手工抠出来单独计算 Hu 矩存成特征库。论文表 2 列出了六张模板的 7 个不变矩数值仔细看就能发现问题φ5 到 φ7 的值基本落在 10⁻³ 到 10⁻⁴ 量级而 φ1 在 1.5 到 3.1 之间前四个矩的区分度远高于后三个。φ5 到 φ7 数值小不是因为信息不重要是因为它们由高阶归一化中心矩组合而来高阶矩对噪声的放大效应明显在硬币这类低纹理目标的识别里实际区分效果并不好。论文的取舍是匹配时只用 φ1-φ4 组成四维特征向量做主判据φ5-φ7 保留下来做单值范围限制。也就是说前四个矩决定“这是哪一类”后三个矩只负责把关如果待检区域和模板的 φ5-φ7 超出经验范围直接判为异常。这么做有两个直接收益一是特征向量从 7 维降到 4 维模板比对的欧氏距离计算量少了一半二是把噪声敏感的高阶矩降级为辅助限制条件系统对图像噪声的容忍度反而高了。只看前四维六张模板之间的区分度已经足够模板φ1φ2φ3φ4五角正面2.42470.00020.01740.0202五角反面3.14010.00360.00260.0182一元正面1.47190.00200.00110.0239一元反面1.53790.00800.01620.0291一角正面1.68730.07520.02220.0075一角反面2.89440.02600.19080.4309一角正面的 φ2 是 0.0752和一元正面的 0.0020 差了一个数量级一角反面的 φ4 达到 0.4309其他模板都在 0.03 以下。这两个维度把一角明显区分开了。五角虽然和一元在某些维度接近但 φ1 的差异足够撑起第一层分类。模板库建好之后匹配阈值不是拍脑袋定的而是拿这六张模板两两计算距离矩阵把“同类最近距离”和“异类最近距离”的间隔找出来阈值落在间隔中间。3.3 特征向量匹配代码与阈值设定模板保存成字典键是面值和正反面值是对应区域的 Hu 矩特征向量。计算过程注意两点第一输入必须是单目标的二值掩码先把硬币区域从背景里分割出来再算矩不要让背景参与统计第二Hu 矩原始值量级差异太大φ1 在 1 到 3 附近φ3 已经掉到 0.017直接用原始值算欧氏距离会让 φ1 主导整个结果必须先取对数压缩量级。def extract_hu_features(mask): 从二值掩码提取 7 个 Hu 不变矩做对数压缩后返回 m cv2.moments(mask) if m[m00] 0: return None hu_raw cv2.HuMoments(m).flatten() # 对数压缩正负号保留数值压到同一量级 return np.sign(hu_raw) * np.log10(np.abs(hu_raw) 1e-7) def match_coin(test_vec, templates, dist_thresh0.15, extra_thresh0.02): test_vec: 待检区域 Hu 特征; templates: 模板字典; dist_thresh: 前四维欧氏距离阈值; extra_thresh: φ5-φ7 单值范围限制 best_name, best_dist None, np.inf for name, vec in templates.items(): # 主判据只用前 4 维 d np.linalg.norm(test_vec[:4] - vec[:4]) # 附加判据φ5-φ7 任一维度偏差过大则跳过 if np.max(np.abs(test_vec[4:] - vec[4:])) extra_thresh: continue if d best_dist: best_dist, best_name d, name return best_name, best_distextract_hu_features里加1e-7是为了防止 φ 值恰好取到 0 时 log10 报错这是处理数值的小习惯。match_coin里的dist_thresh需要在实际模板库里标定做法是拿每张模板去匹配其他所有模板记录最小异类距离和最大同类距离阈值夹在两者之间。如果异类距离和同类距离出现重叠说明当前采图条件下 Hu 矩区分度不够优先调整光源角度而不是硬调阈值。extra_thresh取 0.02 到 0.05 之间这个值对应 φ5-φ7 的噪声波动范围设太严会把正常模板匹配误杀。匹配完成后返回币种名称和距离值距离值可以留作置信度输出。分拣系统里我一般会加一条兜底逻辑最佳匹配距离和次佳匹配距离的比值超过 1.5 才接受结果如果两个都接近说明特征约束阶段选出的是可疑目标宁可丢给人工复核也不让机械臂乱抓这是从漏检率和误抓率之间找平衡点的常规做法。4. 坐标转换与机械臂逆运动学从像素坐标到三个关节角4.1 三个坐标系先捋清楚视觉识别输出的是一组像素坐标 (u, v)机械臂要抓的是物理空间里的一个点中间隔着三个坐标系图像坐标系 OUV 是像素平面上的二维坐标相机坐标系 O1XYZ 以相机光心为原点机械臂坐标系 O2XYZ 以机械臂基座为原点。硬币在桌面上摊开相对相机和机械臂的高度方向Z 轴是固定的所以三个坐标系的关系可以压到二维平面上处理少一个维度标定工作量小很多。像素坐标到世界坐标的换算用的是相机内参数加工作距离。fx 和 fy 是横向和纵向的有效焦距单位是像素cx、cy 是光心在图像上的像素坐标h 是相机到硬币平面的垂直距离。像素坐标乘以内参数矩阵的逆再乘上 h就得到相机坐标系下的物理坐标。公式写成齐次坐标形式就是论文里的式 (12)实际代码里用矩阵乘法一次完成。相机坐标系和机械臂坐标系之间是刚体变换只有旋转关系 R 和平移量 T没有缩放。R 是一个 2x2 旋转矩阵T 是二维平移向量。要确定这两个量理论上取两个公共点就够了一个点在相机坐标系下的坐标和它在机械臂坐标系下的坐标都已知列两个方程解四个未知数。4.2 用公共点标定 R 和 T两个点起步四个点才稳标定的操作方式很直接在机械臂末端装一支尖针让针尖移动到视野内几个已知位置记录针尖在图像里的像素坐标和机械臂控制面板上显示的坐标一一配对。拿到配对点之后解下面的线性方程组。注意这里利用了旋转矩阵的结构约束不是解一个通用的仿射矩阵所以两个点理论上就能解出唯一结果。import numpy as np def solve_rigid_transform(pairs): pairs: [(cam_x, cam_y, arm_x, arm_y), ...]至少 2 组点 返回旋转矩阵 R 和平移向量 T满足 arm R cam T A, b [], [] for (cx, cy, ax, ay) in pairs: # 旋转矩阵结构: [a, -b; b, a]两个独立参数 A.append([cx, -cy, 1, 0]) A.append([cy, cx, 0, 1]) b.append(ax) b.append(ay) A np.array(A, dtypenp.float64) b np.array(b, dtypenp.float64) sol, *_ np.linalg.lstsq(A, b, rcondNone) a, b_rot, tx, ty sol R np.array([[a, -b_rot], [b_rot, a]]) T np.array([tx, ty]) return R, T # 示例3 组标定点 pairs [ (100.0, 120.0, 210.0, 180.0), (300.0, 150.0, 190.0, 320.0), (240.0, 360.0, 420.0, 140.0), ] R, T solve_rigid_transform(pairs) print(R , R) print(T , T)代码里A矩阵第 1、3 行对应 X 方向方程第 2、4 行对应 Y 方向方程。np.linalg.lstsq在点数多于 2 时给出最小二乘解能平均掉一部分针尖对位的人为误差。这里有一个经验值标定点至少要取 4 个而且分布要尽量铺满机械臂的工作范围不要挤在视野中央。两个公共点理论可解但实际对位误差很容易把旋转角算偏点越多、分布越开解出来的 R 和 T 对工作范围内任意位置的转换误差越均匀。标定完成后的验证方法是让尖针依次走到 5 个未参与解算的测试点比较机械臂实际坐标和视觉换算坐标的偏差平均偏差在 2mm 以内可以接受超过就排查是针尖对位不准还是相机安装有倾斜角。刚才的代码假设相机平面和机械臂平面严格平行只有旋转和平移。现场安装很难做到完全平行相机光轴稍微歪一点视野边缘的转换误差就会明显变大。遇到这种情况把模型从刚体变换换成仿射变换给 x 和 y 方向各加一个缩放系数能把这部分误差补偿掉大部分。OpenCV 的cv2.estimateAffine2D可以直接做这件事输入两组点集返回 2x3 仿射矩阵。4.3 逆运动学从目标坐标到三个关节角硬币在机械臂坐标系里的位置确定之后剩下的是逆运动学求解。论文把四自由度机械臂抽象成一个旋转机构加一个连杆机构第一个自由度决定整个臂在平面内的朝向第二、第三个自由度决定末端执行器在平面内能伸多远。θ1 的计算最简单目标点相对机械臂基座的方位角就是 atan2(Y, X)注意用 atan2 而不是 atan因为 atan 无法区分目标点落在哪个象限分拣时硬币可能出现在机械臂的左边也可能出现在右边象限判断错了机械臂会朝反方向转。目标点到机械臂基座的距离 d √(X² Y²)两个连杆长度分别为 a 和 b于是问题变成一个已知三边a、b、d的三角形求解。θ2 是第一个连杆和基座到目标点连线之间的夹角由余弦定理直接解出θ3 是第二个连杆的补充角由两个反正弦项叠加得到。这三个角求得后下发给运动控制器机械臂就能按规划路径去抓取。import math def inverse_kinematics(target, link_a, link_b): target: (x, y) 机械臂坐标系下的目标点; link_a: 第一段臂长; link_b: 第二段臂长 返回 (theta1, theta2, theta3)单位: 度 x, y target theta1 math.degrees(math.atan2(y, x)) d math.hypot(x, y) # 可达性检查目标距离不能超过两臂之和也不能小于两臂之差 if d link_a link_b or d abs(link_a - link_b): raise ValueError(ftarget {target} is unreachable) # 余弦定理求 θ2cos 值夹到 [-1, 1] 防数值越界 cos_t2 (d*d link_a*link_a - link_b*link_b) / (2.0 * d * link_a) cos_t2 max(-1.0, min(1.0, cos_t2)) theta2 math.degrees(math.acos(cos_t2)) # θ3 由两个角度叠加和 θ2 共用同一组边长数据 sin_1 (d*d link_a*link_a - link_b*link_b) / (2.0 * d * link_a) sin_2 (d*d link_b*link_b - link_a*link_a) / (2.0 * d * link_b) sin_1 max(-1.0, min(1.0, sin_1)) sin_2 max(-1.0, min(1.0, sin_2)) theta3 math.degrees(math.asin(sin_1) math.asin(sin_2)) return theta1, theta2, theta3 print(inverse_kinematics((200.0, 150.0), 120.0, 100.0))inverse_kinematics里有一个必须有的环节可达性检查。如果目标点距离超过两臂长度之和余弦定理里的 cos 值会大于 1math.acos直接抛 ValueError反过来距离小于两臂长度之差机械臂就算折到底也够不着。分拣系统里这一检查不能省因为视觉识别有可能把背景噪声误当成硬币给出一个根本不可能到达的坐标机械臂如果强行运动轻则报警停机重则撞到工作台边缘。角度正负号的问题也要留意θ2、θ3 的符号取决于机械臂的关节零点定义和连杆安装方向论文给的公式是理想几何模型接到真机之后必须用点动模式分别验证三个关节的正方向再把符号修正到控制器参数里。5. 硬币分拣避坑漏检率 0.3% 那几个百分点漏在哪5.1 直径判据失效的两种现场现象一一元硬币和一角硬币在分类结果里来回跳变。同一枚一元硬币有时候判成一元有时候判成一角而且不是边缘位置是放在视野中间也会跳。原因通常出在相机高度设错了。论文要求一元和一角的像素直径至少差 5 像素实际装夹时如果相机支架偏高两个直径的像素差被压缩到 2-3 像素加上镜头畸变带来的 1-2 像素随机误差两类的直径分布区间就重叠了。解决方法是重新按第一节的公式计算工作距离再用真实硬币实测像素直径把一元和一角的直径阈值设到两者的中值上而不是用理论直径直接算。现象二硬币和背景粘连外接圆直径整体偏大。磨砂黑背景板用久了会积灰灰度化之后灰尘在图像上形成大块灰斑硬币边缘和背景之间的灰度梯度被抹平连通域分析把背景灰斑并进硬币区域minEnclosingCircle给出的直径自然偏大。原因不是算法问题是成像环境劣化。解决方法是定期清洁背景板更彻底的做法是在预处理里加一步顶帽变换用形态学开运算把背景中的小面积亮斑先压下去再做连通域分析。顶帽变换的核大小取硬币直径的一半左右能保留硬币主体区域同时滤掉灰尘颗粒。5.2 模板匹配阈值的两个翻车点现象三五角硬币被识别成一元颜色约束失效。五角硬币是黄铜材质表面反光强直射光源下高光区域在 YCbCr 空间里的 Cr 值被抬高黄色区域整体偏移进入白色区域的阈值范围。实际表现是同一枚五角在不同光照角度下颜色分类不稳定有时判黄有时判白。原因要拆成两层一是光源方式不对直射光产生了镜面反射二是阈值区间设得太宽把黄色和白色的分界留到了灰色地带。解决方法是先给光源加一层漫射板让光线软下来再用 10 枚真币分别统计五角和一元、一角的 Cr、Cb 分布区间黄色掩码阈值卡在两类分布的最小间隙处。高光区域如果实在压不掉干脆在做颜色分类前把高光 mask 先挖掉对高光区域单独走直径和模板匹配流程。现象四真币被当成假币拒收漏检率不降反升。论文里的漏检率 0.3% 就是这类情况待检硬币图像有噪声或者计算误差导致它和模板的匹配距离超过了阈值被判为假币。这类误判最容易出现在硬币图案磨损、表面有污渍的场景污渍不改变直径和颜色但改变灰度分布统计Hu 矩跟着偏移。解决思路是别把单帧匹配结果当唯一判据。分拣系统里硬币在传送带上是静止的相机可以连续拍 3 帧对三次的匹配距离取中位数再和阈值比较单帧噪声带来的抖动会被滤掉。另外一个常被忽略的细节是 φ5-φ7 的范围限制不能设太严脏污硬币的 Hu 矩低阶部分稳定高阶部分波动大范围限制放宽到 0.05 对真币安全同时又能卡住外观差异明显的仿币。机械臂抓偏和上面四个问题性质不同但更常见。抓偏的现象是视觉定位坐标没变机械臂落点偏差 2-3mm。原因几乎都是 R 和 T 标定只用了一组数据或者标定点集中在视野中心。两个公共点解出来的刚体变换对中心区域拟合得好到视野边缘误差就放大。解决方法是重做标定取 6 个以上分布均匀的点用最小二乘解或cv2.estimateAffine2D验证环节加 5 个未参与标定的测试点平均偏差压到 2mm 再交给机械臂跑自动流程。标定做完以后别急着走把工作台角落放一枚硬币让系统走完一整个抓取流程这一枪试过再批量跑能帮你拦住九成以上的标定事故。6. 模板库扩展新币种先算交叉区分度再定匹配阈值系统上线时只有三种硬币但流通币种会更新防伪特征也可能调整模板库迟早要加新面值。给新币加模板不是采集几张图、算一组 Hu 矩、加进字典就完事了最稳妥的流程是先在模板库内部跑一遍交叉距离矩阵。def cross_distance_matrix(templates): templates: {name: hu_vector}输出两两之间的前四维欧氏距离 names list(templates.keys()) for i in range(len(names)): for j in range(i 1, len(names)): d np.linalg.norm(templates[names[i]][:4] - templates[names[j]][:4]) print(f{names[i]} vs {names[j]}: {d:.3f}) # 模板库示例加一枚新纪念币 templates { one_yuan_front: np.array([1.47, 0.002, 0.001, 0.024]), one_yuan_back: np.array([1.54, 0.008, 0.016, 0.029]), new_coin_front: np.array([1.61, 0.018, 0.009, 0.017]), } cross_distance_matrix(templates)这段代码输出的距离矩阵会直接告诉你一个问题如果新币和现有币的最小异类距离只有 0.02而现有同类模板之间的最大距离却有 0.05说明两类币在 Hu 矩前四维上已经分不开了。这时候去调匹配阈值是没用的阈值怎么设都会同时误伤两类正确做法是回去调整光源角度增加图案纹理的对比度让 Hu 矩的区分度拉开。如果光源已经调到最优还是分不开说明新币图案和旧币在灰度分布统计上确实太接近就要考虑在特征向量里加入 φ5-φ7 的加权项或者引入第二个特征维度比如边缘方向直方图。阈值标定完成后按论文的实验方法做一轮批次验证每种硬币各 30 枚再加 5 枚仿币混入随机散布在视野内连续跑 20 次分拣统计准确率和漏检率。这个批次数不是拍脑袋定的20 次能覆盖不同摆放角度和阴影分布的大部分组合数据量太少说明不了问题。验证过程中如果某一种币连续出现漏检别急着改阈值先看漏检币的图像采集质量大概率是那一枚币有污渍或者光照刚好落在阴影区属于图像质量问题而不是算法问题。我吃过一次亏新增一枚纪念币的时候没跑交叉距离矩阵直接按旧阈值上线结果它和一元正面的距离只有 0.04把三成真币误判成仿币整个分拣流程卡了半个多小时。从那以后我每次扩展模板都强制先跑一遍交叉距离矩阵间隔不够就宁可不上线先回去调光源。希望帮到你。本文还有配套的精品资源点击获取