需求:
产品很小(4*4mm),需要人工一盘盘检查是否有缺失少放之类的,一线人员反馈看一两盘没问题,看多了之后眼睛会花,想要改善这种作业方式。
一盘有490个产品,考虑到产品小,特征不好提取,于是购买了海康的2000万像素CCD相机和16mm焦距镜头,经过实测,发现放大后,产品还是比较模糊,至少字体看不清,暂时先不管了,训练后看看结果怎么样。
过程
一开始收集了不同角度不同光线的图片进行训练,大概20多张,100多个标注点,训练后识别效果不理想,以为是训练数据不够,又不断增加,增加到60多张图,标注点400多个,还是不行,最后又加到100张图,标注点1000多个,每次出来的结果都是和下面那张图一样,部分能识别,部分无法识别。

没办法,只好求助deepseek,得到的结论有几个可能:
1、可能是物体太小,解析度不行,所以训练在多图也无意义
2、训练尺寸太小,需要加大,比如1280、1920、以及更大的尺寸
3、训练图不够,训练特征收集的过少
4、模型不行,更换识别能力更好的模型
根据这几点又一一验证了下:
① 首先物体太小,那我就不识别整盘,把镜头拉近,这样解析度高很多,可以看清产品上的文字了,再次去识别的时候,发现有效果,但是还会缺失一些标注,又倒腾了大半天,还是没法做到识别全部。
② 训练图太小,训练时我分别使用了640、1280、1920、以及更高尺寸的2000-3000多的尺寸去训练,结果还是无法做到100%全部识别
③ 训练图太少,上面提到过,就是增加更多的图片去训练,有高清的、模糊的、倾斜的、反光的,反正各个角度都放了,还是一样不行
④ 模型不行,一开始 是使用yolov8系列的模型,不断从yolov8n到最吃资源的yolov8x ,再从yolov11n到yolov11x系列,识别效果都不行。
有点绷不住了,研究了好几天都不行,没达到预期效果。
最终的办法
还得是AI,最终采用的办法是分割的方式去实现,我就在想着,之前把镜头拉近了产品,解析度更高,为什么也没法识别全部呢?等会就有答案了
根据AI的方法,就是先切分再合并输出:
一开始试了下,半边图识别的效果也一般般,没法全部识别。
为了验证AI的方法,我又重新按不同参数进行训练各种模型,直接上训练代码,主体不变,主要是imgsz会变,每次训练都会从640、1280、1920进行训练,更高的没试了,训练太费时间。
batch记得改动,尺寸越大,资源消耗越大,容易出现资源不足停止训练,所以尺寸越大,batch要越小,训练的时候可以看到内存占用的逐步调整。
######直接在python中训练的
# 导入ultralytics库中的YOLO类(用于目标检测模型)
# 如果提示无法导入,需要先安装该库:pip install ultralytics
from ultralytics import YOLO
# 导入PyTorch库(YOLO底层依赖的深度学习框架)
#import torch
# 主程序入口
if __name__ == "__main__":
# 加载预训练的YOLO模型,模型文件路径为:
# D:/点数识别/model/yolo11n.pt
model = YOLO('D:/点数识别/model/yolo11n.pt')
# 开始训练模型,参数说明:
model.train(
# 数据集配置文件路径,包含训练/验证数据的路径和类别信息
data='D:/点数识别/data.yaml',
# 训练轮数(整个数据集会被遍历330次)
epochs=330,
# 输入图像的尺寸(640x640像素)
imgsz=640,
# 每个批次的样本数量(16张图像)
batch=16,
# 使用GPU进行训练(CUDA设备0)
device='cuda:0',
# 训练结果保存的项目目录
project='D:/点数识别/my_train',
# 当前实验的名称(用于区分不同训练实验)
name='exp_small_objects_640',
# 边界框损失权重(控制检测框回归的强度)
box=3,
# 马赛克数据增强的概率(30%的概率使用马赛克增强)
mosaic=0.3,
# 使用矩形训练(优化内存使用,不填充图像)
rect=True,
)
每个模型训练完毕后,我都会使用一张裁剪后的图去验证,直至验证全部识别,看哪个模型识别成功率高,最后经过实测,1280尺寸的识别成功率最高,1920尺寸还会出现个别无法识别。
##完整的分割识别再合并代码
##感谢deepseek提供的技术支持
def initialize_components():
"""
初始化模型和检测器组件
"""
global model
# 加载YOLO目标检测模型
model_path = os.path.join(os.path.dirname(__file__), '../model/yolov8n.pt')
model = YOLO(model_path)
print('组件初始化完成')
def process_prediction(image_path):
# 改为从图片文件读取
# image_path = os.path.join(current_dir, 'test.jpg') # 默认输入图片路径
if not os.path.exists(image_path):
print(f"未找到输入图片: {image_path}")
exit()
# 读取图片
frame = cv2.imread(image_path)
if frame is None:
print("无法读取图片文件")
exit()
# 将图片对半切分
height, width = frame.shape[:2]
half_width = width // 2
left_img = frame[:, :half_width]
right_img = frame[:, half_width:]
# 计算识别时间
start_detect = time.time()
# 对左半部分进行检测
model.to('cuda')
left_results = model.predict(
source=left_img,
conf=0.05,
iou=0.1,
imgsz=1280,
rect=True,
device='cuda'
)
# 创建输出目录(改为相对路径)
soft_path=r'D:\点数识别\results'
os.makedirs(soft_path, exist_ok=True)
# 对右半部分进行检测
right_results = model.predict(
source=right_img,
conf=0.05,
iou=0.1,
imgsz=1280,
rect=True,
device='cuda'
)
end_detect = time.time()
# print(f'图片识别耗时: {end_detect - start_detect:.2f}秒')
# 初始化计数器
left_count = 0
right_count = 0
# 处理左半部分检测结果
for result in left_results:
left_count += len(result.boxes)
for box in result.boxes:
x1 = int(box.xyxy[0][0])
y1 = int(box.xyxy[0][1])
x2 = int(box.xyxy[0][2])
y2 = int(box.xyxy[0][3])
cls_id = int(box.cls[0])
conf = float(box.conf[0])
label = f'{result.names[cls_id]} {conf:.2f}'
cv2.rectangle(left_img, (x1, y1), (x2, y2), (0, 255, 0), 1)
cv2.putText(left_img, label, (x1, y1-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,255,0), 1)
# 处理右半部分检测结果
for result in right_results:
right_count += len(result.boxes)
for box in result.boxes:
x1 = int(box.xyxy[0][0])
y1 = int(box.xyxy[0][1])
x2 = int(box.xyxy[0][2])
y2 = int(box.xyxy[0][3])
cls_id = int(box.cls[0])
conf = float(box.conf[0])
label = f'{result.names[cls_id]} {conf:.2f}'
cv2.rectangle(right_img, (x1, y1), (x2, y2), (0, 255, 0), 1)
cv2.putText(right_img, label, (x1, y1-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0,255,0), 1)
# 输出统计结果
total_count = left_count + right_count
print(f'左半部分识别数量: {left_count}')
print(f'右半部分识别数量: {right_count}')
print(f'识别总数量: {total_count}')
# 保存切分后的检测结果
left_output = os.path.join(soft_path, 'left_detection.jpg')
right_output = os.path.join(soft_path, 'right_detection.jpg')
cv2.imwrite(left_output, left_img)
cv2.imwrite(right_output, right_img)
# 合并显示结果
combined = cv2.hconcat([left_img, right_img])
combined_output = os.path.join(soft_path, 'combined_detection.jpg')
cv2.imwrite(combined_output, combined)
print(f'左半部分检测结果已保存至: {left_output}')
print(f'右半部分检测结果已保存至: {right_output}')
print(f'合并后的检测结果已保存至: {combined_output}')
return left_count, right_count, total_count



