基于树莓派4B的YOLOv5-Lite目标检测的移植与部署(含训练教程)

news2024/11/15 10:05:46

前言:本文为手把手教学树莓派4B项目——YOLOv5-Lite目标检测,本次项目采用树莓派4B(Cortex-A72)作为核心 CPU 进行部署。该篇博客算是深度学习理论的初步实战,选择的网络模型为 YOLOv5 模型的变种 YOLOv5-Lite 模型。YOLOv5-Lite YOLOv5 相比虽然牺牲了部分网络模型精度,但是缺极大的提升了模型的推理速度,该模型属性将更适合实战部署使用。该项目的实践将帮助大家成功进入 “嵌入式AI” 领域,后续将在该项目上加入嵌入式的 “传统控制” 属性,读者朋友可以期待一下!(文末有代码开源!

硬件实物图:

效果图:

一、YOLOv5-Lite概述

1.1 YOLOv5概述

YOLOv5 网络模型算是 YOLO 系列迭代后特别经典的一代网络模型,作者为:Glenn Jocher。部分学者可能认为YOlOv5创新性不足,其是否称得上 YOLOv5 而议论纷纷。作者认为 YOLOv5 可以算是对 YOLO 系列之前的一次集大成者的总结突破,其属于非常优秀经典的网络模型框架,各种网络结构trick 是非常值得借鉴的!

代码地址:ultralytics/yolov5: YOLOv5 🚀 in PyTorch > ONNX > CoreML > TFLite (github.com)

Yolov5 官方代码中,给出的目标检测网络中一共有4个版本,分别是Yolov5sYolov5mYolov5lYolov5x四个模型。作者仅以 Yolov5s 的网络结构为对象进行讲解,其他版本的读者朋友可以参考其他博客!

Yolov5s 网络是 Yolov5 系列中深度最小,特征图的宽度最小的网络。后面的3种都是在此基础上不断加深,不断加宽。Yolov5 的网络结构图如下(源于江大白大佬的结构图):

上图即 Yolov5 的网络结构图,可以看出,还是分为输入端、Backbone、Neck、Prediction四个部分。

(1)输入端:Mosaic数据增强、自适应锚框计算、自适应图片缩放
(2)Backbone:Focus结构,CSP结构
(3)Neck:FPN+PAN结构
(4)Prediction:GIOU_Loss

上述四部分都是属于如今很常见的模块与Trick了,受限于博客篇幅,各部分的详解就不与读者朋友好好分析和交流了。建议对 YOLO 系列陌生的朋友可以去好好看看其他博主的博客亦或是去B站看视频教学!

下面丢上 Yolov5 作者的算法性能测试图:

到现在为止,Yolov5 已经更新迭代到 v7.0 版本了,科研学术圈以 Yolov5 为基础框架进行魔改的论文数不胜数。通过上述作者的概述读者朋友可能对 Yolov5 有了一个大致的了解,不难发现 Yolov5 是非常优秀的神经网络模型。

可考虑到实际情况,部署的本地机器通常并没有 PC 端那么计算能力强劲。这时候为了整体目标检测系统的稳定运行,往往需要牺牲掉网络模型的精度以换取足够快的检测速度。因此,轻量化部署的网络模型结构就此孕育而生!

1.2 YOLOv5-Lite详解

Yolov5-Lite 网络模型作为轻量化部署网络的代表作之一,深受算法部署工程师的偏爱(作者为中国ppogg大佬)!

Yolov5-Lite地址:GitHub - ppogg/YOLOv5-Lite: 🍅🍅🍅YOLOv5-Lite: lighter, faster and easier to deploy. Evolved from yolov5 and the size of model is only 930+kb (int8) and 1.7M (fp16). It can reach 10+ FPS on the Raspberry Pi 4B when the input size is 320×320~

Yolov5-Lite 算法的模型结构如图下。该算法去除了 Focus 结构层,减小了模型体量,使模型变得更为轻便;同时,去除了 4slice 操作,减少了对计算机芯片缓存的占用,降低了计算机的处理负担。与 Yolov5 算法相比,Yolov5-Lite 算法能避免反复使用 C3 Layer 模块。C3 Layer 模块会占用计算机很多运行空间,从而降低计算机的处理速度。这种方式能使 Yolov5-Lite 算法模型的精度控制在可靠范围内,从而使其更易部署。

在图像识别领域,主干网络结构(Backbone)和检测头(Head)中往往有一段中间层,即特征增强融合网络层(Neck),可更精准地提取融合特征。Yolov5-Lite 算法也采用 FPN+PAN 结构,但其对输出端(Head)进行了通道剪枝,改进了 YOLOv4 算法和 YOLOv5 算法中的 FPN+PAN 的结构,具体表现在以下2个方面:

(1)、YOLOv5 算法不同,Yolov5-Lite 算法自身各结构的通道数量相同,即模型特征增强网络通道网格数也是20×20×96,这样可优化对内存的访问和使用,提高模型的运行效率;

(2)、Yolov5-Lite 算法采用 PANet 结构,将 YOLOv5 算法的通道连接(cat)操作改进为叠加操作,这样可进一步优化对内存的使用,加快处理速度。如下图为 Yolov5-Lite 算法与 YOLOv4 和  YOLOv5 算法中的 PAN 结构对比:

作者总结:

Yolov5-Lite 网络模型源于 YOLOv5 模型,随处可以可见 YOLOv5 网络模型的影子。但是,出于移植部署的目的性,Yolov5-Lite 网络将工作重心放在如何进行快速推理,如何轻量化网络模型大小!

Yolov5-Lite 网络模型不仅通过直接改变网络模型的结构,偏向多使用计算量小的网络模型结构去提取和融合目标特征,同时侧重运用计算机的运行机制:通过降低计算机内存的存储和读取去变相提高网络推理速度!!!

作者这里仅对 Yolov5-Lite 做初步概述,详情读者朋友可以参考学术论文!

二、YOLOv5-Lite训练

2.1 数据集制作

★常规的神经网络模型训练是需要收集到大量语义丰富的数据集进行训练的。但是考虑实际工程下可能仅需要对已知场地且固定实物进行目标检测追踪等任务,这个时候我们可以采取偷懒的下方作者使用的方法!

1、作者使用树莓派4B的 Camera 直接在捕获需要识别目标物的图片信息(捕获期间转动待识别的目标物体);

树莓派4B的 Camera 定时捕获照片的python代码如下:

import cv2
from threading import Thread
import uuid
import os
import time
count = 0
def image_collect(cap):
    global count
    while True:
        success, img = cap.read()
        if success:
            file_name = str(uuid.uuid4())+'.jpg'
            cv2.imwrite(os.path.join('images',file_name),img)
            count = count+1
            print("save %d %s"%(count,file_name))
        time.sleep(0.4)

if __name__ == "__main__":
    
    os.makedirs("images",exist_ok=True)
    
    # 打开摄像头
    cap = cv2.VideoCapture(0)

    m_thread = Thread(target=image_collect, args=([cap]),daemon=True)
    
    while True:

        # 读取一帧图像

        success, img = cap.read()

        if not success:

            continue

        cv2.imshow("video",img)

        key =  cv2.waitKey(1) & 0xFF   

        # 按键 "q" 退出
        if key ==  ord('c'):
            m_thread.start()
            continue
        elif key ==  ord('q'):
            break

    cap.release() 

按动 “c” 开始采集待识别目标图像,按动 “q” 退出摄像头 Camera 的图片采集;

2、将捕获到的待识别目标物照片传输到PC端,利用 Labelme 软件进行标注(Labelme不会使用的建议相关博客);

作者的标注了 3 类目标:drug,prime,glue;读者朋友可以根据自己实际情况标注自己需要的数据集!由于我们标注的数据的标签 label 默认是 JSON 格式的不能被 YOLO 系列的神经网络模型直接进行利用训练。

3、使用 JSON 转 txt 的 YOLO 格式 label 的python代码进行转换(可以直接使用作者提供的代码):

dic_lab.py:

dic_labels= {'drug':0,
            'glue':1,
            'prime':2,
             'path_json':'labels',
             'ratio':0.9}

lablemetoyolo.py:

import os
import json
import random
import base64
import shutil
import argparse
from pathlib import Path
from glob import glob
from dic_lab import dic_labels

def generate_labels(dic_labs):
    path_input_json = dic_labels['path_json']
    ratio = dic_labs['ratio']
    for index, labelme_annotation_path in enumerate(glob(f'{path_input_json}/*.json')):

        # 读取文件名
        image_id = os.path.basename(labelme_annotation_path).rstrip('.json')
        
        # 计算是train 还是 valid
        train_or_valid = 'train' if random.random() < ratio else 'valid'

        # 读取labelme格式的json文件
        labelme_annotation_file = open(labelme_annotation_path, 'r')
        labelme_annotation = json.load(labelme_annotation_file)

        # yolo 格式的 lables
        yolo_annotation_path = os.path.join(train_or_valid, 'labels',image_id + '.txt')
        yolo_annotation_file = open(yolo_annotation_path, 'w')
        
        # yolo 格式的图像保存
        yolo_image = base64.decodebytes(labelme_annotation['imageData'].encode())
        yolo_image_path = os.path.join(train_or_valid, 'images', image_id + '.jpg')
        
        yolo_image_file = open(yolo_image_path, 'wb')
        yolo_image_file.write(yolo_image)
        yolo_image_file.close()
     

        # 获取位置信息
        for shape in labelme_annotation['shapes']:
            if shape['shape_type'] != 'rectangle':
                print(
                    f'Invalid type `{shape["shape_type"]}` in annotation `annotation_path`')
                continue
           

            points = shape['points']
            scale_width = 1.0 / labelme_annotation['imageWidth']
            scale_height = 1.0 / labelme_annotation['imageHeight']
            width = (points[1][0] - points[0][0]) * scale_width
            height = (points[1][1] - points[0][1]) * scale_height
            x = ((points[1][0] + points[0][0]) / 2) * scale_width
            y = ((points[1][1] + points[0][1]) / 2) * scale_height
            object_class = dic_labels[shape['label']]
            yolo_annotation_file.write(f'{object_class} {x} {y} {width} {height}\n')
        yolo_annotation_file.close()
        print("creat lab %d : %s"%(index,image_id))


if __name__ == "__main__":
    os.makedirs(os.path.join("train",'images'),exist_ok=True)
    os.makedirs(os.path.join("train",'labels'),exist_ok=True)
    os.makedirs(os.path.join("valid",'images'),exist_ok=True)
    os.makedirs(os.path.join("valid",'labels'),exist_ok=True)
    generate_labels(dic_labels)

我们需要根据自己的需要自定义字典 dic_lab,字典中的 ratio = 0.9 的作用是将数据集拆分成训练集和验证集 9:1。读者朋友可以根据自己的实际情况去修改字典的标签内容,成功执行 lablemetoyolo.py 代码后效果如下:

labels文件夹下的标签成功转换了 YOLO 系列可以使用的 label 标签,到此时就已经成功准备好我们需要的训练集了!

特别说明:该方法仅适用于上述作者所说的场景下,实际情况下,建议大家还是使用合格的数据集进行训练(即目标与背景语义丰富的数据集),使得训练出来的神经网络具有良好的泛化性与鲁棒性,否则训练出来的网络很容易过拟合!

2.2 YOLOv5-Lite训练

Yolov5-Lite 训练就是常规的神经网络模型训练,我们从 GitHub 上下载 Yolov5-Lite 的源代码,训练平台为:PyCharm 2020.1 x64,GPU:RTX3060 6G,CPU:AMD Ryzen 7 5800H  3.2GHZ

读者朋友可以使用  PyCharm 或者 VsCode 打开 Yolov5-Lite 的源码(作者使用PyCharm 2020.1 x64);

在 Yolov5-Lite 的目录下找到 train.py (训练文件)的 main 函数入口,进行如下配置:

我们设置如下几个核心配置:

--weights v5lite-s.pt

--cfg models/v5Lite-s.yaml

--data data/mydata.yaml

--img-size 320

--batch-size 16

--data data/mydata.yaml

device 0/cpu                        (可以不使用CUDA训练)

读者朋友一定要将数据集存放的地址位置搞正确!!!

mydata.yaml:

Yolov5-Lite 网络模型的训练可以不一定必须使用 CUDA 进行加速,但是 pytorch 架构等依赖库一定需要满足,模型训练依赖要求如下:

# base ----------------------------------------
matplotlib>=3.2.2
numpy>=1.18.5
opencv-python>=4.1.2
Pillow
PyYAML>=5.3.1
scipy>=1.4.1
torch>=1.8.0
torchvision>=0.9.0
tqdm>=4.41.0

# logging -------------------------------------
tensorboard>=2.4.1
# wandb

# plotting ------------------------------------
seaborn>=0.11.0
pandas

# export --------------------------------------
# coremltools>=4.1
# onnx>=1.9.1
# scikit-learn==0.19.2  # for coreml quantization

# extras --------------------------------------
thop  # FLOPS computation
pycocotools>=2.0  # COCO mAP

将训练环境与数据集都搞定之后,就可以点击运行按钮进行 Yolov5-Lite 的模型训练了!

训练成功之后,将会在当前目录下的 run 文件下的 trian 文件下找到 expx (x代表数字),expx 则存放了第 x 次训练时候的各种数据内容,包括:历史最优权重best_weight,当前权重last_weight,训练结果result等等;

三、树莓派4B部署YOLOv5-Lite

树莓派4B运行 Yolov5-Lite 网络模型进行目标检测需要依赖 OpenCV 等视觉Lib,读者朋友可以直接使用作者第一篇博客的配置。

博客地址:http://t.csdn.cn/jbHQm

3.1 ONNX概述

Open Neural Network Exchange(ONNX)是一个开放的生态系统,它使人工智能开发人员在推进项目时选择合适的工具,不用被框架或者生态系统所束缚。ONNX支持不同框架之间的互操作性,简化从研究到生产之间的道路。ONNX支持许多框架(TensorFlow, Pytorch, Keras, MxNet, MATLAB等等),这些框架中的模型都可以导出或者转换为标准ONNX格式。模型采用ONNX格式后,就可在各种平台和设备上运行。

开发者根据深度学习框架优劣选择某个框架,但是这些框架适应不同的开发阶段,由于必须进行转换,从而导致了研究和生产之间的重大延迟。ONNX格式一个通用的IR,能够使得开发人员在开发或者部署的任何阶段选择最适合他们项目的框架。ONNX通过提供计算图的通用表示,帮助开发人员为他们的任务选择合适的框架。

ONNX可视化:ONNX 模型可以通过 netron 进行可视化。

作者总结:

ONNX 顾名思义就是开放的神经网络模型转换,利用它可以轻松将模型更换框架,从而适配亦或是部署在各类设备上。

3.2 ONNX模型转换和移植

如今的开源 YOLO 系列神经网络模型的目录下作者都会预留 export.py 文件将该神经网络模型进行转换到 ONNX 模型,方便大家实际情况下部署使用!

将我们训练好的最优训练权重 weights 存放到 YOLOv5-Lite 主目录下,之后运行如下代码:

python export.py --weights best.pt

运行该指令后将会通过 best.pt 文件,生成 best.onnx ONNX 模型的权重文件;

同时为了成功运行 ONNX 格式的 YOLOv5-Lite 网络模型,需要在树莓派4B中安装 onnxruntim (可以直接使用作者提供的安装包),当然值得注意的是 onnxruntim 的安装需要依赖的 Numpy版本1.21 以上(这点需要大家注意,当然如果使用了作者的镜像完全没有问题!)。

pip install onnx     (tab补全安装包)

将转换成 ONNX 格式的权重文件导入到树莓派4B中,并于目标检测程序保持同一目录下:

到此 ONNX 模型的转换与移植工作就可以完成了!

四、YOLOv5-Lite目标检测

YOLOv5-Lite 的目标检测前向推理程序是很简单的,可以直接借鉴作者如下提供的代码:

import cv2
import numpy as np
import onnxruntime as ort
import time

def plot_one_box(x, img, color=None, label=None, line_thickness=None):
    """
    description: Plots one bounding box on image img,
                 this function comes from YoLov5 project.
    param: 
        x:      a box likes [x1,y1,x2,y2]
        img:    a opencv image object
        color:  color to draw rectangle, such as (0,255,0)
        label:  str
        line_thickness: int
    return:
        no return
    """
    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,
        )

def _make_grid( nx, ny):
        xv, yv = np.meshgrid(np.arange(ny), np.arange(nx))
        return np.stack((xv, yv), 2).reshape((-1, 2)).astype(np.float32)

def cal_outputs(outs,nl,na,model_w,model_h,anchor_grid,stride):
    
    row_ind = 0
    grid = [np.zeros(1)] * nl
    for i in range(nl):
        h, w = int(model_w/ stride[i]), int(model_h / stride[i])
        length = int(na * h * w)
        if grid[i].shape[2:4] != (h, w):
            grid[i] = _make_grid(w, h)

        outs[row_ind:row_ind + length, 0:2] = (outs[row_ind:row_ind + length, 0:2] * 2. - 0.5 + np.tile(
            grid[i], (na, 1))) * int(stride[i])
        outs[row_ind:row_ind + length, 2:4] = (outs[row_ind:row_ind + length, 2:4] * 2) ** 2 * np.repeat(
            anchor_grid[i], h * w, axis=0)
        row_ind += length
    return outs



def post_process_opencv(outputs,model_h,model_w,img_h,img_w,thred_nms,thred_cond):
    conf = outputs[:,4].tolist()
    c_x = outputs[:,0]/model_w*img_w
    c_y = outputs[:,1]/model_h*img_h
    w  = outputs[:,2]/model_w*img_w
    h  = outputs[:,3]/model_h*img_h
    p_cls = outputs[:,5:]
    if len(p_cls.shape)==1:
        p_cls = np.expand_dims(p_cls,1)
    cls_id = np.argmax(p_cls,axis=1)

    p_x1 = np.expand_dims(c_x-w/2,-1)
    p_y1 = np.expand_dims(c_y-h/2,-1)
    p_x2 = np.expand_dims(c_x+w/2,-1)
    p_y2 = np.expand_dims(c_y+h/2,-1)
    areas = np.concatenate((p_x1,p_y1,p_x2,p_y2),axis=-1)
    
    areas = areas.tolist()
    ids = cv2.dnn.NMSBoxes(areas,conf,thred_cond,thred_nms)
    if len(ids)>0:
        return  np.array(areas)[ids],np.array(conf)[ids],cls_id[ids]
    else:
        return [],[],[]
