树莓派摄像头 C++ OpenCV YoloV3 实现实时目标检测

2023-05-16

树莓派摄像头 C++ OpenCV YoloV3 实现实时目标检测

本文将实现树莓派摄像头 C++ OpenCV YoloV3 实现实时目标检测,我们会先实现树莓派对视频文件的逐帧检测来验证算法流程,成功后,再接入摄像头进行实时目标检测。

先声明一下笔者的主要软硬件配置:

树莓派4B 4GB内存

CSI 摄像头

Ubuntu 20.04

OpenCV 的安装

不多讲,参考 Ubuntu 18.04 安装OpenCV C++ 。

准备YoloV3模型权重文件和视频文件

模型配置文件和权重、COCO数据集名称文件

我们先下载作者官方发布的 YoloV3 模型配置文件、权重文件:

wget https://pjreddie.com/media/files/yolov3.weights
wget https://github.com/pjreddie/darknet/blob/master/cfg/yolov3.cfg?raw=true -O ./yolov3.cfg

上面是比较大的网络,由于我们的树莓派算力比较一遍,所以建议使用轻量型的网络 yolov3-tiny:

wget https://pjreddie.com/media/files/yolov3-tiny.weights
wget https://github.com/pjreddie/darknet/blob/master/cfg/yolov3-tiny.cfg?raw=true -O ./yolov3-tiny.cfg

另外,由于模型权重是在 COCO 数据集上进行预训练的,所以我们还要准备 COCO 的类别名称文件,方便在模型输出检测结果后进行后处理,将类别显示在检测结果框上。

wget https://github.com/pjreddie/darknet/blob/master/data/coco.names?raw=true -O ./coco.names

注:如果上面的 github 中的配置文件在命令行下载的比较慢的话,可以直接去 github 网页复制粘贴下来。

准备视频文件

我们会先对一个视频文件进行逐帧检测来验证算法的流程,在之后再使用摄像头进行实时检测。

我们直接通过 you-get 工具去B站下载视频文件并改个名:

pip install you-get
you-get https://www.bilibili.com/video/av32184680
rm fileName.cmt.xml
mv fileName.mp4 demo.mp4

如果是 flv 文件,可以用 ffmpeg 转为 mp4 文件:

ffmpeg -i input.flv output.mp4

视频文件的检测

一切准备就绪我们开始先测试一下视频文件的检测,我们先讲解一遍代码,在最后会给出整个源码。

1 初始化参数

YOLOv3算法的预测结果就是边界框。每一个边界框都旁随着一个置信值。第一阶段中,全部低于置信度阀值的都会排除掉。 对剩余的边界框执行非最大抑制算法,以去除重叠的边界框。非最大抑制由一个参数 nmsThrehold 控制。读者可以尝试改变这个数值,观察输出的边界框的改变。 接下来,设置输入图片的宽度inpWidth和高度 inpHeight。这里设置为416。如果想要更快的速度,可以把宽度和高度设置为320。如果想要更准确的结果,可改变为608。

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <opencv.hpp>
using namespace std;

float confThreshold = 0.5;	//置信度阈值
float nmsThreshold = 0.4;		//非最大抑制阈值
int inpWidth = 416;					//网络输入图片宽度
int inpHeight = 416;				//网络输入图片高度

2 读取模型和COCO类别名

接下来我们读入COCO 类别名并存入 classes 容器。并加载模型与权重文件 yolov3-tiny.cfgyolov3-tiny.weights。这里用到的几个文件就是我们刚才下载好的,读者需要改为自己的路径。最后把DNN的后端设置为OpenCV,目标设置CPU。这里我们树莓派没有GPU等加速推理硬件,就用CPU,如果有GPU,可改为OpenCL、CUDA等

//将类名存进容器
vector<string> classes;	//储存名字的容器
string classesFile = "./coco.names";		//coco.names包含80种不同的类名
ifstream ifs(classesFile.c_str());
string line;
while(getline(ifs,line))classes.push_back(line);

//取得模型的配置和权重文件
cv::String modelConfiguration = "./yolov3-tiny.cfg";
cv::String modelWeights = "./yolov3-tiny.weights";

//加载网络
cv::dnn::Net net = cv::dnn::readNetFromDarknet(modelConfiguration,modelWeights);
net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
net.setPreferableTarget(cv::dnn::DNN_TARGET_OPENCL);			// 这里我们树莓派没有GPU等加速推理硬件,就用CPU,如果有GPU,可改为OpenCL、CUDA等

3 读取输入

这里我们先读入下载好的视频文件,一会儿再使用本地摄像头测试。这里如果是树莓派外接显示器的话可以用创建GUI窗口来查看,但是我们通常是命令行SSH连接树莓派,这时我们就直接将每一帧的检测结果保存为图像文件查看:

// 打开视频文件或者本地摄像头来读取输入
string str, outputFile;
cv::VideoCapture cap("./demo.mp4");
cv::VideoWriter video;
cv::Mat frame,blob;
// 开启摄像头
// cv::VideoCapture capture(0);
// 创建窗口
// static const string kWinName = "YoloV3 OpenCV";
// cv::namedWindow(kWinName,cv::WINDOW_AUTOSIZE);
// 非GUI界面不需要创建窗口

4 循环处理每一帧

OpenCV中,输入到神经网络的图像需要以一种叫 bolb 的格式保存。 读取了输入图片或者视频流的一帧图像后,这帧图像需要经过bolbFromImage() 函数处理为神经网络的输入类型 bolb。在这个过程中,图像像素以一个 1/255 的比例因子,被缩放到0到1之间。同时,图像在不裁剪的情况下,大小调整到 416x416。注意我们没有降低图像平均值,因此传递 [0,0,0] 到函数的平均值输入,保持swapRB 参数到默认值1。 输出的bolb传递到网络,经过网络正向处理,网络输出了所预测到的一个边界框清单。这些边界框通过后处理,滤除了低置信值的。我们随后再详细的说明后处理的步骤。我们在每一帧的左上方打印出了推断时间。伴随着最后的边界框的完成,图像保存到硬盘中,之后可以作为图像输入或者通过 VideoWriter 作为视频流输入。

while(cv::waitKey(1)<0){
    // 取每帧图像
    cap>>frame;
    // 如果视频播放完则停止程序
    if(frame.empty()){
        break;
    }
    // 在dnn中从磁盘加载图片
    cv::dnn::blobFromImage(frame,blob,1/255.0,cv::Size(inpWidth,inpHeight));
    // 设置输入
    net.setInput(blob);
    // 设置输出层
    vector<cv::Mat> outs;			//储存识别结果
    net.forward(outs,getOutputNames(net));
    // 移除低置信度边界框
    postprocess(frame,outs);
    // 显示s延时信息并绘制
    vector<double> layersTimes;
    double freq = cv::getTickFrequency()/1000;
    double t=net.getPerfProfile(layersTimes)/freq;
    string label = cv::format("Infercence time for a frame:%.2f ms",t);
    cv::putText(frame,label,cv::Point(0,15),cv::FONT_HERSHEY_SIMPLEX,0.5,cv::Scalar(0,255,255));
    cout << "Frame: " << frame_cnt++ << ", time: " << t << "ms" << endl;
    // 绘制识别框,在这里如果我们用的是GUI界面,并且刚才创建了窗口的话,可以imshow,否则是命令行SSH连接树莓派的话就imwrite保存图像
    // cv::imshow(kWinName,frame);
    cv::imwrite("output.jpg",frame);
}

5-1 得到输出层的名字

第五步我们给出几个用到的函数的实现。

OpenCV 的网络类中的前向功能需要结束层,直到它在网络中运行。因为我们需要运行整个网络,所以我们需要识别网络中的最后一层。我们通过使用 getUnconnectedOutLayers() 获得未连接的输出层的名字,该层基本就是网络的最后层。然后我们运行前向网络,得到输出,如前面的代码片段 net.forward(getOutputsNames(net))

//从输出层得到名字
vector<cv::String> getOutputNames(const cv::dnn::Net& net){
    static vector<cv::String> names;
    if(names.empty()){
        //取得输出层指标
        vector<int> outLayers = net.getUnconnectedOutLayers();
        vector<cv::String> layersNames = net.getLayerNames();
        //取得输出层名字
        names.resize(outLayers.size());
        for(size_t i =0;i<outLayers.size();i++){
            names[i] = layersNames[outLayers[i]-1];
        }
    }
    return names;
}

5-2 后处理

网络输出的每个边界框都分别由一个包含着类别名字和5个元素的向量表示。 头四个元素代表center_x, center_y, width, height。第五个元素表示包含着目标的边界框的置信度。 其余的元素是和每个类别(如目标种类)有关的置信度。边界框分配给最高分数对应的那一种类。 一个边界框的最高分数也叫做它的置信度 confidence。如果边界框的置信度低于规定的阀值,算法上不再处理这个边界框。 置信度大于或等于置信度阀值的边界框,将进行非最大抑制。这会减少重叠的边界框数目。

