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

树莓派摄像头 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/

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

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

相关文章

【实战】记录一次服务器挖矿病毒处理

信息收集及kill&#xff1a; 查看监控显示长期CPU利用率超高&#xff0c;怀疑中了病毒 top 命令查看进程资源占用&#xff1a; netstat -lntupa 命令查看有无ip进行发包 netstat -antp 然而并没有找到对应的进程名 查看java进程和solr进程 ps aux &#xff1a;查看所有进程…

ag 搜索工具参数详解

ag 搜索工具参数详解 Ag 是类似ack&#xff0c; grep的工具&#xff0c;它来在文件中搜索相应关键字。 官方列出了几点选择它的理由&#xff1a; 它比ack还要快 &#xff08;和grep不在一个数量级上&#xff09;它会忽略.gitignore和.hgignore中的匹配文件如果有你想忽略的文…

CVE-2013-4547 文件名逻辑漏洞

搭建环境&#xff0c;访问 8080 端口 漏洞说明&#xff1a; Nginx&#xff1a; Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;在BSD-like 协议下发行。其特点是占有内存少&#xff0c;并发能力强&#xf…

CVE-2017-7529Nginx越界读取缓存漏洞POC

漏洞影响 低危&#xff0c;造成信息泄露&#xff0c;暴露真实ip等 实验内容 漏洞原理 通过查看patch确定问题是由于对http header中range域处理不当造成&#xff0c;焦点在ngx_http_range_parse 函数中的循环&#xff1a; HTTP头部range域的内容大约为Range: bytes4096-81…

Linux命令行性能监控工具大全

Linux命令行性能监控工具大全 作者&#xff1a;Arnold Lu 原文&#xff1a;https://www.cnblogs.com/arnoldlu/p/9462221.html 关键词&#xff1a;top、perf、sar、ksar、mpstat、uptime、vmstat、pidstat、time、cpustat、munin、htop、glances、atop、nmon、pcp-gui、collect…

Weblogic12c T3 协议安全漏洞分析【CVE-2020-14645 CVE-2020-2883 CVE-2020-14645】

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 关注公众号&#xff1a;b1gpig信息安全&#xff0c;文章推送不错过 ## 前言 WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于JAV…

Getshell总结

按方式分类&#xff1a; 0x01注入getshell&#xff1a; 0x02 上传 getwebshell 0x03 RCE getshell 0x04 包含getwebshell 0x05 漏洞组合拳getshell 0x06 系统层getcmdshell 0x07 钓鱼 getcmdshell 0x08 cms后台getshell 0x09 红队shell竞争分析 0x01注入getshell&#xff1a;…

python 到 poc

0x01 特殊函数 0x02 模块 0x03 小工具开发记录 特殊函数 # -*- coding:utf-8 -*- #内容见POC.demo; POC.demo2 ;def add(x,y):axyprint(a)add(3,5) print(------------引入lambad版本&#xff1a;) add lambda x,y : xy print(add(3,5)) #lambda函数,在lambda函数后面直接…

CVE-2021-41773 CVE-2021-42013 Apache HTTPd最新RCE漏洞复现 目录穿越漏洞

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; CVE-2021-41773漏洞描述&#xff1a; Apache HTTPd是Apache基金会开源的一款流行的HTTP服务器。2021年10月8日Apache HTTPd官方发布安全更新&#xff0c;披…

SSRF,以weblogic为案例

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 复习一下ssrf的原理及危害&#xff0c;并且以weblog的ssrf漏洞为案例 漏洞原理 SSRF(Server-side Request Forge, 服务端请求伪造) 通常用于控制web进而…

C++11 右值引用、移动语义、完美转发、万能引用

C11 右值引用、移动语义、完美转发、引用折叠、万能引用 转自&#xff1a;http://c.biancheng.net/ C中的左值和右值 右值引用可以从字面意思上理解&#xff0c;指的是以引用传递&#xff08;而非值传递&#xff09;的方式使用 C 右值。关于 C 引用&#xff0c;已经在《C引用…

Java安全(一) : java类 | 反射

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 1.java基础 Java平台共分为三个主要版本Java SE&#xff08;Java Platform, Standard Edition&#xff0c;Java平台标准版&#xff09;、Java EE&#xff0…

对某公司一次弱口令到存储型xss挖掘

转自我的奇安信攻防社区文章:https://forum.butian.net/share/885 免责声明: 渗透过程为授权测试,所有漏洞均以提交相关平台,博客目的只为分享挖掘思路和知识传播** 涉及知识: xss注入及xss注入绕过 挖掘过程: 某次针对某目标信息搜集无意发现某工程公司的项目招标平台 …

C++11新特性选讲 语言部分 侯捷

C11新特性选讲 语言部分 侯捷 本课程分为两个部分&#xff1a;语言的部分和标准库的部分。只谈新特性&#xff0c;并且是选讲。 本文为语言部分笔记。 语言 Variadic Templatesmove semanticsautoRange-based for loopInitializer listLambdas… 标准库 type_traitsunodered…

java安全(二):JDBC|sql注入|预编译

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 1 JDBC基础 JDBC(Java Database Connectivity)是Java提供对数据库进行连接、操作的标准API。Java自身并不会去实现对数据库的连接、查询、更新等操作而是通…

java安全(三)RMI

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 1.RMI 是什么 RMI(Remote Method Invocation)即Java远程方法调用&#xff0c;RMI用于构建分布式应用程序&#xff0c;RMI实现了Java程序之间跨JVM的远程通信…

java安全(四) JNDI

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 1.JNDI JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目录接口。通过调用JNDI的API应用程序可以定位资源和其他程序对象。JNDI是Java…

java安全(五)java反序列化

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 1. 序列化 在调用RMI时,发现接收发送数据都是反序列化数据. 例如JSON和XML等语言,在网络上传递信息,都会用到一些格式化数据,大多数处理方法中&#xff0c…

git merge和rebase的区别与选择

git merge和rebase的区别与选择 转自&#xff1a;https://github.com/geeeeeeeeek/git-recipes/wiki/5.1-%E4%BB%A3%E7%A0%81%E5%90%88%E5%B9%B6%EF%BC%9AMerge%E3%80%81Rebase-%E7%9A%84%E9%80%89%E6%8B%A9#merge BY 童仲毅&#xff08;geeeeeeeeekgithub&#xff09; 这是一篇…

java安全(六)java反序列化2,ysoserial调试

给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; 给个关注&#xff1f;宝儿&#xff01; ysoserial 下载地址&#xff1a;https://github.com/angelwhu/ysoserial ysoserial可以让⽤户根据⾃⼰选择的利⽤链&#xff0c;⽣成反序列化利⽤数据&…