def infer_img(img0,net,model_h,model_w,nl,na,stride,anchor_grid,thred_nms=0.4,thred_cond=0.5):
    # 图像预处理
    img = cv2.resize(img0, [model_w,model_h], interpolation=cv2.INTER_AREA)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = img.astype(np.float32) / 255.0
    blob = np.expand_dims(np.transpose(img, (2, 0, 1)), axis=0)

    # 模型推理
    outs = net.run(None, {net.get_inputs()[0].name: blob})[0].squeeze(axis=0)

    # 输出坐标矫正
    outs = cal_outputs(outs,nl,na,model_w,model_h,anchor_grid,stride)

    # 检测框计算
    img_h,img_w,_ = np.shape(img0)
    boxes,confs,ids = post_process_opencv(outs,model_h,model_w,img_h,img_w,thred_nms,thred_cond)

    return  boxes,confs,ids




if __name__ == "__main__":

    # 模型加载
    model_pb_path = "best.onnx"
    so = ort.SessionOptions()
    net = ort.InferenceSession(model_pb_path, so)
    
    # 标签字典
    dic_labels= {0:'drug',
            1:'glue',
            2:'prime'}
    
    # 模型参数
    model_h = 320
    model_w = 320
    nl = 3
    na = 3
    stride=[8.,16.,32.]
    anchors = [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]]
    anchor_grid = np.asarray(anchors, dtype=np.float32).reshape(nl, -1, 2)
    
    video = 0
    cap = cv2.VideoCapture(video)
    flag_det = False
    while True:
        success, img0 = cap.read()
        if success:
            
            if flag_det:
                t1 = time.time()
                det_boxes,scores,ids = infer_img(img0,net,model_h,model_w,nl,na,stride,anchor_grid,thred_nms=0.4,thred_cond=0.5)
                t2 = time.time()
            
                
                for box,score,id in zip(det_boxes,scores,ids):
                    label = '%s:%.2f'%(dic_labels[id],score)
            
                    plot_one_box(box.astype(np.int16), img0, color=(255,0,0), label=label, line_thickness=None)
                    
                str_FPS = "FPS: %.2f"%(1./(t2-t1))
                
                cv2.putText(img0,str_FPS,(50,50),cv2.FONT_HERSHEY_COMPLEX,1,(0,255,0),3)
                
            
            cv2.imshow("video",img0)
        key=cv2.waitKey(1) & 0xFF    
        if key == ord('q'):
        
            break
        elif key & 0xFF == ord('s'):
            flag_det = not flag_det
            print(flag_det)
            
    cap.release() 