// 移除低置信度边界框
void postprocess(cv::Mat& frame,const vector<cv::Mat>& outs){
    vector<int> classIds;			// 储存识别类的索引
    vector<float> confidences;// 储存置信度
    vector<cv::Rect> boxes;		// 储存边框
    for(size_t i=0;i<outs.size();i++){
    //从网络输出中扫描所有边界框
    //保留高置信度选框
    //目标数据data:x,y,w,h为百分比,x,y为目标中心点坐标
        float* data = (float*)outs[i].data;
        for(int j=0;j<outs[i].rows;j++,data+=outs[i].cols){
            cv::Mat scores = outs[i].row(j).colRange(5,outs[i].cols);
            cv::Point classIdPoint;
            double confidence;//置信度
            //取得最大分数值与索引
            cv::minMaxLoc(scores,0,&confidence,0,&classIdPoint);
            if(confidence>confThreshold){
                int centerX = (int)(data[0]*frame.cols);
                int centerY = (int)(data[1]*frame.rows);
                int width = (int)(data[2]*frame.cols);
                int height = (int)(data[3]*frame.rows);
                int left = centerX-width/2;
                int top = centerY-height/2;
                classIds.push_back(classIdPoint.x);
                       confidences.push_back((float)confidence);
                       boxes.push_back(cv::Rect(left, top, width, height));
            }
        }
    }
    //低置信度
    vector<int> indices;//保存没有重叠边框的索引
    //该函数用于抑制重叠边框
    cv::dnn::NMSBoxes(boxes,confidences,confThreshold,nmsThreshold,indices);
    for(size_t i=0;i<indices.size();i++){
        int idx = indices[i];
        cv::Rect box = boxes[idx];
        drawPred(classIds[idx],confidences[idx],box.x,box.y,
        box.x+box.width,box.y+box.height,frame);
    }
}
    

5-3 画出边界框

最后,经过非最大抑制后,得到了边界框。我们把边界框在输入帧上画出,并标出种类名和置信值。

//绘制预测边界框
void drawPred(int classId,float conf,int left,int top,int right,int bottom,cv::Mat& frame){
    //绘制边界框
    cv::rectangle(frame,cv::Point(left,top),cv::Point(right,bottom),cv::Scalar(255,178,50),3);
    string label = cv::format("%.2f",conf);
    if(!classes.empty()){
        CV_Assert(classId < (int)classes.size());
        label = classes[classId]+":"+label;//边框上的类别标签与置信度
    }
    //绘制边界框上的标签
    int baseLine;
    cv::Size labelSize = cv::getTextSize(label,cv::FONT_HERSHEY_SIMPLEX,0.5,1,&baseLine);
    top = max(top,labelSize.height);
    cv::rectangle(frame,cv::Point(left,top-round(1.5*labelSize.height)),cv::Point(left+round(1.5*labelSize.width),top+baseLine),cv::Scalar(255,255,255),cv::FILLED);
    cv::putText(frame, label,cv::Point(left, top), cv::FONT_HERSHEY_SIMPLEX, 0.75,cv::Scalar(0, 0, 0), 1);
}

文件全部源码在文末。

写好之后我们编译执行即可,关于 OpenCV 众多头文件包含、链接库链接时、运行时的链接,对初学者来说可能会遇到一些问题,可以参考:

Linux下编译、链接、加载运行C++ OpenCV的两种方式及常见问题的解决

Linux下C/C++程序编译链接加载过程中的常见问题及解决方法

在这里插入图片描述

可以从左上角和标准输出看到,每帧的检测时间大概在 280ms,速度还可以,精度大体也不错。但是由于模型较小,性能受限,对于一些边缘小物体会有误差,如上图中右侧的小车。

树莓派摄像头实时检测

树莓派摄像头调试参考:树莓派摄像头基础配置及测试 。

在视频文件的检测顺利完成后,实时树莓派的检测就很简单了,只需要将读取输入部分从视频文件改为本地摄像头即可。

主要就是这一行修改:

// cv::VideoCapture cap("./video/demo.mp4");
// cv::VideoWriter video;
// 改为
cv::VideoCapture cap(0);

另外,我们需要设置一些 OpenCV 读取摄像头输入的尺寸大小,否则笔者亲测是有一些小bug:

cap.set(cv::CAP_PROP_FRAME_WIDTH, 640);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480);

在这里插入图片描述

在笔者实验室中实测,速度和精度也都还可以。

全部代码

全部代码可参考:https://github.com/Adenialzz/Hello-AIDeployment

如有错误或遗漏,欢迎留言指正。

Ref:

https://blog.csdn.net/cuma2369/article/details/107666559

https://ryanadex.github.io/2019/01/15/opencv-yolov3/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

树莓派摄像头 C++ OpenCV YoloV3 实现实时目标检测 的相关文章

  • 4.二叉树的遍历(C++版)

    二叉树的递归 1 二叉树递归遍历 二叉树的递归序 递归序过程 xff1a 两个注释1之间的代码代表第一次来到一个节点的时候 xff0c 会判断一下这个节点是否为空 xff1b 来到这个节点的左树去遍历 遍历完第二次回到本函数 xff0c 进
  • 6.暴力递归转动态规划

    动态规划 1 什么是动态规划 xff1f 动态规划就是暴力递归 xff08 回溯 xff09 的过程中有重复调用的过程 xff0c 动态规划在算过每次调用后把答案记下来 xff0c 下次再遇到重复过程直接调用这个行为就叫动态规划 动态规划就
  • 8.岛问题

    岛问题 题目 一个矩阵中只有0和1两种值 xff0c 每个位置都可以和自己的上 下 左 右四个位置相连 xff0c 如果有一片1连在一起 xff0c 这个部分叫做一个岛 xff0c 求一个矩阵中有多少个岛 xff1f 例子 0 0 1 0
  • 9.KMP算法

    KMP算法 1 KMP算法解决的问题 字符串str1和str2 xff0c str1是否包含str2 xff0c 如果包含返回str2在str1中开始的位置 xff0c 如果不包含返回 1 如果做到时间复杂度O N 完成 xff1f 测试用
  • 10.Manacher算法(用于解决回文子串问题)

    Manacher算法 1 Manacher算法解决的问题 字符串str中 xff0c 最长回文子串的长度如何求解 xff1f 如何做到时间复杂度O N 完成 xff1f 回文序列是从左往右和从右往左看一样 xff0c 如abba xff0c
  • git push代码到远程仓库,报错解决:fatal: unable to access ‘https://github.com/.......‘: OpenSSL SSL_read: Connec

    报错如下 xff1a 产生原因 xff1a 一般是这是因为服务器的SSL证书没有经过第三方机构的签署 xff0c 所以才报错解除ssl验证后 xff0c 再次git即可 解决办法输入此条git命令 xff1a git config glob
  • 11.滑动窗口的最大值——重要结构双端队列

    滑动窗口最大 xff08 小 xff09 值 1 滑动窗口最大值结构 窗口概念 xff1a 一开始窗口左边界L 有边界R都停留在数组左侧 xff0c 窗口L和R都只能往数组右边移动 xff0c 并且左边界L永远不能超过有边界R 任何时刻都能
  • 12.单调栈——解决接雨水和柱状图中的最大矩形等问题

    单调栈 1 单调栈实现结构 单调栈解决的问题 xff1a 给你一个数组 想要用尽可能低的代价知道数组中每一个元素的左边元素比它大的或者右边元素比他大的信息是什么 如果用暴力方法 xff0c 左边遍历一次右边遍历一次 xff0c 时间复杂度为
  • 12.快速排序

    1荷兰国旗问题 问题1 xff1a 给定一个数组arr和一个数num xff0c 将小于等于num的数放在数组的左边大于num的数放在数组的右边 xff08 不要求有序 xff09 要求额外空间复杂度为O 1 时间复杂度为O N 遍历数组元
  • 死锁预防、死锁避免、死锁检测

    死锁 1 死锁的概念 1 1死锁的定义 多个进程并发执行 xff0c 由于竞争资源而造成的一种僵局 xff08 互相等待 xff09 xff0c 若无外力作用 xff0c 这些进程都将无法推进 xff0c 这就是死锁现象 例如 xff1a
  • 内存分配方式

    内存分配方式 1 基本概念 内存管理的基本概念 虽然计算机硬件发展 xff0c 内存容量在不断变大 xff0c 但是也不可能将所有用户进程和系统所需要的程序和数据放入内存中 xff0c 因此操作系统必须要对内存空间进行合理划分和有效动态分配
  • 虚拟内存和LRU页面置换算法

    虚拟内存 1 虚拟内存的基本概念 传统存储管理方式的特征 传统的内存管理策略都是为了同时将多个进程保存进内存中 xff0c 它们具有以下的共同特征 xff1a 一次性 作业必须一次性全部装入内存后 xff0c 才能开始运行 xff08 静态
  • 0.0C++和C的区别

    C 43 43 和C的区别 C 43 43 如今是一个同时支持面向过程 面向对象 函数形式 泛型形式 元编程形式的语言 我们该如何理解C 43 43 这门语言呢 xff1f Effective C 43 43 书中给出了一个简单的方法 xf
  • 15.9为什么要将成员变量设置为private

    为什么要将成员变量声明为private 为什么要将成员变量封装为private xff0c 主要有以下四个原因 xff1a 好处1 xff1a 如果成员变量不是public xff0c 那么客户唯一能访问成员变量的唯一方式就是通过成员函数
  • 2.7.C++中static关键字的5种基本用法

    static关键字 static关键字主要应用于以下几种情况 xff1a 情况1 xff1a static静态函数 定义静态函数 xff1a 在函数返回类型前加上static关键字 xff0c 函数即被定义为静态函数 静态函数只能在本源文件
  • 进程调度算法

    进程调度 在多道程序系统中 xff0c 进程数量往往多于处理机的个数 xff0c 因此进程竞争使用处理机的情况在所难免 处理机调度是对处理机进行分配 xff0c 即从就绪队列中按照一定的算法选择一个进程并将处理机分配给它运行 xff0c 以
  • git clone 出现fatal: unable to access ‘https://github 类错误解决方法

    git clone 遇到问题 xff1a fatal unable to access https github comxxxxxxxxxxx Failed to connect to xxxxxxxxxxxxx 问题 将命令行里的http
  • 进程通信的方式

    进程通信 1 进程通信的概念 进程是一个独立的资源分配单元 xff0c 不同进程 xff08 主要是指不同的用户进程 xff09 之间的资源是独立的 xff0c 没有关联的 xff0c 不能在一个进程中直接访问另一个进程的资源 但是 xff
  • 网络通信的过程

    网络通信的过程 封装 上层协议时如何使用下层协议提供的服务的呢 xff1f 其实这是通过封装实现的 应用程序是在发送到物理网络上之前 xff0c 将沿着协议栈从上往下依次传递 每层协议都将在上层数据的基础上加上自己的头部信息 xff08 有
  • TCP三次握手、四次挥手

    TCP通信流程 TCP和UDP TCP和UDP区别如下 xff1a UDP xff1a 用户数据报文协议 xff0c 面向无连接 xff0c 可以单播 xff0c 多播 xff0c 广播 xff0c 面向数据报 xff0c 不可靠 TCP