效果很OK, 由于没做定位,放上去检测的时候,对半分割时可能把产品分割成两半了,导致识别异常,这种改善比较简单,做个L型定位,然后放上就移动到一侧,这样分割的时候就不会分割到产品了,也不影响效率。
最终的用法,由于技术不行,不会做到两个程序中的内存交互,只能通过笨方法,存储到文件夹中:
上位机界面,黑色部分是摄像头识别产品区域,490是指这个产品满盘标准数量,如果不达标就是数量异常,会弹窗提醒,并由用户确认

以上设计完成, 并验证可用,识别速度首次因为需要加载模型,所以第一次大概3-5秒左右,识别过程中保持在3秒全部识别完毕,共计490个目标,采用3060显卡,CPU的话识别慢一点,大概5-8秒左右,还有很大的优化空间。
思考
为什么之前也采用了局部画面去识别,但是没法识别全部呢?后续采用了分割的方式就能识别全部呢?
首先我认为最开始局部去识别的时候,是把镜头拉近,导致目标很清晰,也就有很多特征,所以特征太多了,识别能力反而下降部分。
如果是全图的照片去分割,因为全图距离目标比较远,特征很多都模糊的,所以细节少很多,也就是识别起来没那么多特征信息,所以识别效果很好,就像是一个不近视的和近视的人,不近视的人,看的清很多细节,就知道这个人叫什么名字,而近视的人,看到的只是人,却不知对方叫什么名,我们刚好需要的就是这种,只需要知道目标就行,不需要知道目标具体叫什么,所以模模糊糊的识别效果反而好很多。
还有一点疑问的猜想
为什么同一个模型,在图片都没有压缩的情况下,整图识别和分割识别会差那么多呢?
我想整图识别的话会在识别过程中整体压缩到1280尺寸,细节丢失很多,但是分割后再压缩成1280尺寸,那么细节丢失会减少一半,还有一种可能性,就是yolo识别目标的上限是一次性达不到400多个,太多了就会丢失识别过程。
*本人非视觉专业或算法专业,纯粹业余人员,如有错误请指出修正,或有更好的建议可以分享优化,一起进步