前言 最近,我独立开发了一套面向光电设备的上位机系统,用于实时拍摄与目标跟踪。起初,我对 AI 可谓一窍不通,但项目需求逼人,只能“硬核”补课。几番调研后,我锁定了当下最火的目标检测框架——YOLO。然而,YOLO 在大目标上表现惊艳,面对小目标却力不从心。为此,我又引入 OpenCV 的轮廓识别算法,将两种方案融合:大目标交给 YOLO,小目标交给 OpenCV,互补短板,整体精度显著提升。为了把算法落地,我啃完了 YOLO 标注、训练、推理的整条链路,并自研了一套配套软件,集成两大核心功能:
- 智能标注:
针对工业场景反复打磨,支持一键框选、快捷键批量操作、自动保存与回滚;相比市面工具,真正做到了“打开就会用,十分钟出活”。
- 目标检测:
图片或视频直接拖拽进软件,即可实时完成目标识别与跟踪,结果可视化、可导出,全流程无缝衔接。
本文将完整记录从零搭建这套系统的思路、踩坑与优化细节,并公开软件的核心功能设计,希望能给同样奋战在上位机与 AI 结合一线的开发者一点参考。1 图片搜集与标注为训练“鸟 vs 无人机”二分类模型,每类需准备上千张样本。数据源三路并进:
- 自采:无人机实拍+长焦拍鸟,确保场景真实;
- 开源:COCO 中抽取含鸟/无人机的切片;
- 补充:淘宝购买高清图包、CSDN 资源帖批量下载。
下载完成后,按类别归档:dataset/bird/ ,dataset/drone/ 目录就绪,双击启动自研标注工具,开始高效标注。
2 环境搭建
虚拟环境使用conda,ide使用PyCharm,官网下载ultralytics。具体配置参见文章《基于YOLOv11的无人机目标检测实战(Windows环境)》
3 图片整理与训练
图片按照训练集、验证集、测试集分类,比例为8:1:1 ,实现分类的代码如下- 1 # 将图片和标注数据按比例切分为 训练集和测试集
- 2 import shutil
- 3 import random
- 4 import os
- 5
- 6 # 原始路径
- 7 image_original_path = "data/images/"
- 8 label_original_path = "data/labels/"
- 9
- 10 cur_path = os.getcwd()
- 11 # 训练集路径
- 12 train_image_path = os.path.join(cur_path, "datasets/images/train/")
- 13 train_label_path = os.path.join(cur_path, "datasets/labels/train/")
- 14
- 15 # 验证集路径
- 16 val_image_path = os.path.join(cur_path, "datasets/images/val/")
- 17 val_label_path = os.path.join(cur_path, "datasets/labels/val/")
- 18
- 19 # 测试集路径
- 20 test_image_path = os.path.join(cur_path, "datasets/images/test/")
- 21 test_label_path = os.path.join(cur_path, "datasets/labels/test/")
- 22
- 23 # 训练集目录
- 24 list_train = os.path.join(cur_path, "datasets/train.txt")
- 25 list_val = os.path.join(cur_path, "datasets/val.txt")
- 26 list_test = os.path.join(cur_path, "datasets/test.txt")
- 27
- 28 train_percent = 0.8
- 29 val_percent = 0.1
- 30 test_percent = 0.1
- 31
- 32
- 33 def del_file(path):
- 34 for i in os.listdir(path):
- 35 file_data = path + "\" + i
- 36 os.remove(file_data)
- 37
- 38
- 39 def mkdir():
- 40 if not os.path.exists(train_image_path):
- 41 os.makedirs(train_image_path)
- 42 else:
- 43 del_file(train_image_path)
- 44 if not os.path.exists(train_label_path):
- 45 os.makedirs(train_label_path)
- 46 else:
- 47 del_file(train_label_path)
- 48
- 49 if not os.path.exists(val_image_path):
- 50 os.makedirs(val_image_path)
- 51 else:
- 52 del_file(val_image_path)
- 53 if not os.path.exists(val_label_path):
- 54 os.makedirs(val_label_path)
- 55 else:
- 56 del_file(val_label_path)
- 57
- 58 if not os.path.exists(test_image_path):
- 59 os.makedirs(test_image_path)
- 60 else:
- 61 del_file(test_image_path)
- 62 if not os.path.exists(test_label_path):
- 63 os.makedirs(test_label_path)
- 64 else:
- 65 del_file(test_label_path)
- 66
- 67
- 68 def clearfile():
- 69 if os.path.exists(list_train):
- 70 os.remove(list_train)
- 71 if os.path.exists(list_val):
- 72 os.remove(list_val)
- 73 if os.path.exists(list_test):
- 74 os.remove(list_test)
- 75
- 76
- 77 def main():
- 78 mkdir()
- 79 clearfile()
- 80
- 81 file_train = open(list_train, 'w')
- 82 file_val = open(list_val, 'w')
- 83 file_test = open(list_test, 'w')
- 84
- 85 total_txt = os.listdir(label_original_path)
- 86 num_txt = len(total_txt)
- 87 list_all_txt = range(num_txt)
- 88
- 89 num_train = int(num_txt * train_percent)
- 90 num_val = int(num_txt * val_percent)
- 91 num_test = num_txt - num_train - num_val
- 92
- 93 train = random.sample(list_all_txt, num_train)
- 94 # train从list_all_txt取出num_train个元素
- 95 # 所以list_all_txt列表只剩下了这些元素
- 96 val_test = [i for i in list_all_txt if not i in train]
- 97 # 再从val_test取出num_val个元素,val_test剩下的元素就是test
- 98 val = random.sample(val_test, num_val)
- 99
- 100 print("训练集数目:{}, 验证集数目:{}, 测试集数目:{}".format(len(train), len(val), len(val_test) - len(val)))
- 101 for i in list_all_txt:
- 102 name = total_txt[i][:-4]
- 103
- 104 srcImage = image_original_path + name + '.jpg'
- 105 if os.path.isfile(srcImage):
- 106 suffix = '.jpg'
- 107 else:
- 108 suffix = '.png'
- 109 srcImage = image_original_path + name + '.png'
- 110
- 111 srcLabel = label_original_path + name + ".txt"
- 112
- 113 if i in train:
- 114 dst_train_Image = train_image_path + name + suffix
- 115 dst_train_Label = train_label_path + name + '.txt'
- 116 shutil.copyfile(srcImage, dst_train_Image)
- 117 shutil.copyfile(srcLabel, dst_train_Label)
- 118 file_train.write(dst_train_Image + '\n')
- 119 elif i in val:
- 120 dst_val_Image = val_image_path + name + '.jpg'
- 121 dst_val_Label = val_label_path + name + '.txt'
- 122 shutil.copyfile(srcImage, dst_val_Image)
- 123 shutil.copyfile(srcLabel, dst_val_Label)
- 124 file_val.write(dst_val_Image + '\n')
- 125 else:
- 126 dst_test_Image = test_image_path + name + suffix
- 127 dst_test_Label = test_label_path + name + '.txt'
- 128 shutil.copyfile(srcImage, dst_test_Image)
- 129 shutil.copyfile(srcLabel, dst_test_Label)
- 130 file_test.write(dst_test_Image + '\n')
- 131
- 132 file_train.close()
- 133 file_val.close()
- 134 file_test.close()
- 135
- 136
- 137 if __name__ == "__main__":
- 138 main()
复制代码 训练代码如下- 1 import warnings
- 2 warnings.filterwarnings('ignore')
- 3 from ultralytics import YOLO
- 4 if __name__ == '__main__':
- 5 model = YOLO('ultralytics/cfg/models/12/yolo12n.yaml')
- 6 model.load('yolo12n.pt') #注释则不加载
- 7 results = model.train(
- 8 data='data.yaml', #数据集配置文件的路径
- 9 epochs=180, #训练轮次总数
- 10 batch=4, #批量大小,即单次输入多少图片训练
- 11 imgsz=640, #训练图像尺寸
- 12 workers=2, #加载数据的工作线程数
- 13 device= 0, #指定训练的计算设备,无nvidia显卡则改为 'cpu'
- 14 optimizer='SGD', #训练使用优化器,可选 auto,SGD,Adam,AdamW 等
- 15 amp= True, #True 或者 False, 解释为:自动混合精度(AMP) 训练
- 16 patience = 50,
- 17 cache=False, # True 在内存中缓存数据集图像,服务器推荐开启
- 18 augment=True,
- 19 lr0 = 0.001,
- 20 lrf = 0.01,
- 21 cos_lr=True,
- 22 weight_decay=0.05
- 23 )
复制代码 训练结果,mAP精度达到9提上,分数非常高。
4 图片视频目标识别
视频文件目标识别
后记 本文详细介绍了基于YOLO与OpenCV的光电上位机目标检测系统的开发过程。针对YOLO框架在小目标检测上的局限性,创新性地结合OpenCV轮廓识别算法,提升了系统的检测精度和适应性。通过数月的技术研究与实践,完成了以下工作:
- 技术研究:深入掌握YOLO模型的标注、训练及推理流程,并结合OpenCV优化小目标识别效果。
- 软件开发:自主设计并实现了一套功能完善的软件系统,包括图像标注工具和目标检测模块,显著提升了易用性和效率。
- 创新优化:针对实际需求优化标注功能,使软件操作更便捷,性能优于市面同类产品。
本系统的开发不仅验证了YOLO与OpenCV结合的技术可行性,也为类似场景下的目标检测任务提供了可借鉴的解决方案。未来,可进一步优化模型性能,拓展多目标跟踪、实时检测等高级功能,以满足更广泛的应用需求。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |