目录

​编辑

1.代码讲解与神经网络基础概念:

2.如何训练模型介绍视频:

(a)模型检测

(1)训练好的模型文件(weight)

(2)检测的目标(摄像头,图片,视频,屏幕)source:

(3)置信度阈值:config-thres

(4)IOU阈值:iou-thres

(5)基于torch.hub模型训练:

(6)训练结果(run):

(b)数据集构建:

(c)模型训练:

weight与data:

最终训练的模型结果:

tensorbroad

(d)Pyside6可视化图像界面:

(e)Gradio搭建Web GUI

效果:

代码:

 3.一些常见的问题:

(1):python: can‘t open file ‘...detect.py‘ : [Errno 2] No such file or directory

(2)在vscode打开时前面是PS而不是(yolov5)自己命名的虚拟环境:

4.边缘检测:


1.代码讲解与神经网络基础概念:

卷积神经网络基础概念与YOLOv5代码讲解_conv网络-CSDN博客

这篇讲了有需求的可以看一下(目前已经整理完成):
1.神经网络的基础知识(组成成分,卷积层,池化层等[包括一些计算])

2.yolov5的网络结构(yolov5 7.0)只是使用大致了解就行,想要修改网络就够,就看写yolov5新论文,学习借鉴下网络模型,再修改数据集,置信度,iou值就好

3.yolov5主要文件detect.py,yolo.py,train.py的运行讲解(具体可以看视频,图片有些杂乱,但每个小的部分都注明了视频时间[选择性看],视频内容讲述相当详细)

4.yolov5的环境配置,一些必要软件的下载(miniconda[没必要使用anconda],pytorch,pycharm,vscode[编译器])pytorch在下载时一定要注意是cpu还是gpu,在虚拟环境下载过一次torch后,在下载一次会自动下载cpu版本[注意:pip install -r requirements,txt【yolov5源码中,此外不要使用yolov5-master版本,使用yolov5 7.0 不然会有些问题】,要改些参数更加适配]推荐使用gpu

5.每个文件里面常见的参数以及含义与作用,具体想实验可以在终端里面对yolov5的实验文件进行实验。修改参数在终端中使用(如果电脑像防止电脑死机,建议将train.py中的resume改为true)

如果只是为了完成毕设等简单需求,上述文章完全能实现所需功能(加上本篇文章)

一些常用的终端指令:
假设我们创立一个名字为yolov5的虚拟环境:

1.conda create -n yolov5 == 3.10               创建python版本为3.10的新环境

2.conda remove -n yolov5 --all                   环境删除

3.pip install gitpython                                  [不一定是这个] 缺少哪个可以直接在终端下载

4.pip list                                                      虚拟环境里面有哪些安装包

5.conda activate yolov5:                          激活虚拟环境

6.pip install -r requirements.txt :                 下载yolov5所需安装包

2.如何训练模型介绍视频:

最近看的一堆视频中,讲的最好的,由浅入深,手把手保姆教学(环境配置一定要看新版的

学习视频:【手把手带你实战YOLOv5-入门篇】YOLOv5 模型检测_哔哩哔哩_bilibili

贴标签与分类:labelImg

详细:小白快速上手 labelimg:新手图像标注详解教程_labelimg软件-CSDN博客

(a)模型检测

除了在终端命令行中输入还能在源代码中直接修改(detect.py中 run)

def run(
    weights=ROOT / "yolov5s.pt",  # model path or triton URL
    source=ROOT / "data/images",  # file/dir/URL/glob/screen/0(webcam)
    data=ROOT / "data/coco128.yaml",  # dataset.yaml path
    imgsz=(640, 640),  # inference size (height, width)
    conf_thres=0.25,  # confidence threshold
    iou_thres=0.45,  # NMS IOU threshold
    max_det=1000,  # maximum detections per image
    device="",  # cuda device, i.e. 0 or 0,1,2,3 or cpu
    view_img=False,  # show results
    save_txt=False,  # save results to *.txt
    save_format=0,  # save boxes coordinates in YOLO format or Pascal-VOC format (0 for YOLO and 1 for Pascal-VOC)
    save_csv=False,  # save results in CSV format
    save_conf=False,  # save confidences in --save-txt labels
    save_crop=False,  # save cropped prediction boxes
    nosave=False,  # do not save images/videos
    classes=None,  # filter by class: --class 0, or --class 0 2 3
    agnostic_nms=False,  # class-agnostic NMS
    augment=False,  # augmented inference
    visualize=False,  # visualize features
    update=False,  # update all models
    project=ROOT / "runs/detect",  # save results to project/name
    name="exp",  # save results to project/name
    exist_ok=False,  # existing project/name ok, do not increment
    line_thickness=3,  # bounding box thickness (pixels)
    hide_labels=False,  # hide labels
    hide_conf=False,  # hide confidences
    half=False,  # use FP16 half-precision inference
    dnn=False,  # use OpenCV DNN for ONNX inference
    vid_stride=1,  # video frame-rate stride
):

终端中显示的是PC而不是(base) (yolov5)在3.常遇到的问题提供解决思路

(1)训练好的模型文件(weight)

输入没有的网络模型,代码能够识别并自行下载

python detect.py -- weights yolov5s.pt

(2)检测的目标(摄像头,图片,视频,屏幕)source:

python detect.py --source data/images/bus.jpg

python detect.py --source screen

在终端按 ctrl+c停止

(3)置信度阈值:config-thres

        置信度阈值是在机器学习和计算机视觉任务中用来衡量模型预测结果可信度的关键参数,它决定了哪些预测被认为是有效的。在目标检测中,这个阈值用于筛选出模型足够自信的预测结果,以减少误报和提高检测的准确性。通过调整置信度阈值,可以在不同应用场景中平衡假阳性和假阴性的数量,从而优化模型的性能和适应特定的需求。

python detect.py --conf-thres 0.45

config-thres值为0.05时

config-thres值为0.45时

(4)IOU阈值:iou-thres

越低框越少,越高框越多

IoU衡量的是预测边界框与真实边界框之间的重叠程度。

定义:IoU是两个边界框交集面积与并集面积的比值,即IoU = 交集面积 / 并集面积。这个值介于0和1之间,值越大表示预测框与真实框的重合程度越高

阈值设置:IoU阈值的设定对于模型性能有重要影响。在NMS中,IoU阈值用于判断两个边界框是否应该被合并或抑制。典型的IoU阈值为0.5,当两个边界框的IoU大于0.5时,认为两个框重叠度较高,可能表示同一个目标;如果IoU小于0.5,则认为两个框重叠度较低,可以保留两个框

python detect.py --iou-thres 0.5

(5)基于torch.hub模型训练:

需要下载jupyter环境,只有vscode能够使用,pycharm无法使用

创建这个后缀的文件(后缀为.ipynb

pip install jupyterlab
import torch

#Model
model = torch.hub.load("./","yolov5s",source="local")

#Images
img = "./data/images/zidane.jpg"

#Inference
results = model(img)

#results
results.show()

运行效果:

(6)训练结果(run):

(b)数据集构建:

import cv2
import matplotlib.pyplot as plt

# 打开视频文件
video = cv2.VideoCapture("./BVN.mp4")
# 读取一帧 ret:反馈true flase frame:保存帧图片
ret, frame = video.read()

#显示OpenCV采集的图片(颜色会有些奇怪)
plt.imshow(frame)

#图片颜色从BGR转化为RGB
plt.imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))

#将转化后的图片存储在images文档里面
video = cv2.VideoCapture("./BVN.mp4")
num = 0         # 计数器
save_step = 30  # 间隔帧
while True:
    ret, frame = video.read()
    if not ret:
        break
    num += 1
    if num % save_step == 0:
        cv2.imwrite("./images/" + str(num) + ".jpg", frame)

control + ·(波浪号)终端快捷键、

下载labelimg

终端激活yolov5环境后,输入:pip install lableimg

open Dir:选择图片文件

Change Save Dir:标注完后的存储的图片

最下面一定要转化为yolo格式

在view中选择auto save (不用手动保存了)

快捷键:

a:上张图片

b:下张图片

w:直接画框

(c)模型训练:

train的图片要比val多

重要的是图片格式一定要是yolo的(在labelimg贴标签时与分类)

weight与data:

weight:可以直接使用yolo提供的yolov5s,yolov5x等

 parser.add_argument("--weights", type=str, default=ROOT / "yolov5s.pt", help="initial weights path")

data:默认为coco128.yaml 自己分类是建议创建一个新的,这里新创建取名为bvn

        所有的路径都是相对路径(相对于整个yolov5-master这个项目的)

parser.add_argument("--data", type=str, default=ROOT / "data/bvn.yaml", help="dataset.yaml path")

bvn.yaml

# Train/val/test sets as 1) dir: path/to/imgs, 2) file: path/to/imgs.txt, or 3) list: [path/to/imgs1, path/to/imgs2, ..]
path: ./datasets # dataset root dir
train: images/train # train images (relative to 'path') 128 images
val: images/val # val images (relative to 'path') 128 images
test: # test images (optional)

