Hugo ʕ•ᴥ•ʔ Bear Blog

前言


💡 首先需要感谢学校为我们提供的一个良好的学习平台,让我们通过毕设来了解项目开发的流程,并通过标准化的书写来规范格式。感谢罗老师的耐心指导和帮助,在实际毕设的写作过程中,关注着我们的每一个节点。
同时需要感谢公司Andawell的支持,能为我提供在公司边实习边做毕设的机会。同时能将毕设与项目开发的标准更好地结合。
最后还需要鼓励自己,因为需要付出很多的努力。

正文


一方面主要是技术上的要求,一方面主要是论文上的排版。然后希望我遇到的问题,也能帮到大家。

word 中 mathtype 公式偏上浮或偏下沉怎么办


刚才遇到了公式上浮的问题,我使用的是 amath 进行公式的编写,搭配图片转 latex 进行调整。然后找到了这个问题的解决办法。感谢 whu 数码君提供的回答

如何使用图片转 latex


之前有同学问过我这个问题,我今天也再一次遇到了这个问题,看到论文中复杂的公式,一筹莫展怎么办,可以通过图片转 latex 的方式,这是我比较常用的方式,但是现在主流的转 latex 的好的软件不多,在此特别感谢simpletex提供的支持。

关于语病的修改


感谢 up 主:用镜头记录成长,参考论文写作表达语句表达系列课程 一 病句修改 成分残缺_哔哩哔哩_bilibili该视频进行总结。

一、主语残缺


从这件小事,说明一个道理

应改为,这件小事说明一个道理。上述例子中缺少主语。

当学生了解了美是什么,就会以美的标准米规范自己的行为和思想。

应去除“当”字。

通过这件小事,使我明白了一个道理。

去除”通过“二字。

通过对该课题的研究,得出新时代精神的含义。

需添加主语,例如“笔者得出……”(另外还需要尽量避免在论文中使用“我”字)

课堂教学低效,一直未得到解决。

需在”低效“后面加上”的问题“

我对经济与生活这本教材感到困感。

需在“教材”后面加上“的内容”

在经历了几千年的封建统治后,人们又开始重视被禁锯的古典文化,并成为人文主义者的武器,用米反对神权。

应改为”古典文化并成为人文主义者的武器“

2022 年颁布了《家庭教育指导法》,是我国首部家庭教育法。

把“了”改成“的”

二、谓语残缺


经过几十年的努力,我国己经独立自主地研制、发射、跟踪和测检地球同步通讯卫星的能力。

具备……的能力

某大学工商管理学院在课程设置上除了专业课、外语课、政治理论课,还包括演讲与口才、基础写作等课程,以进一步提高说写方面的技能。

除了设置有…… (第一句话缺少谓语)

我市最近发动了全面的质量大检查运动,要在这个运动中建立与加强技术管理制度等一系列工作。

要在这个运动中完成建立与加强技术管理制度等一系列工作

政府必须严厉打击食品安全违法违规行为的高压态势,及时消除各环节领域的隐患,创新食品安全监管惩处体制机制。

保持严厉打击…

教师在法律专业知识和逻辑思维能力提升方面面临困难。

教师在法律专业知识拓展… (知识和提升搭配不是非常好,搭配知识拓展可能会更好)

三、宾语残缺


学校宿舍、教学楼等人群密集区,一且发生火灾,后果不堪设想,因此学生掌握火灾中自救互教相当重要。

掌握…技能

这部电影在塑造人物形象所提供的经验是非常宝贵的,

在…方面

桃花乡走可持续发展之路,按照建成生态环境和谐优美、资源集约节约利用、经济和社会协调发展的生态乡,制定了五年发展建设规划。

按照…目标,规划

四、虚词残缺


2014 年底,我国探月工程三期“再入返回飞行”试验获得成功,确保嫦娥五号任务顺利实施和探月工程持续推进莫定坚实基础,

为…奠定坚实基础

这本书非常有价值,常常图书馆收藏。

常常被图书馆收藏

现代女性越来越独立,她们除了自己独立挣钱外,对自身的价值、生活环境也越发重视,那些价值剥削、性骚扰,她们将说不。

对那些价值剥削、性骚扰,她们将说不。

搭建 yolov5 环境


详细参照 up 主:思绪亦无限的介绍:

Win11 中从头安装软件和配置环境运行深度学习项目

然后最主要的还需要引入英伟达的 CUDA,可以现在本地装,但我还是倾向于选择autodl,因为这里已经预装了 cuda 环境。

这里细节很多,但现在又过了好久了,有点忘了,在选择基础镜像的时候就应该要注意选择合适的版本,不然后面配置 cuda 会麻烦死。(看来日志还是要及时记录,当时费了一下午的劲,真是服了)

数据集的预处理


课题中 yolov5 的数据集是采用的航拍视角,在肉眼识别存在困难,还有很多干扰。所以需要预先对数据集进行切片处理。然后挑出切片完后包含黑匣子的图片,再进行标注。

以下是切片程序:

from PIL import Image
import os
import random
#from PIL import Image: 导入Python Imaging Library (PIL) 中的 Image 模块,用于图像处理。
#import os: 导入 os 模块,用于处理文件和目录。
#import random: 导入 random 模块,用于生成随机数。

def slice_and_save_images(input_folder, output_folder, random_size=True, fixed_size=(640, 640), overlap_ratio=0.2):
    #函数的定义,它接收几个参数,包括输入输出文件夹路径、是否随机切片大小、固定切片大小以及重叠比率。
    # 获取输入文件夹中的所有文件
    image_files = [f for f in os.listdir(input_folder) if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp'))]

    # 创建输出文件夹
    os.makedirs(output_folder, exist_ok=True)

    # 循环处理每张图像
    for image_file in image_files:
        input_path = os.path.join(input_folder, image_file)

        # 打开图像
        original_image = Image.open(input_path)

        # 获取图像大小
        width, height = original_image.size

        # 随机生成或手动指定切片大小
        #根据 random_size 参数决定是随机生成切片大小还是使用固定大小。随机大小在固定大小的一半到固定大小之间。
        if random_size:
            slice_width = random.randint(fixed_size[0] // 2, fixed_size[0])
            slice_height = random.randint(fixed_size[1] // 2, fixed_size[1])
        else:
            slice_width, slice_height = fixed_size

        # 计算重叠区域大小,由 overlap_ratio 参数决定的。
        overlap_width = int(slice_width * overlap_ratio)
        overlap_height = int(slice_height * overlap_ratio)

        # 计算切片数量
        num_slices_horizontal = (width - overlap_width) // (slice_width - overlap_width)
        num_slices_vertical = (height - overlap_height) // (slice_height - overlap_height)

        # 创建一个文件夹用于保存切片后的小图片
        output_subfolder = os.path.join(output_folder, os.path.splitext(image_file)[0])
        os.makedirs(output_subfolder, exist_ok=True)

        # 切片并保存
        for i in range(num_slices_horizontal):
            for j in range(num_slices_vertical):
                left = i * (slice_width - overlap_width)
                upper = j * (slice_height - overlap_height)
                right = left + slice_width
                lower = upper + slice_height

                # 切片
                slice_image = original_image.crop((left, upper, right, lower))

                # 生成唯一的切片文件名
                slice_filename = f"{os.path.splitext(image_file)[0]}_slice_{j}_{i}.jpg"

                # 保存切片
                slice_image.save(os.path.join(output_subfolder, slice_filename))

# 调用函数,传入输入图像文件夹路径和输出文件夹路径,更换为自己的文件夹
slice_and_save_images(r"C:\Users\Raj\Desktop\bbox", r"C:\Users\Raj\Desktop\bbox全部切片",
                      random_size=False, fixed_size=(640, 640))

切片完成之后,需要挑出包含切片中包含目标对象的图片,之后作为训练集。

验证集


最开始验证集是用的数据集的一部分,但是后面采用新的验证集,难度瞬间就上来了,测试精度从 0.996 瞬间跌到都到不了 0.7。一方面需要扩充数据集,另一方面需要选择合适的模型对网络参数进行调整。

扩充训练集


感谢 CSDN 博主:我要变胖哇路人贾’ω’

参考文章:YOLO 数据集实现数据增强的方法(裁剪、平移 、旋转、改变亮度、加噪声等)

数据增强的概念和作用


数据增强是一种重要的机器学习方法之一,是基于已有的训练样本数据来生成更多的训练数据,其目的就是为了使扩增的训练数据尽可能接近真实分布的数据,从而提高检测精度。此外,数据增强能够迫使模型学习到更多鲁棒性的特征,从而有效提高模型的泛化能力

常见的数据增强的办法


(1)比较常用的几何变换方法主要有: 翻转、旋转、裁剪、缩放、平移、抖动 (2)比较常用的像素变换方法主要有: 加椒盐噪声、高斯噪声、进行高斯模糊、调整 HSV 对比度、调节亮度、饱和度、直方图均衡化、调整白平衡等

具体解释如下:

txt 转 xml


在数据增强的时候,代码是对 xml 进行操作的,所以需要先经历一步,从 txt 到 xml 的过程。

核心原理解释:将 YOLO 格式中的归一化坐标转换为像素坐标。

  1. 从 YOLO 格式的文本文件中读取每个目标的类别索引和归一化的边界框坐标  (class_index, x_center_norm, y_center_norm, width_norm, height_norm)
  2. 将归一化的中心点坐标  (x_center_norm, y_center_norm)  和宽度、高度  (width_norm, height_norm)  转换为像素坐标。
  3. 使用像素坐标来计算 PASCAL VOC 格式的边界框  (xmin, ymin, xmax, ymax)

核心转换逻辑:

# 计算 PASCAL VOC 的 (xmin, ymin, xmax, ymax)
xmin = int(round(x_center - (width / 2)))
ymin = int(round(y_center - (height / 2)))
xmax = int(round(x_center + (width / 2)))
ymax = int(round(y_center + (height / 2)))

转换代码:

# -*- coding: utf-8 -*-
import os
import cv2
from lxml.etree import Element, SubElement, tostring


def txt_xml(img_path, img_name, txt_path, img_txt, xml_path, img_xml):
    clas = []
    # 读取txt的信息
    txt_img = os.path.join(txt_path, img_txt)

    # 图像的宽度和高度
    imw = 640  # 应当根据实际图片大小进行调整
    imh = 640  # 应当根据实际图片大小进行调整

    with open(txt_img, "r") as f:
        for line in f.readlines():
            line = line.strip('\n')
            list = line.split(" ")
            print(list)
            clas.append(list)

    # 创建XML文档的根节点和相关子节点
        # 创建XML文档的根节点
        node_root = Element('annotation')
        node_folder = SubElement(node_root, 'folder')
        node_folder.text = '1'
        node_filename = SubElement(node_root, 'filename')

        # 图像名称
        node_filename.text = img_name
        node_size = SubElement(node_root, 'size')
        node_width = SubElement(node_size, 'width')
        node_width.text = str(imw)
        node_height = SubElement(node_size, 'height')
        node_height.text = str(imh)
        node_depth = SubElement(node_size, 'depth')
        node_depth.text = '3'

    # 遍历所有的标注信息并添加到XML中
        # 遍历所有的标注信息
        for i in range(len(clas)):
            x_center_norm = float(clas[i][1])
            y_center_norm = float(clas[i][2])
            width_norm = float(clas[i][3])
            height_norm = float(clas[i][4])
            # 转换归一化坐标到像素坐标
            x_center = x_center_norm * imw
            y_center = y_center_norm * imh
            width = width_norm * imw
            height = height_norm * imh
            # 计算并转换为字符串
            xmin = str(int(round(x_center - (width / 2))))
            ymin = str(int(round(y_center - (height / 2))))
            xmax = str(int(round(x_center + (width / 2))))
            ymax = str(int(round(y_center + (height / 2))))

            # 创建XML节点并赋值
            node_object = SubElement(node_root, 'object')
            node_name = SubElement(node_object, 'name')
            node_name.text = str(clas[i][0])
            node_pose = SubElement(node_object, 'pose')
            node_pose.text = "Unspecified"
            node_truncated = SubElement(node_object, 'truncated')
            node_truncated.text = "0"
            node_difficult = SubElement(node_object, 'difficult')
            node_difficult.text = '0'
            node_bndbox = SubElement(node_object, 'bndbox')
            node_xmin = SubElement(node_bndbox, 'xmin')
            node_xmin.text = xmin
            node_ymin = SubElement(node_bndbox, 'ymin')
            node_ymin.text = ymin
            node_xmax = SubElement(node_bndbox, 'xmax')
            node_xmax.text = xmax
            node_ymax = SubElement(node_bndbox, 'ymax')
            node_ymax.text = ymax

    # 将XML内容写入文件
    xml = tostring(node_root, pretty_print=True)
    img_newxml = os.path.join(xml_path, img_xml)
    with open(img_newxml, 'wb') as file_object:
        file_object.write(xml)


if __name__ == "__main__":
    # 图像、标注和XML文件夹所在位置、更换自己的绝对路径
    img_path = r"D:\@Graduation\enhance\data\images\train"
    txt_path = r"D:\@Graduation\enhance\data\labels\train"
    xml_path = r"D:\@Graduation\enhance\data\labels\xml"
    for img_name in os.listdir(img_path):
        img_base_name = os.path.splitext(img_name)[0]
        img_xml = img_base_name + ".xml"
        img_txt = img_base_name + ".txt"
        txt_xml(img_path, img_name, txt_path, img_txt, xml_path, img_xml)

例如,这就是标注后的归一化坐标

0 0.719594 0.901037 0.108083 0.091896

这就是转换后的 xml 坐标和其他的一些元素,转换完可以用 labelimg 验证一下

<annotation>
  <folder>1</folder>
  <filename>000000_slice_2_2.jpg</filename>
  <size>
    <width>640</width>
    <height>640</height>
    <depth>3</depth>
  </size>
  <object>
    <name>0</name>
    <pose>Unspecified</pose>
    <truncated>0</truncated>
    <difficult>0</difficult>
    <bndbox>
      <xmin>426</xmin>
      <ymin>547</ymin>
      <xmax>495</xmax>
      <ymax>606</ymax>
    </bndbox>
  </object>
</annotation>

数据集扩充


根据上述分析的方式进行数据集扩充

# -*- coding=utf-8 -*-

import time
import random
import copy
import cv2
import os
import math
import numpy as np
from skimage.util import random_noise
from lxml import etree, objectify
import xml.etree.ElementTree as ET
import argparse


# 显示图片
def show_pic(img, bboxes=None):
    '''
    输入:
        img:图像array
        bboxes:图像的所有boudning box list, 格式为[[x_min, y_min, x_max, y_max]....]
        names:每个box对应的名称
    '''
    for i in range(len(bboxes)):
        bbox = bboxes[i]
        x_min = bbox[0]
        y_min = bbox[1]
        x_max = bbox[2]
        y_max = bbox[3]
        cv2.rectangle(img, (int(x_min), int(y_min)), (int(x_max), int(y_max)), (0, 255, 0), 3)
    cv2.namedWindow('pic', 0)  # 1表示原图
    cv2.moveWindow('pic', 0, 0)
    cv2.resizeWindow('pic', 1200, 800)  # 可视化的图片大小
    cv2.imshow('pic', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


# 图像均为cv2读取
class DataAugmentForObjectDetection():
    def __init__(self, rotation_rate=0.5, max_rotation_angle=5,
                 crop_rate=0.5, shift_rate=0.5, change_light_rate=0.5,
                 add_noise_rate=0.5, flip_rate=0.5,
                 cutout_rate=0.5, cut_out_length=50, cut_out_holes=1, cut_out_threshold=0.5,
                 is_addNoise=True, is_changeLight=True, is_cutout=True, is_rotate_img_bbox=True,
                 is_crop_img_bboxes=True, is_shift_pic_bboxes=True, is_filp_pic_bboxes=True):

        # 配置各个操作的属性
        self.rotation_rate = rotation_rate
        self.max_rotation_angle = max_rotation_angle
        self.crop_rate = crop_rate
        self.shift_rate = shift_rate
        self.change_light_rate = change_light_rate
        self.add_noise_rate = add_noise_rate
        self.flip_rate = flip_rate
        self.cutout_rate = cutout_rate

        self.cut_out_length = cut_out_length
        self.cut_out_holes = cut_out_holes
        self.cut_out_threshold = cut_out_threshold

        # 是否使用某种增强方式
        self.is_addNoise = is_addNoise
        self.is_changeLight = is_changeLight
        self.is_cutout = is_cutout
        self.is_rotate_img_bbox = is_rotate_img_bbox
        self.is_crop_img_bboxes = is_crop_img_bboxes
        self.is_shift_pic_bboxes = is_shift_pic_bboxes
        self.is_filp_pic_bboxes = is_filp_pic_bboxes

    # ----1.加噪声---- #
    def _addNoise(self, img):
        '''
        输入:
            img:图像array
        输出:
            加噪声后的图像array,由于输出的像素是在[0,1]之间,所以得乘以255
        '''
        # return cv2.GaussianBlur(img, (11, 11), 0)
        return random_noise(img, mode='gaussian', seed=int(time.time()), clip=True) * 255

    # ---2.调整亮度--- #
    def _changeLight(self, img):
        alpha = random.uniform(0.35, 1)
        blank = np.zeros(img.shape, img.dtype)
        return cv2.addWeighted(img, alpha, blank, 1 - alpha, 0)

    # ---3.cutout--- #
    def _cutout(self, img, bboxes, length=100, n_holes=1, threshold=0.5):
        '''
        原版本:https://github.com/uoguelph-mlrg/Cutout/blob/master/util/cutout.py
        Randomly mask out one or more patches from an image.
        Args:
            img : a 3D numpy array,(h,w,c)
            bboxes : 框的坐标
            n_holes (int): Number of patches to cut out of each image.
            length (int): The length (in pixels) of each square patch.
        '''

        def cal_iou(boxA, boxB):
            '''
            boxA, boxB为两个框,返回iou
            boxB为bouding box
            '''
            # determine the (x, y)-coordinates of the intersection rectangle
            xA = max(boxA[0], boxB[0])
            yA = max(boxA[1], boxB[1])
            xB = min(boxA[2], boxB[2])
            yB = min(boxA[3], boxB[3])

            if xB <= xA or yB <= yA:
                return 0.0

            # compute the area of intersection rectangle
            interArea = (xB - xA + 1) * (yB - yA + 1)

            # compute the area of both the prediction and ground-truth
            # rectangles
            boxAArea = (boxA[2] - boxA[0] + 1) * (boxA[3] - boxA[1] + 1)
            boxBArea = (boxB[2] - boxB[0] + 1) * (boxB[3] - boxB[1] + 1)
            iou = interArea / float(boxBArea)
            return iou

        # 得到h和w
        if img.ndim == 3:
            h, w, c = img.shape
        else:
            _, h, w, c = img.shape
        mask = np.ones((h, w, c), np.float32)
        for n in range(n_holes):
            chongdie = True  # 看切割的区域是否与box重叠太多
            while chongdie:
                y = np.random.randint(h)
                x = np.random.randint(w)

                y1 = np.clip(y - length // 2, 0,
                             h)  # numpy.clip(a, a_min, a_max, out=None), clip这个函数将将数组中的元素限制在a_min, a_max之间,大于a_max的就使得它等于 a_max,小于a_min,的就使得它等于a_min
                y2 = np.clip(y + length // 2, 0, h)
                x1 = np.clip(x - length // 2, 0, w)
                x2 = np.clip(x + length // 2, 0, w)

                chongdie = False
                for box in bboxes:
                    if cal_iou([x1, y1, x2, y2], box) > threshold:
                        chongdie = True
                        break
            mask[y1: y2, x1: x2, :] = 0.
        img = img * mask
        return img

    # ---4.旋转--- #
    def _rotate_img_bbox(self, img, bboxes, angle=5, scale=1.):
        '''
        参考:https://blog.csdn.net/u014540717/article/details/53301195crop_rate
        输入:
            img:图像array,(h,w,c)
            bboxes:该图像包含的所有boundingboxs,一个list,每个元素为[x_min, y_min, x_max, y_max],要确保是数值
            angle:旋转角度
            scale:默认1
        输出:
            rot_img:旋转后的图像array
            rot_bboxes:旋转后的boundingbox坐标list
        '''
        # 旋转图像
        w = img.shape[1]
        h = img.shape[0]
        # 角度变弧度
        rangle = np.deg2rad(angle)  # angle in radians
        # now calculate new image width and height
        nw = (abs(np.sin(rangle) * h) + abs(np.cos(rangle) * w)) * scale
        nh = (abs(np.cos(rangle) * h) + abs(np.sin(rangle) * w)) * scale
        # ask OpenCV for the rotation matrix
        rot_mat = cv2.getRotationMatrix2D((nw * 0.5, nh * 0.5), angle, scale)
        # calculate the move from the old center to the new center combined
        # with the rotation
        rot_move = np.dot(rot_mat, np.array([(nw - w) * 0.5, (nh - h) * 0.5, 0]))
        # the move only affects the translation, so update the translation
        rot_mat[0, 2] += rot_move[0]
        rot_mat[1, 2] += rot_move[1]
        # 仿射变换
        rot_img = cv2.warpAffine(img, rot_mat, (int(math.ceil(nw)), int(math.ceil(nh))), flags=cv2.INTER_LANCZOS4)

        # 矫正bbox坐标
        # rot_mat是最终的旋转矩阵
        # 获取原始bbox的四个中点,然后将这四个点转换到旋转后的坐标系下
        rot_bboxes = list()
        for bbox in bboxes:
            xmin = bbox[0]
            ymin = bbox[1]
            xmax = bbox[2]
            ymax = bbox[3]
            point1 = np.dot(rot_mat, np.array([(xmin + xmax) / 2, ymin, 1]))
            point2 = np.dot(rot_mat, np.array([xmax, (ymin + ymax) / 2, 1]))
            point3 = np.dot(rot_mat, np.array([(xmin + xmax) / 2, ymax, 1]))
            point4 = np.dot(rot_mat, np.array([xmin, (ymin + ymax) / 2, 1]))
            # 合并np.array
            concat = np.vstack((point1, point2, point3, point4))
            # 改变array类型
            concat = concat.astype(np.int32)
            # 得到旋转后的坐标
            rx, ry, rw, rh = cv2.boundingRect(concat)
            rx_min = rx
            ry_min = ry
            rx_max = rx + rw
            ry_max = ry + rh
            # 加入list中
            rot_bboxes.append([rx_min, ry_min, rx_max, ry_max])

        return rot_img, rot_bboxes

    # ---5.裁剪--- #
    def _crop_img_bboxes(self, img, bboxes):
        '''
        裁剪后的图片要包含所有的框
        输入:
            img:图像array
            bboxes:该图像包含的所有boundingboxs,一个list,每个元素为[x_min, y_min, x_max, y_max],要确保是数值
        输出:
            crop_img:裁剪后的图像array
            crop_bboxes:裁剪后的bounding box的坐标list
        '''
        # 裁剪图像
        w = img.shape[1]
        h = img.shape[0]
        x_min = w  # 裁剪后的包含所有目标框的最小的框
        x_max = 0
        y_min = h
        y_max = 0
        for bbox in bboxes:
            x_min = min(x_min, bbox[0])
            y_min = min(y_min, bbox[1])
            x_max = max(x_max, bbox[2])
            y_max = max(y_max, bbox[3])

        d_to_left = x_min  # 包含所有目标框的最小框到左边的距离
        d_to_right = w - x_max  # 包含所有目标框的最小框到右边的距离
        d_to_top = y_min  # 包含所有目标框的最小框到顶端的距离
        d_to_bottom = h - y_max  # 包含所有目标框的最小框到底部的距离

        # 随机扩展这个最小框
        crop_x_min = int(x_min - random.uniform(0, d_to_left))
        crop_y_min = int(y_min - random.uniform(0, d_to_top))
        crop_x_max = int(x_max + random.uniform(0, d_to_right))
        crop_y_max = int(y_max + random.uniform(0, d_to_bottom))

        # 随机扩展这个最小框 , 防止别裁的太小
        # crop_x_min = int(x_min - random.uniform(d_to_left//2, d_to_left))
        # crop_y_min = int(y_min - random.uniform(d_to_top//2, d_to_top))
        # crop_x_max = int(x_max + random.uniform(d_to_right//2, d_to_right))
        # crop_y_max = int(y_max + random.uniform(d_to_bottom//2, d_to_bottom))

        # 确保不要越界
        crop_x_min = max(0, crop_x_min)
        crop_y_min = max(0, crop_y_min)
        crop_x_max = min(w, crop_x_max)
        crop_y_max = min(h, crop_y_max)

        crop_img = img[crop_y_min:crop_y_max, crop_x_min:crop_x_max]

        # 裁剪boundingbox
        # 裁剪后的boundingbox坐标计算
        crop_bboxes = list()
        for bbox in bboxes:
            crop_bboxes.append([bbox[0] - crop_x_min, bbox[1] - crop_y_min, bbox[2] - crop_x_min, bbox[3] - crop_y_min])

        return crop_img, crop_bboxes

    # ---6.平移--- #
    def _shift_pic_bboxes(self, img, bboxes):
        '''
        平移后的图片要包含所有的框
        输入:
            img:图像array
            bboxes:该图像包含的所有boundingboxs,一个list,每个元素为[x_min, y_min, x_max, y_max],要确保是数值
        输出:
            shift_img:平移后的图像array
            shift_bboxes:平移后的bounding box的坐标list
        '''
        # 平移图像
        w = img.shape[1]
        h = img.shape[0]
        x_min = w  # 裁剪后的包含所有目标框的最小的框
        x_max = 0
        y_min = h
        y_max = 0
        for bbox in bboxes:
            x_min = min(x_min, bbox[0])
            y_min = min(y_min, bbox[1])
            x_max = max(x_max, bbox[2])
            y_max = max(y_max, bbox[3])

        d_to_left = x_min  # 包含所有目标框的最大左移动距离
        d_to_right = w - x_max  # 包含所有目标框的最大右移动距离
        d_to_top = y_min  # 包含所有目标框的最大上移动距离
        d_to_bottom = h - y_max  # 包含所有目标框的最大下移动距离

        x = random.uniform(-(d_to_left - 1) / 3, (d_to_right - 1) / 3)
        y = random.uniform(-(d_to_top - 1) / 3, (d_to_bottom - 1) / 3)

        M = np.float32([[1, 0, x], [0, 1, y]])  # x为向左或右移动的像素值,正为向右负为向左; y为向上或者向下移动的像素值,正为向下负为向上
        shift_img = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))

        #  平移boundingbox
        shift_bboxes = list()
        for bbox in bboxes:
            shift_bboxes.append([bbox[0] + x, bbox[1] + y, bbox[2] + x, bbox[3] + y])

        return shift_img, shift_bboxes

    # ---7.镜像--- #
    def _filp_pic_bboxes(self, img, bboxes):
        '''
            平移后的图片要包含所有的框
            输入:
                img:图像array
                bboxes:该图像包含的所有boundingboxs,一个list,每个元素为[x_min, y_min, x_max, y_max],要确保是数值
            输出:
                flip_img:平移后的图像array
                flip_bboxes:平移后的bounding box的坐标list
        '''
        # 翻转图像

        flip_img = copy.deepcopy(img)
        h, w, _ = img.shape

        sed = random.random()

        if 0 < sed < 0.33:  # 0.33的概率水平翻转,0.33的概率垂直翻转,0.33是对角反转
            flip_img = cv2.flip(flip_img, 0)  # _flip_x
            inver = 0
        elif 0.33 < sed < 0.66:
            flip_img = cv2.flip(flip_img, 1)  # _flip_y
            inver = 1
        else:
            flip_img = cv2.flip(flip_img, -1)  # flip_x_y
            inver = -1

        # 调整boundingbox
        flip_bboxes = list()
        for box in bboxes:
            x_min = box[0]
            y_min = box[1]
            x_max = box[2]
            y_max = box[3]

            if inver == 0:
                # 0:垂直翻转
                flip_bboxes.append([x_min, h - y_max, x_max, h - y_min])
            elif inver == 1:
                # 1:水平翻转
                flip_bboxes.append([w - x_max, y_min, w - x_min, y_max])
            elif inver == -1:
                # -1:水平垂直翻转
                flip_bboxes.append([w - x_max, h - y_max, w - x_min, h - y_min])
        return flip_img, flip_bboxes

    # 图像增强方法
    def dataAugment(self, img, bboxes):
        '''
        图像增强
        输入:
            img:图像array
            bboxes:该图像的所有框坐标
        输出:
            img:增强后的图像
            bboxes:增强后图片对应的box
        '''
        change_num = 0  # 改变的次数
        # print('------')
        while change_num < 1:  # 默认至少有一种数据增强生效

            if self.is_rotate_img_bbox:
                if random.random() > self.rotation_rate:  # 旋转
                    change_num += 1
                    angle = random.uniform(-self.max_rotation_angle, self.max_rotation_angle)
                    scale = random.uniform(0.7, 0.8)
                    img, bboxes = self._rotate_img_bbox(img, bboxes, angle, scale)

            if self.is_shift_pic_bboxes:
                if random.random() < self.shift_rate:  # 平移
                    change_num += 1
                    img, bboxes = self._shift_pic_bboxes(img, bboxes)

            if self.is_changeLight:
                if random.random() > self.change_light_rate:  # 改变亮度
                    change_num += 1
                    img = self._changeLight(img)

            if self.is_addNoise:
                if random.random() < self.add_noise_rate:  # 加噪声
                    change_num += 1
                    img = self._addNoise(img)
            if self.is_cutout:
                if random.random() < self.cutout_rate:  # cutout
                    change_num += 1
                    img = self._cutout(img, bboxes, length=self.cut_out_length, n_holes=self.cut_out_holes,
                                       threshold=self.cut_out_threshold)
            if self.is_filp_pic_bboxes:
                if random.random() < self.flip_rate:  # 翻转
                    change_num += 1
                    img, bboxes = self._filp_pic_bboxes(img, bboxes)

        return img, bboxes


# xml解析工具
class ToolHelper():
    # 从xml文件中提取bounding box信息, 格式为[[x_min, y_min, x_max, y_max, name]]
    def parse_xml(self, path):
        '''
        输入:
            xml_path: xml的文件路径
        输出:
            从xml文件中提取bounding box信息, 格式为[[x_min, y_min, x_max, y_max, name]]
        '''
        tree = ET.parse(path)
        root = tree.getroot()
        objs = root.findall('object')
        coords = list()
        for ix, obj in enumerate(objs):
            name = obj.find('name').text
            box = obj.find('bndbox')
            x_min = int(box[0].text)
            y_min = int(box[1].text)
            x_max = int(box[2].text)
            y_max = int(box[3].text)
            coords.append([x_min, y_min, x_max, y_max, name])
        return coords

    # 保存图片结果
    def save_img(self, file_name, save_folder, img):
        cv2.imwrite(os.path.join(save_folder, file_name), img)

    # 保持xml结果
    def save_xml(self, file_name, save_folder, img_info, height, width, channel, bboxs_info):
        '''
        :param file_name:文件名
        :param save_folder:#保存的xml文件的结果
        :param height:图片的信息
        :param width:图片的宽度
        :param channel:通道
        :return:
        '''
        folder_name, img_name = img_info  # 得到图片的信息

        E = objectify.ElementMaker(annotate=False)

        anno_tree = E.annotation(
            E.folder(folder_name),
            E.filename(img_name),
            E.path(os.path.join(folder_name, img_name)),
            E.source(
                E.database('Unknown'),
            ),
            E.size(
                E.width(width),
                E.height(height),
                E.depth(channel)
            ),
            E.segmented(0),
        )

        labels, bboxs = bboxs_info  # 得到边框和标签信息
        for label, box in zip(labels, bboxs):
            anno_tree.append(
                E.object(
                    E.name(label),
                    E.pose('Unspecified'),
                    E.truncated('0'),
                    E.difficult('0'),
                    E.bndbox(
                        E.xmin(box[0]),
                        E.ymin(box[1]),
                        E.xmax(box[2]),
                        E.ymax(box[3])
                    )
                ))

        etree.ElementTree(anno_tree).write(os.path.join(save_folder, file_name), pretty_print=True)


if __name__ == '__main__':

    need_aug_num = 5  # 每张图片需要增强的次数

    is_endwidth_dot = True  # 文件是否以.jpg或者png结尾

    dataAug = DataAugmentForObjectDetection()  # 数据增强工具类

    toolhelper = ToolHelper()  # 工具

    # 获取相关参数
    parser = argparse.ArgumentParser()
    parser.add_argument('--source_img_path', type=str, default='D:\@Graduation\enhance\images')
    parser.add_argument('--source_xml_path', type=str, default='D:\@Graduation\enhance\labels')
    parser.add_argument('--save_img_path', type=str, default='D:\@Graduation\enhance\images2')
    parser.add_argument('--save_xml_path', type=str, default='D:\@Graduation\enhance\Annotations2')
    args = parser.parse_args()
    source_img_path = args.source_img_path  # 图片原始位置
    source_xml_path = args.source_xml_path  # xml的原始位置

    save_img_path = args.save_img_path  # 图片增强结果保存文件
    save_xml_path = args.save_xml_path  # xml增强结果保存文件

    # 如果保存文件夹不存在就创建
    if not os.path.exists(save_img_path):
        os.mkdir(save_img_path)

    if not os.path.exists(save_xml_path):
        os.mkdir(save_xml_path)

    for parent, _, files in os.walk(source_img_path):
        files.sort()
        for file in files:
            cnt = 0
            pic_path = os.path.join(parent, file)
            xml_path = os.path.join(source_xml_path, file[:-4] + '.xml')
            values = toolhelper.parse_xml(xml_path)  # 解析得到box信息,格式为[[x_min,y_min,x_max,y_max,name]]
            coords = [v[:4] for v in values]  # 得到框
            labels = [v[-1] for v in values]  # 对象的标签

            # 如果图片是有后缀的
            if is_endwidth_dot:
                # 找到文件的最后名字
                dot_index = file.rfind('.')
                _file_prefix = file[:dot_index]  # 文件名的前缀
                _file_suffix = file[dot_index:]  # 文件名的后缀
            img = cv2.imread(pic_path)

            # show_pic(img, coords)  # 显示原图
            while cnt < need_aug_num:  # 继续增强
                auged_img, auged_bboxes = dataAug.dataAugment(img, coords)
                auged_bboxes_int = np.array(auged_bboxes).astype(np.int32)
                height, width, channel = auged_img.shape  # 得到图片的属性
                img_name = '{}_{}{}'.format(_file_prefix, cnt + 1, _file_suffix)  # 图片保存的信息
                toolhelper.save_img(img_name, save_img_path,
                                    auged_img)  # 保存增强图片

                toolhelper.save_xml('{}_{}.xml'.format(_file_prefix, cnt + 1),
                                    save_xml_path, (save_img_path, img_name), height, width, channel,
                                    (labels, auged_bboxes_int))  # 保存xml文件
                # show_pic(auged_img, auged_bboxes)  # 强化后的图
                print(img_name)
                cnt += 1  # 继续增强下一张

这是扩充数据集后的效果图:

接下来需要对扩充好的数据集进行 xml 转 txt:

import xml.etree.ElementTree as ET
import os
from os import getcwd
import glob

# 1.
# 自己创建文件夹,例如:label_mal label_txt  也可以修改别的
image_set = 'xml'  # 需要转换的文件夹名称(文件夹内放xml标签文件)
imageset2 = 'labels2'  # 保存txt的文件夹
# 2.
# 换成你的类别 当前的顺序,就txt 0,1,2,3 四个类别
classes = ['0']  # 标注时的标签 注意顺序一定不要错。

# 3.
# # 转换文件夹的绝对路径
# data_dir = 'D:/detectAuto_/data'
# 或者 读取当前路径
data_dir = getcwd()  # 当前路径


'''
xml中框的左上角坐标和右下角坐标(x1,y1,x2,y2)
》》txt中的中心点坐标和宽和高(x,y,w,h),并且归一化
'''


def convert(size, box):
    dw = 1. / size[0]
    dh = 1. / size[1]
    x = (box[0] + box[1]) / 2.0
    y = (box[2] + box[3]) / 2.0
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x * dw
    w = w * dw
    y = y * dh
    h = h * dh
    return (x, y, w, h)


def convert_annotation(data_dir, imageset1, imageset2, image_id):
    in_file = open(data_dir + '/%s/%s.xml' % (imageset1, image_id),encoding='UTF-8')  # 读取xml
    out_file = open(data_dir + '/%s/%s.txt' % (imageset2, image_id), 'w',encoding='UTF-8')  # 保存txt

    tree = ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)
    for obj in root.iter('object'):
        difficult = obj.find('difficult').text
        cls = obj.find('name').text
        if cls not in classes or int(difficult) == 1:
            continue
        cls_id = classes.index(cls)  # 获取类别索引
        xmlbox = obj.find('bndbox')
        b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
             float(xmlbox.find('ymax').text))
        bb = convert((w, h), b)
        out_file.write(str(cls_id) + " " + " ".join([str('%.6f' % a) for a in bb]) + '\n')


image_ids = []
for x in glob.glob(data_dir + '/%s' % image_set + '/*.xml'):
    image_ids.append(os.path.basename(x)[:-4])
print('\n%s数量:' % image_set, len(image_ids))  # 确认数量
i = 0
for image_id in image_ids:
    i = i + 1
    convert_annotation(data_dir, image_set, imageset2, image_id)
    print("%s 数据:%s/%s文件完成!" % (image_set, i, len(image_ids)))

print("Done!!!")

然后打包好数据集上传服务器

Autodl 训练效果


由于引入了数据集进行扩充后,图片的数量增多,训练时间也更长。

50 轮训练的效果图:

60 轮训练的效果图:

100 轮训练的效果图:

应该还是数据集的问题,出现了过拟合的现象。之后肯定还需要对数据集进行调整,另外由于采用的是最快的训练模型,在训练精度上肯定还有提升的空间。但之前在使用的时候,还出现了一些报错的情况,需要再花时间研究研究。

测试模块的编写


在设计 GUI 之前,需要初步完成图片和视频检测的程序编写

1.图片检测测试-核心代码摘录

def predict(img):
    img = torch.from_numpy(img).to(device)
    img = img.half() if half else img.float()
    img /= 255.0
    if img.ndimension() == 3:
        img = img.unsqueeze(0)

    t1 = time_synchronized()
    pred = model(img, augment=False)[0]
    pred = non_max_suppression(pred, opt.conf_thres, opt.iou_thres, classes=opt.classes,
                               agnostic=opt.agnostic_nms)
    t2 = time_synchronized()
    InferNms = round((t2 - t1), 2)

    return pred, InferNms


def cv_imread(filePath):
    # 读取图片
    cv_img = cv2.imdecode(np.fromfile(filePath, dtype=np.uint8), -1)

    if len(cv_img.shape) > 2:
        if cv_img.shape[2] > 3:
            cv_img = cv_img[:, :, :3]
    return cv_img


def plot_one_box(img, x, color=None, label=None, line_thickness=None):
    # Plots one bounding box on image img
    tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1  # line/font thickness
    color = color or [random.randint(0, 255) for _ in range(3)]
    c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
    cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if label:
        tf = max(tl - 1, 1)  # font thickness
        t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
        c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
        cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA)  # filled
        cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)


if __name__ == '__main__':
    img_path = "./UI_rec/testpicture/000743_slice_3_5.jpg"
    image = cv_imread(img_path)
    image = cv2.resize(image, (640, 640))
    img0 = image.copy()
    img = letterbox(img0, new_shape=imgsz)[0]
    img = np.stack(img, 0)
    img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416
    img = np.ascontiguousarray(img)

    pred, useTime = predict(img)

    det = pred[0]
    p, s, im0 = None, '', img0
    if det is not None and len(det):  # 如果有检测信息则进入
        det[:, :4] = scale_coords(img.shape[1:], det[:, :4], im0.shape).round()  # 把图像缩放至im0的尺寸
        number_i = 0  # 类别预编号
        detInfo = []
        for *xyxy, conf, cls in reversed(det):  # 遍历检测信息
            c1, c2 = (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3]))
            # 将检测信息添加到字典中
            detInfo.append([names[int(cls)], [c1[0], c1[1], c2[0], c2[1]], '%.2f' % conf])
            number_i += 1  # 编号数+1

            label = '%s %.2f' % (names[int(cls)], conf)

            # 画出检测到的目标物
            plot_one_box(image, xyxy, label=label, color=colors[int(cls)])
    # 实时显示检测画面
    cv2.imshow('Stream', image)
    # if cv2.waitKey(1) & 0xFF == ord('q'):
    #     break
    c = cv2.waitKey(0) & 0xff

2.视频检测测试-核心代码摘录

def predict(img):
    img = torch.from_numpy(img).to(device)
    img = img.half() if half else img.float()
    img /= 255.0
    if img.ndimension() == 3:
        img = img.unsqueeze(0)

    t1 = time_synchronized()
    pred = model(img, augment=False)[0]
    pred = non_max_suppression(pred, opt.conf_thres, opt.iou_thres, classes=opt.classes,
                               agnostic=opt.agnostic_nms)
    t2 = time_synchronized()
    InferNms = round((t2 - t1), 2)

    return pred, InferNms


def plot_one_box(img, x, color=None, label=None, line_thickness=None):
    # Plots one bounding box on image img
    tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1  # line/font thickness
    color = color or [random.randint(0, 255) for _ in range(3)]
    c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
    cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if label:
        tf = max(tl - 1, 1)  # font thickness
        t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
        c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
        cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA)  # filled
        cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)


if __name__ == '__main__':
    #video_path = 1
    video_path = "UI_rec/testvideo/test.mp4"
    # 初始化视频流
    vs = cv2.VideoCapture(video_path)
    (W, H) = (None, None)
    frameIndex = 0  # 视频帧数

    try:
        prop = cv2.CAP_PROP_FRAME_COUNT
        total = int(vs.get(prop))
        # print("[INFO] 视频总帧数:{}".format(total))
    # 若读取失败,报错退出
    except:
        print("[INFO] could not determine # of frames in video")
        print("[INFO] no approx. completion time can be provided")
        total = -1

    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    ret, frame = vs.read()
    vw = frame.shape[1]
    vh = frame.shape[0]
    print("[INFO] 视频尺寸:{} * {}".format(vw, vh))
    output_video = cv2.VideoWriter("./UI_rec/result/results.avi", fourcc, 20.0, (vw, vh))  # 处理后的视频对象

    # 遍历视频帧进行检测
    while True:
        # 从视频文件中逐帧读取画面
        (grabbed, image) = vs.read()

        # 若grabbed为空,表示视频到达最后一帧,退出
        if not grabbed:
            print("[INFO] 运行结束...")
            output_video.release()
            vs.release()
            exit()
        # 获取画面长宽
        if W is None or H is None:
            (H, W) = image.shape[:2]

        image = cv2.resize(image, (640, 640))
        img0 = image.copy()
        img = letterbox(img0, new_shape=imgsz)[0]
        img = np.stack(img, 0)
        img = img[:, :, ::-1].transpose(2, 0, 1)  # BGR to RGB, to 3x416x416
        img = np.ascontiguousarray(img)

        pred, useTime = predict(img)

        det = pred[0]
        p, s, im0 = None, '', img0
        if det is not None and len(det):  # 如果有检测信息则进入
            det[:, :4] = scale_coords(img.shape[1:], det[:, :4], im0.shape).round()  # 把图像缩放至im0的尺寸
            number_i = 0  # 类别预编号
            detInfo = []
            for *xyxy, conf, cls in reversed(det):  # 遍历检测信息
                c1, c2 = (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3]))
                # 将检测信息添加到字典中
                detInfo.append([names[int(cls)], [c1[0], c1[1], c2[0], c2[1]], '%.2f' % conf])
                number_i += 1  # 编号数+1

                label = '%s %.2f' % (names[int(cls)], conf)

                # 画出检测到的目标物
                plot_one_box(image, xyxy, label=label, color=colors[int(cls)])

        # 实时显示检测画面
        cv2.imshow('Stream', image)
        image = cv2.resize(image, (vw, vh))
        output_video.write(image)  # 保存标记后的视频
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

        # print("FPS:{}".format(int(0.6/(end-start))))
        frameIndex += 1

        # if frameIndex >= total:  # 可设置检测的最大帧数提前退出
        #     print("[INFO] 运行结束...")
        #     output_video.release()
        #     vs.release()
        #     exit()

