智能光电检测:YOLO+OpenCV联合算法工程实践

前言 最近,我独立开发了一套面向光电设备的上位机系统,用于实时拍摄与目标跟踪。起初,我对 AI 可谓一窍不通,但项目需求逼人,只能"硬核"补课。几番调研后,我锁定了当下最火的目标检测框架------YOLO。然而,YOLO 在大目标上表现惊艳,面对小目标却力不从心。为此,我又引入 OpenCV 的轮廓识别算法,将两种方案融合:大目标交给 YOLO,小目标交给 OpenCV,互补短板,整体精度显著提升。

为了把算法落地,我啃完了 YOLO 标注、训练、推理的整条链路,并自研了一套配套软件,集成两大核心功能:

  1. 智能标注:

    针对工业场景反复打磨,支持一键框选、快捷键批量操作、自动保存与回滚;相比市面工具,真正做到了"打开就会用,十分钟出活"。

  2. 目标检测:

    图片或视频直接拖拽进软件,即可实时完成目标识别与跟踪,结果可视化、可导出,全流程无缝衔接。

本文将完整记录从零搭建这套系统的思路、踩坑与优化细节,并公开软件的核心功能设计,希望能给同样奋战在上位机与 AI 结合一线的开发者一点参考。

1 图片搜集与标注

为训练"鸟 vs 无人机"二分类模型,每类需准备上千张样本。数据源三路并进:

  1. 自采:无人机实拍+长焦拍鸟,确保场景真实;
  2. 开源:COCO 中抽取含鸟/无人机的切片;
  3. 补充:淘宝购买高清图包、CSDN 资源帖批量下载。

下载完成后,按类别归档:dataset/bird/ ,dataset/drone/ 目录就绪,双击启动自研标注工具,开始高效标注。

2 环境搭建

虚拟环境使用conda,ide使用PyCharm,官网下载ultralytics。具体配置参见文章《基于YOLOv11的无人机目标检测实战(Windows环境)》

3 图片整理与训练

图片按照训练集、验证集、测试集分类,比例为8:1:1 ,实现分类的代码如下

python 复制代码
# 将图片和标注数据按比例切分为 训练集和测试集
import shutil
import random
import os

# 原始路径
image_original_path = "data/images/"
label_original_path = "data/labels/"

cur_path = os.getcwd()
# 训练集路径
train_image_path = os.path.join(cur_path, "datasets/images/train/")
train_label_path = os.path.join(cur_path, "datasets/labels/train/")

# 验证集路径
val_image_path = os.path.join(cur_path, "datasets/images/val/")
val_label_path = os.path.join(cur_path, "datasets/labels/val/")

# 测试集路径
test_image_path = os.path.join(cur_path, "datasets/images/test/")
test_label_path = os.path.join(cur_path, "datasets/labels/test/")

# 训练集目录
list_train = os.path.join(cur_path, "datasets/train.txt")
list_val = os.path.join(cur_path, "datasets/val.txt")
list_test = os.path.join(cur_path, "datasets/test.txt")

train_percent = 0.8
val_percent = 0.1
test_percent = 0.1


def del_file(path):
    for i in os.listdir(path):
        file_data = path + "\\" + i
        os.remove(file_data)


def mkdir():
    if not os.path.exists(train_image_path):
        os.makedirs(train_image_path)
    else:
        del_file(train_image_path)
    if not os.path.exists(train_label_path):
        os.makedirs(train_label_path)
    else:
        del_file(train_label_path)

    if not os.path.exists(val_image_path):
        os.makedirs(val_image_path)
    else:
        del_file(val_image_path)
    if not os.path.exists(val_label_path):
        os.makedirs(val_label_path)
    else:
        del_file(val_label_path)

    if not os.path.exists(test_image_path):
        os.makedirs(test_image_path)
    else:
        del_file(test_image_path)
    if not os.path.exists(test_label_path):
        os.makedirs(test_label_path)
    else:
        del_file(test_label_path)


def clearfile():
    if os.path.exists(list_train):
        os.remove(list_train)
    if os.path.exists(list_val):
        os.remove(list_val)
    if os.path.exists(list_test):
        os.remove(list_test)