# Classes
names:
  0: daitu
  1: mingren

最终训练的模型结果:

位置:

best.pt最好的一轮模型  

last.pt最后一轮的模型

后面detect.py模型路径:

model = torch.hub.load("./","custom",path="runs/train/exp8/weights/best.pt",source="local")

tensorbroad

看模型训练如何:

tensorboard --logdir runs  

ctrl按住点网址

文章讲每个参数是什么很详细

TensorBoard最全使用教程:看这篇就够了-CSDN博客

(d)Pyside6可视化图像界面:

效果:

视频因为版本问题,会与下文代码有些不同

python版本要大于3.8.0(不能等于)

视频教学:【手把手带你实战YOLOv5-入门篇】YOLOv5 Pyside6可视化界面_哔哩哔哩_bilibili

重要文件:

main_window_ui.py

main_window_ui

自己创建的base_ui

代码:

pyside6下载

pip install pyside6

Qt Creater界面:

界面运行代码(base_ui.py中[自己创建])

注意模型与图像,视频路径

  • convert2QImage(img)

    • 参数:img(numpy数组),OpenCV图像格式。
    • 返回:QImage对象,适合在PySide6中显示。
    • 用法:将图像数据转换为QImage格式。
  • image_pred(self, file_path)

    • 参数:file_path(字符串),图像文件的路径。
    • 返回:QImage对象,模型预测后的图像。
    • 用法:加载图像文件,使用PyTorch模型进行预测,并返回预测后的图像。
  • open_image(self)

    • 无参数。
    • 用法:打开文件对话框,让用户选择图像文件,显示原始图像,并显示模型预测后的图像。
  • open_video(self)

    • 无参数。
    • 用法:打开文件对话框,让用户选择视频文件,开始播放视频,并显示模型预测后的帧。
  • video_pred(self)

    • 无参数。
    • 用法:从视频流中读取一帧,进行预测,并更新GUI中的显示。
  • bind_slots(self)

    • 无参数。
    • 用法:将GUI中的按钮事件(如点击)绑定到相应的处理函数

基于PySide6的图形用户界面应用程序,集成了PyTorch和OpenCV库来处理图像和视频。程序首先导入必要的库,包括系统库、PyTorch、OpenCV和PySide6的GUI组件。接着,定义了一个convert2QImage函数,用于将OpenCV的BGR格式图像转换为PySide6的QImage格式,以便在GUI中显示。程序定义了一个MainWindow类,它继承自QMainWindow和由Qt Designer生成的Ui_MainWindow,后者包含了界面布局。在MainWindow类的__init__方法中,程序初始化界面、加载PyTorch模型、设置定时器,并绑定事件。image_pred方法接受一个文件路径,使用PyTorch模型进行预测,并返回转换后的QImage对象。open_image方法允许用户通过文件对话框选择图像文件,然后使用image_pred方法进行预测,并在GUI中显示原始和预测后的图像。open_video方法允许用户选择视频文件,然后使用OpenCV打开视频,并启动定时器以定期获取视频帧。video_pred方法定期从视频中读取一帧,将其转换为RGB格式,使用PyTorch模型进行预测,并在GUI中显示预测后的帧。bind_slots方法将GUI中的按钮点击事件绑定到相应的处理函数。最后,在主函数中,程序创建应用程序实例、主窗口实例,并启动事件循环,以运行整个GUI应用程序

改变视频流畅度:修改self.timer.setInterval()

import sys
import torch
import cv2
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton,QFileDialog
from PySide6.QtGui import QPixmap,QImage
from PySide6.QtCore import QTimer


from main_window_ui import Ui_MainWindow

def convert2QImage(img):
    height,width,channel = img.shape
    return QImage(img,width,height,width*channel,QImage.Format_RGB888)