效果呈现

GUI 界面


主控程序:

# -*- coding: utf-8 -*-
# runMain.py
from sys import argv, exit
#这行代码从sys模块中导入了两个函数:argv和exit。argv是一个命令行参数的列表,当从终端运行Python脚本时,可以通过这个列表访问这些参数。exit函数用于退出Python程序。
from PyQt5.QtWidgets import QApplication
#这行代码从PyQt5的QtWidgets模块中导入了QApplication类。QApplication类管理GUI应用程序的控制流和主要设置,是所有基于Qt的GUI应用程序的起点。
from bboxRecognition import bbox_MainWindow
#这行代码从bboxRecognition模块(可能是一个自定义模块)中导入了一个名为bbox_MainWindow的类。这个类用于识别黑匣子。
if __name__ == '__main__':
#这行代码检查是否是直接运行这个脚本,而不是作为模块导入到其他脚本中。如果是直接运行,那么下面的代码块将被执行
    app = QApplication(argv)
#这行代码创建了一个QApplication实例,它是任何PyQt5应用程序的入口点。argv参数传递给QApplication,这样它就可以处理来自命令行的参数。
    win = bbox_MainWindow()
#这行代码创建了box_MainWindow类的一个实例,这个实例很可能是应用程序的主窗口。
    win.showTime()
#与时间相关的功能。
    exit(app.exec_())
#最后,这行代码启动了应用程序的主事件循环,通过调用exec_()方法。当事件循环开始后,应用程序将等待用户事件并响应他们。exit函数确保应用程序可以干净地退出,返回码将传递给exit,以便操作系统可以知道程序是正常退出还是发生了错误。

简要摘录核心功能代码:

1.启动 GUI 界面

#这段代码定义了一个名为bbox_MainWindow的类,它继承自QBeautyUI。这个类是一个GUI窗口,用于某种目标检测或者视频处理应用程序。
class bbox_MainWindow(QBeautyUI):
    #这行代码调用父类QBeautyUI的构造函数,确保继承的功能得到正确初始化。
    def __init__(self, *args, obj=None, **kwargs):
        super(bbox_MainWindow, self).__init__(*args, **kwargs)

        self.setupUi(self)  # 界面生成
        self.retranslateUi(self)  # 界面控件
        #这两行代码可能调用了在QBeautyUI中定义的方法,用于初始化和设置用户界面。
        self.setUiStyle(window_flag=True, transBack_flag=True)  # 设置界面样式
        #这行代码调用setUiStyle方法来设置窗口的样式,是使窗口透明或者应用其他视觉效果。
        self.path = getcwd()
        self.video_path = getcwd()
        #这里设置了两个路径变量,self.path和self.video_path,它们都被初始化为当前工作目录的路径。
        self.timer_camera = QtCore.QTimer()  # 定时器
        self.timer_video = QtCore.QTimer()  # 视频定时器
        self.flag_timer = ""  # 用于标记正在进行的功能项(视频/摄像)

        self.LoadModel()  # 加载预训练模型
        self.slot_init()  # 定义槽函数,这是Qt中用于处理信号和槽机制的函数,用于响应事件。
        self.files = []  #
        self.cap_video = None  # 视频流对象
        self.CAM_NUM = 0  # 摄像头标号
        self.cap = cv2.VideoCapture(self.CAM_NUM)  # 屏幕画面对象
        #这几行代码初始化了一些变量,包括一个空列表self.files,一个空的视频流对象self.cap_video,一个摄像头编号self.CAM_NUM,以及使用cv2.VideoCapture创建的摄像头视频捕获对象self.cap。
        self.detInfo = []
        self.current_image = []
        self.detected_image = None
        # self.dataset = None
        self.count = 0  # 表格行数,用于记录识别识别条目
        self.res_set = []  # 用于历史结果记录的列表
        self.c_video = 0
        self.count_name = ["黑匣子"]
        self.count_table = []
        #这一部分初始化了更多的变量,包括用于存储检测信息、当前图像、检测到的图像、结果集和其他相关数据的列表和变量。
  1. 读取预训练模型