def main():
    mkdir()
    clearfile()

    file_train = open(list_train, 'w')
    file_val = open(list_val, 'w')
    file_test = open(list_test, 'w')

    total_txt = os.listdir(label_original_path)
    num_txt = len(total_txt)
    list_all_txt = range(num_txt)

    num_train = int(num_txt * train_percent)
    num_val = int(num_txt * val_percent)
    num_test = num_txt - num_train - num_val

    train = random.sample(list_all_txt, num_train)
    # train从list_all_txt取出num_train个元素
    # 所以list_all_txt列表只剩下了这些元素
    val_test = [i for i in list_all_txt if not i in train]
    # 再从val_test取出num_val个元素,val_test剩下的元素就是test
    val = random.sample(val_test, num_val)

    print("训练集数目:{}, 验证集数目:{}, 测试集数目:{}".format(len(train), len(val), len(val_test) - len(val)))
    for i in list_all_txt:
        name = total_txt[i][:-4]

        srcImage = image_original_path + name + '.jpg'
        if os.path.isfile(srcImage):
            suffix = '.jpg'
        else:
            suffix = '.png'
            srcImage = image_original_path + name + '.png'

        srcLabel = label_original_path + name + ".txt"

        if i in train:
            dst_train_Image = train_image_path + name + suffix
            dst_train_Label = train_label_path + name + '.txt'
            shutil.copyfile(srcImage, dst_train_Image)
            shutil.copyfile(srcLabel, dst_train_Label)
            file_train.write(dst_train_Image + '\n')
        elif i in val:
            dst_val_Image = val_image_path + name + '.jpg'
            dst_val_Label = val_label_path + name + '.txt'
            shutil.copyfile(srcImage, dst_val_Image)
            shutil.copyfile(srcLabel, dst_val_Label)
            file_val.write(dst_val_Image + '\n')
        else:
            dst_test_Image = test_image_path + name + suffix
            dst_test_Label = test_label_path + name + '.txt'
            shutil.copyfile(srcImage, dst_test_Image)
            shutil.copyfile(srcLabel, dst_test_Label)
            file_test.write(dst_test_Image + '\n')

    file_train.close()
    file_val.close()
    file_test.close()


if __name__ == "__main__":
    main()

训练代码如下

python 复制代码
import warnings
warnings.filterwarnings('ignore')
from ultralytics import YOLO
if __name__ == '__main__':
  model = YOLO('ultralytics/cfg/models/12/yolo12n.yaml')
  model.load('yolo12n.pt')  #注释则不加载
  results = model.train(
    data='data.yaml',  #数据集配置文件的路径
    epochs=180,  #训练轮次总数
    batch=4,  #批量大小,即单次输入多少图片训练
    imgsz=640,  #训练图像尺寸
    workers=2,  #加载数据的工作线程数
    device= 0,  #指定训练的计算设备,无nvidia显卡则改为 'cpu'
    optimizer='SGD',  #训练使用优化器,可选 auto,SGD,Adam,AdamW 等
    amp= True,  #True 或者 False, 解释为:自动混合精度(AMP) 训练
    patience = 50,
    cache=False,  # True 在内存中缓存数据集图像,服务器推荐开启
    augment=True,
    lr0 = 0.001,
    lrf = 0.01,
    cos_lr=True,
    weight_decay=0.05
)

训练结果,mAP精度达到9提上,分数非常高。

4 图片视频目标识别

视频文件目标识别

后记 本文详细介绍了基于YOLO与OpenCV的光电上位机目标检测系统的开发过程。针对YOLO框架在小目标检测上的局限性,创新性地结合OpenCV轮廓识别算法,提升了系统的检测精度和适应性。通过数月的技术研究与实践,完成了以下工作:

  1. 技术研究:深入掌握YOLO模型的标注、训练及推理流程,并结合OpenCV优化小目标识别效果。

  2. 软件开发:自主设计并实现了一套功能完善的软件系统,包括图像标注工具和目标检测模块,显著提升了易用性和效率。

  3. 创新优化:针对实际需求优化标注功能,使软件操作更便捷,性能优于市面同类产品。

本系统的开发不仅验证了YOLO与OpenCV结合的技术可行性,也为类似场景下的目标检测任务提供了可借鉴的解决方案。未来,可进一步优化模型性能,拓展多目标跟踪、实时检测等高级功能,以满足更广泛的应用需求。