class MainWindow(QMainWindow,Ui_MainWindow):
    def __init__(self):
        super( MainWindow,self).__init__()
        self.setupUi(self)
        #选择detect.py所需的模型路径,一般是train后的模型,文件路径为相对路径
        self.model = torch.hub.load("./","custom",path="runs/train/exp8/weights/best.pt",source="local")
        self.timer=QTimer()
        #修改视频流畅度,单位毫秒
        self.timer.setInterval(1)
        self.video = None
        self.bind_slots()
        
    def image_pred(self,file_path):
        results = self.model(file_path)
        image=results.render()[0]
        return convert2QImage(image)

    def open_image(self):
        self.timer.stop()
        file_path= QFileDialog.getOpenFileName(self,dir = "./datasets/images/train",filter ="*.jpg;*.png;*.jpeg")
        if file_path[0]:
            file_path = file_path[0]
            qimage = self.image_pred(file_path)
            self.input.setPixmap(QPixmap(file_path))
            self.output.setPixmap(QPixmap.fromImage(qimage))

    def open_video(self):
        file_path= QFileDialog.getOpenFileName(self,dir = "./datasets",filter ="*.MP4")
        if file_path[0]:
            file_path = file_path[0]
            self.video = cv2.VideoCapture(file_path)
            self.timer.start()

                
    def video_pred(self):
        ret,frame = self.video.read()
        if not ret:
                self.timer.stop()
        else:
            frame =cv2.cvtColor(frame,cv2.COLOR_BGR2RGB)
            self.input.setPixmap(QPixmap.fromImage(convert2QImage(frame)))
            results = self.model(frame)
            image=results.render()[0]
            self.output.setPixmap(QPixmap.fromImage(convert2QImage(image)))
       

    def bind_slots(self):
        self.detect_image.clicked.connect(self.open_image)
        self.detect_vedio.clicked.connect(self.open_video)
        self.timer.timeout.connect(self.video_pred)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec()

(e)Gradio搭建Web GUI

vscode下载插件:

运行后:

效果:

通过调节conf与iou的值能够得到想要的效果,得到较好的阈值

下面是两组不同参数的对比

代码:

如果想调用的是电脑摄像头修改为:

gr.Interface(inputs=[gr.Webcam(), gr.Slider(minimum=0, maximum=1, value=base_conf

import torch
import gradio as gr

model = torch.hub.load("./", "custom", path="runs/train/exp8/weights/best.pt", source="local")

title = "基于Gradio的YOLOv5演示项目"

desc = "这是一个基于Gradio的YOLOv5演示项目,非常简洁,非常方便!"

base_conf, base_iou = 0.25, 0.45

def det_image(img, conf_thres, iou_thres):
    model.conf = conf_thres
    model.iou = iou_thres
    return model(img).render()[0]

gr.Interface(inputs=["image", gr.Slider(minimum=0, maximum=1, value=base_conf), gr.Slider(minimum=0, maximum=1, value=base_iou)], 
             outputs=["image"], 
             fn=det_image,
             title=title,
             description=desc,
             live=True, 
             examples=[["./datasets/images/train/30.jpg", base_conf, base_iou], ["./datasets/images/train/60.jpg", 0.3, base_iou]]).launch(share=True)

 3.一些常见的问题:

(1):python: can‘t open file ‘...detect.py‘ : [Errno 2] No such file or directory

python: can‘t open file ‘...detect.py‘ : [Errno 2] No such file or directory_can't open file 'detect.py': [errno 2] no such fil-CSDN博客

亲测有效

(2)在vscode打开时前面是PS而不是(yolov5)自己命名的虚拟环境:

#默认配置为PS    
PS:C:User

#目标是(base)与(yolov5)[自定义虚拟环境名称]
(base) C:User
(yolov5) C:User

如何检测是否能够运行:
在(yolov5) C:User.....:python detect.py测试一下

终端中最右上角,选择command Prompt

之前:

之后:

一个是python,另外一个是cmd:

同时可以选择默认配置文件,设置conmmand prompt

4.边缘检测:

边缘检测是图像处理和计算机视觉中的一个基础任务,目的是识别图像中不同区域之间的显著变化,这些变化通常与颜色、亮度和表面特性的突变相关。边缘是图像中物体轮廓的体现,用于区分图像中的不同物体。图像特征的检测是图像处理流水线中的第一步,也是后续特征描述、匹配等步骤的基础。边缘检测的本质在于梯度分析,即通过计算图像的梯度(灰度值的变化率)来检测图像中的边缘。

边缘检测的种类

边缘检测的方法主要可以分为以下几种:

  1. 微分算子法:通过计算图像的一阶或二阶导数来确定边缘的位置,常见的算子包括Sobel、Prewitt、Roberts等
  2. 最优算子法:以Canny算子为代表,它通过高斯滤波、梯度计算、非极大值抑制和双阈值处理等步骤来检测边缘。8.Canny边缘检测原理_哔哩哔哩_bilibili
  3. 全局提取方法:以能量最小化为准则,从全局最优的角度提取边缘,如松驰法

主要使用了opencv来完成

# YOLOv5 🚀 by Ultralytics, GPL-3.0 license

import os
import sys
import numpy as np
from pathlib import Path
import time
import torch
import serial
from time import sleep
import binascii
import serial.tools.list_ports
FILE = Path(__file__).resolve()  # 获取文件绝对路径 D:\study2\Yolov5\yolov5-7.0
ROOT = FILE.parents[0]  # 获取文件绝对路径的上一级 D:\study2\Yolov5
if str(ROOT) not in sys.path:  # 模块的查询路径的列表
    sys.path.append(str(ROOT))  # add ROOT to PATH
ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # relative  将绝对路径转换为相对路径

from models.common import DetectMultiBackend
from utils.general import (LOGGER, Profile, check_file, check_img_size, check_imshow, check_requirements, colorstr, cv2,
                           increment_path, non_max_suppression, print_args, scale_boxes, strip_optimizer, xyxy2xywh)
from utils.plots import Annotator, colors, save_one_box
from utils.torch_utils import select_device, smart_inference_mode

stringList = ["毛球", "山楂片", "洗面奶", "螺丝刀", "补水乳", "U盘", "电池"]
ports = serial.tools.list_ports.comports()
if ports:
    print("以下是可用的串口列表:")
    for i, port in enumerate(ports):
        # print(f"{i+1}. {port}")
        if port.description[0:16] == "USB-SERIAL CH340":
            s = serial.Serial(port.device, 115200)
            print("已连接:" + port.description)
            break
else:
    print("没有检测到可用的串口。")

# 存放物品信息的类
class object:
    myX=0
    myY=0
    myLabel = " "
    myAngle = 0

    def __init__(self,x,y,label,angle):
        # stringList = ["mao","xi","shan","luo","u","dian","shui"]
        self.myX = x
        self.myY = y
        # self.myLabel = stringList.index(label) if label in stringList else 100
        self.myLabel = label
        self.myAngle = angle

# ff 00  0000  0000  00  0000  5b
# 包头   x坐标  y坐标  种类  角度 包尾
#向stm32发送数据的函数
def write_data(
        x = 0, # 物品中心点x坐标
        y = 0, # 物品中心点y坐标
        label = 0, # 物品种类
        angle = 0, # 物品偏转角度
):
    # 转换x, y, 和 angle为字节(两个字节,大端格式)
    x_bytes = x.to_bytes(2, byteorder='big')
    y_bytes = y.to_bytes(2, byteorder='big')
    angle_bytes = int(angle).to_bytes(2, byteorder='big')
    label_bytes = label.to_bytes(1, byteorder='big')  # 假设标签是一个字节
    head = b'\xff' + b'\x00'
    end = b'\x5b' + b'\r\n'
    s.write(head + x_bytes + y_bytes + label_bytes + angle_bytes + end)
    s.flushOutput()

# 识别模型初始化函数
def init(
        weights=ROOT / 'runs/train/exp8/weights/best.pt',  # 识别模型的默认权重文件
        data=ROOT / 'det_sample/data.yaml',  # 识别模型的默认数据集 data.yaml
        device='',  # cuda device, i.e. 0 or 0,1,2,3 or cpu 默认调用GPU加速
        imgsz=(480, 640),  # inference size (height, width) 识别时图片的大小
        half=False,  # use FP16 half-precision inference 是否使用16位半精度推理
        dnn=False,  # use OpenCV DNN for ONNX inference 是否使用Opencv DNN进行ONNX推理
):
    # Load model
    device = select_device(device)      # 选择是否用GPU
    model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data, fp16=half)   # 选择模型的后端框架(选择pytorch框架)
    stride, names, pt = model.stride, model.names, model.pt  # 模型的步长(一般为32) 类别名 是否为基于pytorch的模型
    imgsz = check_img_size(imgsz, s=stride)  # check image size  若不满足倍数关系则重新计算一个
    # Run inference
    bs = 1  # batch_size    识别时每次输入一张图片
    model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz))  # warmup 空白图热身
    return model    # 返回识别模型

def recv(serial):
    while True:
        data = serial.read_all().hex()
        if data == '':
            continue
        else:
            break
        sleep(0.02)
    return data