就该目标检测算法作者简单给大家讲解一下:

上方的 model_pb_path 和 dic_labels 是需要根据自己实际情况去改一下的,其余基本保持不变即可!

上述基本除了 anchor 锚框这个数据,其余都是不需要改动的,这里 anchor 直接使用了默认值。如果大家想在目标检测的时候可以更好地框选出自己地识别目标,可以用 K-means 聚类算法去自适应 anchor 的大小,从小聚类出符合自己数据类型的 anchor ,这样目标检测的时候可能效果更好!

一切准备就绪,直接运行咱们的 test_video.py 程序进行目标检测:

python3 test_video.py

按键 “s” 开启目标检测功能,按键 “q” 退出当前目标检测程序!

五、项目效果

5.1 实战视频

基于树莓派4B的YOLOv5-Lite目标检测

5.2 作者有话

作者本次仅使用了 ONNX 模型下直接跑 YOLOv5-Lite 的网络模型,目前该状态下的FPS仅维持在5左右,效果其实比直接跑 YOLOv5 网络模型已经好很多了(YOLOv5的FPS在0.3FPS左右)。但是距离可以与控制结合感觉还是差了点,所以,后续作者将对目标检测进行加速处理(使用NCNN,MNN等模型加速)。

当然,作者也会分享自己实验室提出的轻量化目标检测网络从网络模型出发加速推理。将计算机视觉与控制结合的嵌入式AI教学后续也会出博客分享给各位,希望给大家日常的工作或者是电赛提供些许帮助!