随机推荐

  • Qt的多线程编程

    Qt线程 基本概念 并发 当有多个线程在操作时 xff0c 如果系统只有一个CPU xff0c 则它根本不可能真正同时进行一个以上的线程 xff0c 它只能把CPU运行时间划分成若干个时间段 xff0c 再将时间段分配给各个线程执行 xff
  • CMake编译C++文件

    这篇文章介绍如何使用cmake工具编译一个最简单的helloworld cpp文件 首先创建一个空的文件夹 mkdir cmake test 在该文件夹下 xff0c 我们新建一个helloworld cpp文件 span class to
  • 智能小车建图导航-在rviz中导航(运行)

    笔记来源 xff1a 机器人开发与实践 xff08 古月 xff09 或者直接运行这个脚本文件 xff1a xff08 如果你没有在 bracsh文件中加入source xff0c 建议加入或者在脚本文件的上面中添加source xff0c
  • 004-S500无人机-相关的器件参数以及计算

    这篇博客主要是记录S500无人机的相关器件的参数 xff0c 参数的来源来源于holybro官网 xff1a https shop holybro com 我这里进行参数的归纳以及计算 一 电机 xff08 2216 880kv xff09
  • TX2 学习记录(开启板载/USB摄像头)

    刚拿到手一个TX2 xff0c 简单地学习一下这块板子 xff0c 因为是学长留下来的板子 xff0c 所以刷机的步骤我就省略了 xff0c 各位小伙伴可以参考其他大佬的博客进行刷机 xff0c 再来就记录一下一些操作指令吧 打开USB摄像
  • ubuntu16.04中进行ROS通信编程

    ROS通信学习 基础知识学习字段ROS通信小例子一 创建一个工作区二 创建一个ROS工程包三 创建通信的发 收节点四 测试程序的正确性 图像ROS通信小例子视频ROS通信小例子多机ROS通信 基础知识学习 x1f31f 话题与服务的区别 话
  • 2021电赛F题智能送药小车方案分析(openMV数字识别,红线循迹,STM32HAL库freeRTOS,串级PID快速学习,小车自动返回)

    2021全国大学生电子设计竞赛F题智能送药小车 前提 xff1a 本篇文章重在分享自己的心得与感悟 xff0c 我们把最重要的部分 xff0c 摄像头循迹 xff0c 摄像头数字识别问题都解决了 xff0c 有两种方案一种是openARTm
  • CARLA常见错误解决方案以及常见的问题解决方案

    记录Linux环境 Windows环境下常见的运行自动驾驶仿真器CARLA出现的错误 问题1 问题1比较基础 xff0c 创建虚拟环境以及删除虚拟环境 conda create span class token operator span
  • cmd找不到conda以及通过cmd启用Anaconda中的Python环境(base)

    问题 xff1a 在cmd中输入python无法进入或启用python ipython conda jupyter notebook 一 解决方法 xff1a 在系统环境中添加Anaconda路径 lt 1 gt 1 打开高级系统设置 xf
  • c语言实现strcat函数

    char strcat char strDestination const char strSource 一 函数介绍 作用 xff1a 连接字符串的函数 xff0c 函数返回指针 xff0c 两个参数都是指针 xff0c 第一个参数所指向
  • C/C++的static关键字作用(转载)

    一 限制符号的作用域只在本程序文件 若变量或函数 xff08 统称符号 xff09 使用static修饰 xff0c 则只能在本程序文件内使用 xff0c 其他程序文件不能调用 xff08 非static的可以通过extern 关键字声明该
  • crc校验

    参考链接 xff1a https www cnblogs com esestt archive 2007 08 09 848856 html 一 CRC校验原理 1 CRC校验全称为循环冗余校验 xff08 Cyclic Redundanc
  • ubuntu安装eclipse教程

    在安装eclipse之前 xff0c 要先安装JDK xff0c 一 安装JDK 1 从官网上下载JDK 链接 xff1a https www oracle com java technologies downloads 选择的jdk文件一
  • UDP通信入门篇

    UDP通信属于网络通信中的一种方式 xff0c 需要用套接字来进行通信 初接触UDP通信时 xff0c 不知道需要链接静态库 pragma comment lib ws2 32 lib xff0c 导致自己在前期浪费了很多时间去排查问题 除
  • window11配置深度学习环境

    Anaconda 43 PyCharm 43 CUDA 43 CUDNN 43 PyTorch 1 Anaconda安装 下载路径 xff1a https www anaconda com 安装方式 xff1a 以管理员身份安装 中间选项
  • python配置opencv环境后,读取图片,报错:can‘t open/read file: check file path/integrity

    运行出错代码 xff1a import cv2 import numpy as np image 61 cv2 imread 39 C Pictures 桌面背景图片切换 wallhaven 6oq1k7 jpg 39 cv2 IMREAD
  • 断言

    代码中放置一些假设 xff0c 通过判断假设是否为真 xff0c 进而判断程序是否正确 断言就是用来测试程序中的假设是否正确的 xff0c 若果假设被违反 xff0c 那么就中断程序的执行 断言assert是定义在assert h中的 宏
  • STM32输出SPWM波,HAL库,cubeMX配置,滤波后输出1KHz正弦波

    SPWM波 对于功率方向 输出SPWM波是必须要掌握的 工程 stm32生成spwm代码Keil工程链接资源 引用spwm波定义 PWM波形就是指占空比可变的波形 SPWM波形是指脉冲宽度按正弦规律变化且和正弦波等效的PWM波形 两者的区别
  • C语言链表写法,练习链表

    C语言链表写法 xff0c 练习链表 建立要一个文件 xff1a LinkList h 内容 xff1a span class token macro property span class token directive keyword
  • 树莓派摄像头 C++ OpenCV YoloV3 实现实时目标检测

    树莓派摄像头 C 43 43 OpenCV YoloV3 实现实时目标检测 本文将实现树莓派摄像头 C 43 43 OpenCV YoloV3 实现实时目标检测 xff0c 我们会先实现树莓派对视频文件的逐帧检测来验证算法流程 xff0c