# 对输入的图片进行目标检测
def find_where(im,          # resize后的图片
               model,       # 模型
               im0s,        # 原图
               conf_thres = 0.25,  # 置信度阈值
               iou_thres = 0.45,   # NMS IOU threshold
               max_det=100, # 每张图的最大目标数
               line_thickness=2,  # bounding box thickness (pixels) 检测框的线宽
               objects_list=[],
               ):
    im = torch.from_numpy(im).to(model.device)  # 转为pytorch支持的格式 torch.Size([1,3, , ])
    im = im.half() if model.fp16 else im.float()  # uint8 to fp16/32 是否使用半精度
    im /= 255  # 0 - 255 to 0.0 - 1.0 归一化
    if len(im.shape) == 3:  # 若图像为3维,则扩充到4维
        im = im[None]  # expand for batch dim
    # Inference      augment是否数据增强
    pred = model(im, augment=False, visualize=False)  # 得到的检测框

    # NMS 非极大值过滤   根据conf_thres和iou_thres筛选检测框
    pred = non_max_suppression(pred, conf_thres, iou_thres,
                               max_det=max_det)  # 1,5(目标数),6. [前四个检测框的坐标、置信度、类别]
    obj_num = len(pred[0])
    # process predictions
    for i, det in enumerate(pred):  # 遍历一个batch中的所有图片  det:检测框信息
        im0, frame = im0s.copy(), 0
        # 定义一个绘图工具用于绘制检测框
        annotator = Annotator(im0, line_width=line_thickness, example=str(model.names))  # 绘制预测框

        if len(det):  # 判断有无框
            # Rescale boxes from img_size to im0 size
            det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round()  # 将输入到模型的图片框坐标映射到原图上
            # 画出检测框
            for *xyxy, conf, cls in reversed(det):
                c = int(cls)  # integer class 获取当前类别
                label = f'{model.names[c]} {conf:.2f}'
                annotator.box_label(xyxy, label, color=colors(c, True))
                if (c == 0):
                    obj_num = obj_num - 1
                    objects_list.append(object(int((xyxy[0]+xyxy[2])/2), int((xyxy[1]+xyxy[3])/2), c, 90))
                else:
                    objects_list.append(object(int((xyxy[0] + xyxy[2]) / 2), int((xyxy[1] + xyxy[3]) / 2), c, 0))

        im0 = annotator.result()    # 获取画有检测框的图片
        cv2.imshow('result', im0)   # 显示画有检测框的图片
        key = cv2.waitKey(1)
        return obj_num

def find_angle(im0s,objects_list,obj_num=0):
    gray = cv2.cvtColor(im0s, cv2.COLOR_BGR2GRAY)
    # 高斯滤波   (要处理的图像,卷积核的大小,sigma) 用于处理高斯噪点
    gray = cv2.GaussianBlur(gray, (7, 7), 0)
    # Canny 边缘计算  (要处理的图像,边界阈值下限,边界阈值上限)
    binary = cv2.Canny(gray, 30, 120)
    # 轮廓检索
    contours, hierarchy = cv2.findContours(binary,
                                           cv2.RETR_EXTERNAL,
                                           cv2.CHAIN_APPROX_SIMPLE)

    cv2.imshow('origin', im0s)
    cv2.imshow('binary', binary)
    # 轮廓过滤以及绘制
    draw_img = im0s.copy()
    num = 0;
    for i in range(len(contours)):
        # 筛掉面积过小的轮廓
        area = cv2.contourArea(contours[i])
        if area < 2000:
            continue
        # 找到包含轮廓的最小矩形框
        rect = cv2.minAreaRect(contours[i])
        # print(rect)
        # 计算矩形框的四个顶点坐标
        box = cv2.boxPoints(rect)
        box = np.intp(box)  # 取整
        x_min = min(box[0][0], box[3][0])
        x_max = max(box[1][0], box[2][0])
        y_min = min(box[0][1], box[1][1])
        y_max = max(box[2][1], box[3][1])
        num = num+1;
        # 绘制轮廓
        cv2.drawContours(draw_img, [box], 0, (0, 0, 255), 5)
        for obj in objects_list:
            if obj.myX>=x_min and obj.myX<=x_max and obj.myY>=y_min and obj.myY<=y_max :
                if (box[0][1] - box[3][1])*(box[0][1] - box[3][1]) + (box[0][0] - box[3][0])*(box[0][0] - box[3][0]) < (box[1][1] - box[0][1])*(box[1][1] - box[0][1])+(box[1][0] - box[0][0])*(box[1][0] - box[0][0]):
                    obj.myAngle = rect[2] + 90
                else:
                    obj.myAngle = rect[2]
        # 打印角度
        # print(rect[2])
    cv2.imshow('origin with contours', draw_img)
    key = cv2.waitKey(1)
    if( num == obj_num ):
        return objects_list,True
    else:
        return objects_list,False