六、项目代码

代码地址:基于树莓派4B的YOLOv5-Lite目标检测的资源包资源-CSDN文库

如果积分不够的朋友,点波关注评论区留下邮箱,作者无偿提供源码和后续问题解答。求求啦关注一波吧 !!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/690660.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

记一次解决vmware安装windows server 2019时, 虚拟机网络电缆被拔出,连不上网的问题

项目场景&#xff1a; 项目需要基于electron开发桌面端软件&#xff0c;实现前后端项目的自动化部署、可视化配置等功能&#xff0c;经过需求分析后&#xff0c;确定首先要适配的场景即&#xff1a;通过桌面端软件远程连接&#xff0c;在桌面端软件中执行安装、运行、配置等一…

用ZLmediaKit流媒体服务器时候遇到的常规问题

照zlmediakit的源码 自己复制了一份 然后有的地方编译不过修改了部分 测试的时候发现有两个问题 第一是 ffmpeg的ffplay 能播放 vlc不能播放 第二个问题是directProxy设置为0的时候 推流的时候 然后用ffplay播放 只有音频没有视频 查了好久终于解决这个问题 第一个…

Verilog/C++实现排序算法

Verilog/C实现排序算法 1、冒泡排序算法 冒泡排序是一种简单的交换类排序。 冒泡排序算法的原理如下&#xff1a; 1、比较相邻的元素。如果第一个比第二个大&#xff0c;就交换他们两个。2、对每一对相邻元素做同样的工作&#xff0c;从开始第一对到结尾的最后一对。在这一…