def LoadModel(self, model_path=None):
        """
        读取预训练模型
        """
        parser = argparse.ArgumentParser()
        parser.add_argument('--weights', nargs='+', type=str, default='../weights/best.pt',
                            help='model.pt path(s)')  # 模型路径仅支持.pt文件
        parser.add_argument('--img-size', type=int, default=480, help='inference size (pixels)')  # 检测图像大小,仅支持480
        parser.add_argument('--conf-thres', type=float, default=0.25, help='object confidence threshold')  # 置信度阈值
        parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS')  # NMS阈值
        # 选中运行机器的GPU或者cpu,有GPU则GPU,没有则cpu,若想仅使用cpu,可以填cpu即可
        parser.add_argument('--device', default='0',
                            help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
        parser.add_argument('--save-dir', type=str, default='inference', help='directory to save results')  # 文件保存路径
        parser.add_argument('--classes', nargs='+', type=int,
                            help='filter by class: --class 0, or --class 0 2 3')  # 分开类别
        parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')  # 使用NMS
        self.opt = parser.parse_args()  # opt局部变量,重要
        out, weight, imgsz = self.opt.save_dir, self.opt.weights, self.opt.img_size  # 得到文件保存路径,文件权重路径,图像尺寸
        self.device = select_device(self.opt.device)  # 检验计算单元,gpu还是cpu
        self.half = self.device.type != '0'  # 如果使用gpu则进行半精度推理
        if model_path:
            weight = model_path
        self.model = attempt_load(weight, map_location=self.device)  # 读取模型
        self.imgsz = check_img_size(imgsz, s=self.model.stride.max())  # 检查图像尺寸
        if self.half:  # 如果是半精度推理
            self.model.half()  # 转换模型的格式
        self.names = self.model.module.names if hasattr(self.model, 'module') else self.model.names  # 得到模型训练的类别名
    """
    这段代码定义了一个名为 `LoadModel` 的方法,它用于加载预训练的机器学习模型,并对模型进行一些初始化设置。这个方法是用于深度学习模型的推理(inference)的准备工作。具体来看,方法的功能包括:
1. **参数解析**:
   使用 `argparse` 库来解析命令行参数。这些参数包括模型权重文件的路径、输入图像的大小、置信度阈值、IOU阈值、计算设备(CPU或GPU)、结果保存目录、类别过滤以及是否使用类别无关的非极大值抑制(NMS)。
2. **设置默认参数**:
   如果在调用方法时没有指定 `model_path`,它会使用默认的参数设置。
3. **选择计算设备**:
   使用 `select_device` 函数(这个函数在代码中没有给出,但可以推断它的功能是选择使用CPU还是GPU进行计算)。
4. **模型加载**:
   使用 `attempt_load` 函数加载模型,这个函数可能是一个自定义函数或者来自某个库,用于加载模型并将其映射到指定的计算设备上。
5. **图像尺寸检查**:
   使用 `check_img_size` 函数确保输入图像的尺寸与模型的步长兼容。
6. **半精度推理**:
   如果计算设备是GPU,则将模型转换为半精度(float16),这样可以加快推理速度并减少内存使用。
7. **类别名称处理**:
   获取模型训练时的类别名称,并且如果这些名称在 `Chinese_name` 字典中有对应的中文名称,就替换为中文名称。
8. **颜色分配**:
   为每个类别分配一个颜色,这通常用于在图像上绘制检测结果。如果类别的数量没有超过预定义的颜色列表 `color`,它会使用这些颜色;否则,会随机生成颜色。
9. **预推理**:
   创建一个零图像张量,并通过模型进行一次前向传递,这通常是为了让模型在实际推理之前“热身”,确保CUDA核心已经加载,从而在后续的推理中能够更快。
这个方法是深度学习模型部署的常见步骤的集合,用于准备模型进行后续的图像处理和对象检测任务。代码中提到的一些函数和变量(如 `select_device`, `attempt_load`, `check_img_size`, `Chinese_name`)没有在这段代码中定义,所以它们的具体实现细节是未知的,但从上下文中可以推测它们的大致功能。

3.融合检测程序-如上面所呈现

后续多模态的融合


后续完成基础可见光条件的训练之后,还要尝试进行多光谱的融合,提高模型的精确度。

20240413 会议纪要


总结


这篇文章随进度更新,用于记录在毕设过程中遇到的心得和问题。

#实用教程