def main():
    # 检测requirements文件中的依赖包是否存在
    check_requirements(exclude=('tensorboard', 'thop'))
    # 获取识别模型
    model = init()
    # 获取摄像头 0为电脑自带摄像头 1为普通外界摄像头
    cap = cv2.VideoCapture(1)
    while cap.isOpened():   # 若摄像头开启则一直循环以下代码
        start_time = time.time()    # 获取识别前的时间(用于帧率计算)
        objects_list = []
        ret, im0s = cap.read()  # 读取摄像头信息 返回值 第一个:读取状态 true:读到  false:未读到   第二个:视频帧数据
        if ret == True:  # 如果读到图片
            shape = im0s.shape[:2]  # 获取图片的宽和高
            im = im0s.transpose((2, 0, 1))[::-1]  # HWC to CHW ,BGR to RGB 将图片格式变为(3,640,640)
            im = np.ascontiguousarray(im)  # 将不连续的数组转换为连续的数组,使得数组数据在内存中连续存储。
            # 检测目标位置
            obj_num = find_where(im, model, im0s,objects_list=objects_list)
            objects_list,if_find = find_angle(im0s, objects_list,obj_num)
            objects_list.sort(key=lambda objects_list: objects_list.myX,)
            if len(objects_list) and if_find:
                for obj in objects_list:
                    if obj.myAngle:
                        write_data(obj.myX, obj.myY, obj.myLabel, obj.myAngle )
                    elif obj.myLabel == 0 :
                        write_data(obj.myX, obj.myY, obj.myLabel, obj.myAngle )
                    if obj.myAngle:
                        print("x = ", obj.myX, " y = ", obj.myY, " label = ", obj.myLabel, ":", stringList[obj.myLabel], " angle = ", obj.myAngle )
                    elif obj.myLabel == 0 :
                        print("x = ", obj.myX, " y = ", obj.myY, " label = ", obj.myLabel, ":", stringList[obj.myLabel], " angle = ", obj.myAngle)
        end_time = time.time()  # 获取一次检测结束的时间
        FPS = 1/(end_time-start_time)   # 获取检测帧率
        print(FPS)

if __name__ == "__main__":
    main()

边缘检测主要部分:

def find_angle(im0s, objects_list, obj_num=0):
    gray = cv2.cvtColor(im0s, cv2.COLOR_BGR2GRAY)

将彩色图转化为灰色图
    gray = cv2.GaussianBlur(gray, (7, 7), 0)

高斯滤波降噪
    binary = cv2.Canny(gray, 30, 120)

Canny算法:

       通过高斯滤波降噪、梯度计算、非极大值抑制、双阈值检测和边缘跟踪等步骤,有效地从图像中识别出物体的轮廓。该算法首先利用高斯滤波器减少图像噪声,然后计算图像的梯度幅度和方向,通过非极大值抑制细化边缘,接着应用双阈值策略区分强边缘和弱边缘,并最终通过滞后过程连接边缘片段,以确定和优化边缘的位置。因其出色的边缘检测性能和对噪声的鲁棒性

  image:输入图像,必须是单通道的8位图像(即灰度图)。gray:表示输入的灰度图像,它是通过cv2.cvtColor函数将原始图像转换为灰度图,然后可能经过高斯模糊处理得到的。

  threshold1:第一个阈值,用于Canny边缘检测的双阈值过程。threshold1通常设置得较低,用于边缘的初步检测。

低阈值(第二个值):

     初步边缘检测:低阈值用于初步识别可能的边缘像素。在Canny算法的非极大值抑制步骤之后,梯度幅度超过低阈值的像素被初步认为是边缘像素。

     减少误检:通过设置一个较低的阈值,可以减少漏检(false negatives),即减少未能检测到真实边缘的情况。但这也可能导致更多的误检(false positives),即错误地将非边缘像素识别为边缘。

  threshold2:第二个阈值,用于确定强边缘。threshold2通常设置得较高,表示那些确定无疑的边缘。

高阈值(第三个值):

     确定强边缘:高阈值用于确定强边缘像素。只有当梯度幅度超过高阈值的像素才被最终确定为强边缘像素。

     抑制误检:通过设置一个较高的阈值,可以减少误检,即减少将非边缘像素错误识别为边缘的情况,但这也可能导致漏检,即错过一些实际的边缘像素。

梯度幅度介于低阈值和高阈值之间的像素被视为不确定的边缘像素。这些像素将进入下一步处理,即边缘连接阶段:

边缘连接(滞后检测)强边缘连接:如果不确定的边缘像素与已经确定的强边缘像素(梯度幅度超过高阈值的像素)相连,则这些不确定的边缘像素也会被归为边缘像素。这一步骤通过滞后过程(hysteresis)实现,即通过检查每个像素的梯度值,并将其与已确定的边缘像素进行比较,以确定是否将这些像素包含在最终的边缘图中。

 

  cv2.Canny函数执行后,会返回一个二值图像binary,其中边缘被标记为白色(255),非边缘区域为黑色(0)


    contours, hierarchy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

输入:

     binary:输入的二值图像,其中边缘像素被标记为白色(非零值),非边缘像素被标记为黑色(零值)。

     cv2.RETR_EXTERNAL:这是检索轮廓的方法。cv2.RETR_EXTERNAL 表示只检索最外层的轮廓,忽略任何内层的轮廓。OpenCV 提供了几种不同的轮廓检索模式:

                  cv2.RETR_EXTERNAL:只检索最外层的轮廓。
                  cv2.RETR_LIST:检索所有轮廓,但不建立任何父子关系。
                  cv2.RETR_TREE:检索所有轮廓,并重建完整的轮廓树,包括轮廓之间的层次                                                    结构。
                  cv2.CHAIN_APPROX_SIMPLE:这是轮廓近似方法,用于压缩轮廓,减少轮廓                                                  点的数量。

                  cv2.CHAIN_APPROX_SIMPLE :只保留轮廓的拐点,即方向变化最大的点,忽                                                    略中间的点。这可以显著减少轮廓数据的大小,同时保留轮廓                                                  的基本形状。其他近似方法包括:

                  cv2.CHAIN_APPROX_NONE:保留轮廓上的所有点。
                  cv2.CHAIN_APPROX_TC89_L1、cv2.CHAIN_APPROX_TC89_KCOS:使用                                                    不同的算法进行轮廓近似,这些方法考虑了轮廓的曲率。
输出:

        contours:一个 Python 列表,其中包含了检测到的轮廓。每个轮廓都是一个点集(numpy 数组),这些点定义了轮廓的边界。
        hierarchy:这是一个与 contours 中的每个轮廓相关联的层次结构信息的数组。它提供了轮廓之间的父子关系,例如哪些轮廓是哪些轮廓的内层轮廓。如果只需要轮廓而不关心层次结构,这个输出可以忽略。

    cv2.imshow('origin', im0s)
    cv2.imshow('binary', binary)
    draw_img = im0s.copy()
    num = 0
    for i in range(len(contours)):
        area = cv2.contourArea(contours[i])

计算当前轮廓的面积
        if area < 2000:
            continue

如果轮廓的面积小于2000个像素点,则跳过当前轮廓,不对其进行进一步处理
        rect = cv2.minAreaRect(contours[i])

计算能够包含当前轮廓的最小外接矩形。这个矩形可能不是轴对齐的
        box = cv2.boxPoints(rect)

获取rect矩形的四个顶点坐标
        box = np.intp(box)

将顶点坐标转换为整数
        x_min = min(box[0][0], box[3][0])
        x_max = max(box[1][0], box[2][0])
        y_min = min(box[0][1], box[1][1])
        y_max = max(box[2][1], box[3][1])
        num = num + 1
        cv2.drawContours(draw_img, [box], 0, (0, 0, 255), 5)

draw_img图像上绘制轮廓框,颜色为红色,线宽为5。
        for obj in objects_list:
            if obj.myX >= x_min and obj.myX <= x_max and obj.myY >= y_min and obj.myY <= y_max:

检查物体对象的中心点是否在当前处理的轮廓矩形框内
                if (box[0][1] - box[3][1]) * (box[0][1] - box[3][1]) + (box[0][0] - box[3][0]) * (box[0][0] - box[3][0]) < (box[1][1] - box[0][1]) * (box[1][1] - box[0][1]) + (box[1][0] - box[0][0]) * (box[1][0] - box[0][0]):
                    obj.myAngle = rect[2] + 90
                else:
                    obj.myAngle = rect[2]

判断矩形框的长轴和短轴,根据长轴的方向来确定物体的角度,并将其存储在物体对象的myAngle属性中。这里通过比较对角线上的点的距离来确定长轴的方向。如果对角线的方向是顺时针方向(从左上到右下),则角度为rect[2] + 90;否则,角度为rect[2]

通过对角度的计算能够算作一新的类型,对于对象的种类识别与定位能够更加准确。
    cv2.imshow('origin with contours', draw_img)
    key = cv2.waitKey(1)
    if num == obj_num:
        return objects_list, True
    else:
        return objects_list, False

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