深入解析Spring Boot:构建现代化Java应用程序的利器

深入解析Spring Boot&#xff1a;构建现代化Java应用程序的利器 文章目录 导言1. 简化的开发流程2. 内嵌的Web服务器3. 自动配置4. 健康检查和监控5. 外部化配置6. 强大的生态系统小结 导言 Spring Boot 是一个开源的Java框架&#xff0c;旨在简化和加速Java应用程序的开发过程…

关于CSPM国标证书(项目管理专业人员能力评价)

先来回答一下粉丝提问&#xff1a; 1、软考高项可以对标吗&#xff1f; 答案&#xff1a;软考高项是不可以对标的。 2、Prince2可以对应哪一个级别&#xff1f; 答案&#xff1a;哪一个级别都不可以对标&#xff0c;目前可对标的是PMI&#xff0c;IPMA、HCSE-PM&#xff08;华…

代码随想录二刷day35 |贪心 之 860.柠檬水找零 406.根据身高重建队列 452. 用最少数量的箭引爆气球

day35 860.柠檬水找零406.根据身高重建队列452. 用最少数量的箭引爆气球 860.柠檬水找零 题目链接 解题思路&#xff1a; 局部最优&#xff1a;遇到账单20&#xff0c;优先消耗美元10&#xff0c;完成本次找零。全局最优&#xff1a;完成全部账单的找零。 代码如下&#xff1a…

NodeJS NVM版本管理⑩⑧

文章目录 ✨文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持&#x1f618;前言 N V M I n t r o d u c e NVM Introduce NVMIntroduce N V M U s e NVMUse NVMUse D o w n l o a d A n d I n s t a l l Download And Install Downloa…

DTC143ZM理解介绍(含电路应用)

前人种树&#xff0c;后人乘凉&#xff1b;创造不易&#xff0c;请勿迁移~ author daisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 daisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主daisy.skye擅长嵌入式,Qt,Linux,等方面的知识https://blog.csdn.net/qq_40715266?t…

C语言sin函数学习

函数名: sin 头文件&#xff1a;<math.h> 函数原型: double sin(double x); 功 能: 正弦函数 参 数&#xff1a; double x 操作的弧度 返回值&#xff1a; 返回弧度的正弦值 1π/180弧度 这是C标准库的定义&#xff1b; #include<stdio.h>#include<mat…

普乐蛙VR太空宇宙vr星际飞船设备模拟太空飞船

科技感十足!你有没有想过自己能够亲身体验到太空飞行的感觉?这一刻&#xff0c;梦想成真! 宇航员体验 VR科技让人类走进了未知领域&#xff0c;现在&#xff0c;你可以在舒适的空间内感受宇宙中的万千风景&#xff0c;体验宇航员的工作和生活。而这次VR科技体验馆更是为你准备…

uni-easyinput连续输入出现闪动解决

原因分析&#xff1a; 是因为uni-easyinput源码中&#xff0c;在输入触发事件中一直去同步modelValue&#xff0c;而modelValue其实就是双向绑定的值&#xff08;有一定延迟&#xff09;&#xff0c;当连续输入时&#xff0c;会导致input和modelValue不一致&#xff0c;就会出…

YOLOv8遇见VisDrone 2023目标检测挑战赛-YOLOv8实战VisDrone无人机目标检测(视频教程)

课程链接&#xff1a;https://edu.csdn.net/course/detail/38688 VisDrone 2023目标检测挑战赛( http://aiskyeye.com/challenge-2023/)和 ICCV 2023 顶会联合举行&#xff0c;用于检测从无人机获取的视觉数据中的物体。优胜者可出席 ICCV 2023 研讨会&#xff0c;并获得万元奖…

如何利用python做爬虫?

Python爬虫在许多情况下是非常有用的&#xff0c;爬虫可以帮助自动化地从互联网上获取大量数据。这些数据可以是产品信息、新闻文章、社交媒体内容、股票数据等通过爬虫可以减少人工收集和整理数据的工作量&#xff0c;提高效率。在软件开发中&#xff0c;可以使用爬虫来进行自…

为什么学习STM32相对困难?如何优化学习过程?

当你在学习STM32时感到困惑&#xff0c;可以考虑以下优化策略&#xff1a;理解基础概念&#xff1a;确保你对STM32的基础概念有清晰的理解&#xff0c;包括芯片架构、寄存器配置和外设功能等。通过仔细阅读官方文档、参考手册或教程&#xff0c;加深对这些概念的理解。我这里有…

Android Jetpack Compose之OutlinedButton的使用

Android Jetpack Compose是一个现代化的UI工具包&#xff0c;它让开发者可以更方便地构建出美观且功能强大的Android应用。本文将详细介绍其中的一个重要组件——OutlinedButton。 一. OutlinedButton简介 二. 如何使用OutlinedButton 三. 自定义OutlinedButton 四. Outlin…

两段代码共存于一个文件,编译时有选择的编译其中的一部分,有几种方法实现?如何实现?(笔试题)

两段代码共存于一个文件&#xff0c;编译时有选择的编译其中的一部分&#xff0c;请问有几种方法实现&#xff1f;如何实现&#xff1f; 1. 条件编译 使用预处理指令来控制代码的编译。通过在代码中添加条件编译指令&#xff0c;可以根据条件选择性地编译代码。条件编译指令通…

【每天40分钟,我们一起用50天刷完 (剑指Offer)】第八天 8/50

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

chatgpt赋能python:Python获取句柄的方法——在Windows平台上实现窗口控制

Python获取句柄的方法——在Windows平台上实现窗口控制 句柄&#xff08;Handle&#xff09;是Windows系统中非常重要的概念&#xff0c;它是一种指向资源对象的引用&#xff0c;以数字的形式来表示。在Windows上&#xff0c;所有资源对象都具有独特的句柄。窗口也是一种资源对…

【强化学习】常用算法之一 “Q-learning”

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,活动,python领域博主爱笑的男孩。擅长深度学习,活动,python,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typeblog个…

学习Kotlin~变量

变量定义 声明变量 var maximumAge: Int 5;数据类型全部都是引用类型 var y1: String "Hello World";var y2: Char A;var y3: Boolean true;var y4: Int 5;var y5: Double 3.14;var和val 声明可修改使用var声明只读使用val&#xff0c;只读变量并非绝对只读 …