环境配置方式:🚀 整个过程采用远程的方式,记录树莓派部署 YOLOv5s 的过程。

准备:

  • 树莓派 5-4B
  • 树莓派系统(安装的官方 64 位系统)
  • USB 摄像头

# 获取 yolov5s.pt 权值文件

使用的是轻量的 yolov5s 模型,在本地训练后,通过命令发送发到树莓派上:

  1. 首先查看树莓派地址:
ifconfig  # 查看地址


连接的 wify,可以看到所属的地址端口。

  1. 将本地训练好的 yolov5s.pt 权值文件发送到树莓派上:
scp yolov5s.pt <用户名>@<树莓派地址>:<保存文件地址>

# 树莓派环境搭建

本打算参考其他博主的方法,将 pt 权值文件转为 ONNX 再利用 OpenVINO 转为 IR 模型部署到树莓派上。

在本地成功得到 IR 模型后,树莓派配置 OpenVINO 环境加速推理时,遇到问题有点多😤,准备有空再进行尝试。

树莓派中运行 yolov5s 的环境使用 Anaconda 配置和管理,步骤如下:

  • 安装 Anaconda
  • 创建虚拟环境
  • 下载 yolov5 源码
  • 安装依赖库
  1. 安装 Anaconda🌱
    下载并安装 Anaconda 以管理 Python 环境,分别在终端执行:
wget https://repo.anaconda.com/archive/Anaconda3-2021.05-Linux-x86_64.sh
bash Anaconda3-2021.05-Linux-x86_64.sh
  1. 创建虚拟环境并安装 YOLOv5 🌱
conda create -n yolov5 python=3.9 -y                # 创建名为 yolov5 的虚拟环境,并指定 Python 版本为 3.9
    conda activate yolov5                               # 激活虚拟环境
    git clone https://github.com/ultralytics/yolov5.git # 克隆 YOLOv5 源码
    cd yolov5
    pip install -r requirements.txt                     # 安装 YOLOv5 所需的依赖库

# 检测图片

  1. 运行程序🌱
python detect.py \
    --weights yolov5s.pt \   # 没有准备可省略该行,自动下载官方权值文件
    --img 320 \              # 指定图片大小,压缩图片,加快检测速度,但是清晰度降低
    --source /image/1.jpg \  # 指定图片路径
    --device cpu \           # 使用 cpu 进行推理

使用的 cpu 进行推理,速度较慢,看资料显示树莓派也是有 GPU 的,但是因为树莓派 GPU 的算力太低,所以还是使用 cpu 进行推理,如果想使用 GPU 进行推理,需要安装 CUDA ,而且树莓派安装 CUDA 比较麻烦。

  1. 检测效果如下🌱:


# 开启摄像头实时检测

🍋检测视频流,遇到无法弹出实时检测界面,这让之前的工作毫无成就感😳,之后通过安装 lightdm 来解决的。

lightdm 能够提供图形界面服务,使 OpenCV cv2.imshow() 能通过 VNC 的虚拟显示端口渲染窗口。

  1. 安装 lightdm 🌱
sudo apt install lightdm                # 安装图形界面管理器
    sudo dpkg-reconfigure lightdm          # 绑定显示服务到 VNC
    sudo systemctl restart lightdm          # 重启服务使配置生效
  1. 查看摄像头🌱
    使用 v4l2-ctl 指令查看所有视频设备
v4l2-ctl --list-devices

可以看到识别到的 USB 摄像头 /dev/video0/dev/video1 ,使用 0 作为 source 参数。

  1. 运行程序🌱
python detect.py \
    --weights yolov5s.pt \
    --source 0 \             # 使用摄像头(0 表示第一个摄像头)
    --view-img \             # 启用实时画面显示 --img-size 320
    --device cpu
  1. 检测效果如下🌱:

🍀由于树莓派不支持 GPU,所以速度并不快,下面尝试使用 OpenCVdnn 模块进行推理,将模型转为 ONNX 格式,再使用 OpenCV 进行推理,速度会快很多。

# onnx 推理(C++)

# 安装 Opencv

安装 Opencv 参考链接:安装 OpenCV

# 获取 yolov5s.pt 权值文件

yolov5s.pt 权值文件转为 ONNX 格式,并导出为 best.onnx 权值文件。

python export.py --weights weights/best.pt --include onnx

# 编写推理代码

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <chrono>  // 添加时间测量头文件
using namespace cv;
using namespace cv::dnn;
std::vector<std::string> classes = {
	"defective_insulator","broken_defect","good_insulator","flashover_defect"
};
int main() {
	// 1. 输入图片路径
    std::string img_path = "images/insulator.jpg"; // 输入图片路径
    Mat frame = imread(img_path);
    if(frame.empty()) {
        std::cerr << "Error: Could not read the image." << std::endl;
        return -1;
    }
	// 2. 加载模型
    Net net =  cv::dnn::readNetFromONNX("weights/ZJ/best.onnx");
	net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
	net.setPreferableTarget(cv::dnn::DNN_TARGET_OPENCL);
	// 打印模型信息
    // std::cout << "Network layers:" << std::endl;
    // for (int i = 0; i < net.getLayerNames().size(); i++) {
    //     std::cout << net.getLayerNames()[i] << std::endl;
    // }
	
    if (net.empty()) {
        std::cerr << "Error: Could not load the neural network." << std::endl;
        return -1;
    }
	// 3. 预处理参数设置
    const int input_width = 512;
    const int input_height = 512;
    float x_factor = frame.cols / (float)input_width;  // 修改为动态计算
    float y_factor = frame.rows / (float)input_height;
	// 预处理计时
	auto preprocess_start = std::chrono::high_resolution_clock::now();
	// 4. 图像预处理(增加 letterbox 处理)
    Mat resized;
    int new_width, new_height;
    if (frame.cols > frame.rows) {
        new_width = input_width;
        new_height = (int)(frame.rows * input_width / (float)frame.cols);
    } else {
        new_height = input_height;
        new_width = (int)(frame.cols * input_height / (float)frame.rows);
    }
    resize(frame, resized, Size(new_width, new_height));
    
    Mat blob = blobFromImage(resized, 1/255.0, Size(input_width, input_height), Scalar(0,0,0), true, false);
	//blob.convertTo (blob, CV_16F);  // 半精度计算
	// 预处理计时结束
	auto preprocess_end = std::chrono::high_resolution_clock::now();
	// 推理计时
	auto inference_start = std::chrono::high_resolution_clock::now();
    net.setInput(blob);
	// 5. 推理后处理
	cv::Mat preds =net.forward();
	// 推理计时结束
	auto inference_end = std::chrono::high_resolution_clock::now();
	// 后处理计时
    auto postprocess_start = std::chrono::high_resolution_clock::now();
	std::vector<int> class_ids;
	std::vector<float> confidences;
	std::vector<Rect> boxes;
	std::cout << "rows: "<< preds.size[1]<< " data: " << preds.size[2] << std::endl;
	// cv::Mat det_output(preds.size[1], preds.size[2], CV_32F, preds.ptr<float>());
	cv::Mat det_output = preds.reshape(1, preds.size[1]); // 避免内存复制
	//In a typical YOLO output, the format is [x_center, y_center, width, height, object_confidence, class_score1, class_score2, ..., class_scoreN] for each bounding box. 
	for (int i = 0; i < det_output.rows; i++) {
		float confidence = det_output.at<float>(i, 4);
		cv::Mat class_scores = det_output.row(i).colRange(5, 5 + classes.size());
		
		Point class_id_point;
		double max_class_score;
		minMaxLoc(class_scores, NULL, &max_class_score, NULL, &class_id_point);
		int class_id = class_id_point.x; 
		float final_confidence = confidence * max_class_score;
		// std::cout << "Final confidence: " << final_confidence << std::endl;
		if (final_confidence < 0.45) {
			continue;
		}
		
		float cx = det_output.at<float>(i, 0);
		float cy = det_output.at<float>(i, 1);
		float ow = det_output.at<float>(i, 2);
		float oh = det_output.at<float>(i, 3);
		int x = static_cast<int>((cx - 0.5 * ow) * x_factor);
		int y = static_cast<int>((cy - 0.5 * oh) * y_factor);
		int width = static_cast<int>(ow * x_factor);
		int height = static_cast<int>(oh * y_factor);
		
        boxes.push_back(Rect(x, y, width, height));
        confidences.push_back(final_confidence);
        class_ids.push_back(class_id);
	}
	std::vector<int> indices;
	dnn::NMSBoxes(boxes, confidences, 0.6, 0.3, indices); 
	// Draw the final bounding boxes
	for (size_t i = 0; i < indices.size(); ++i) {
		int idx = indices[i];
		Rect box = boxes[idx];
		cv::rectangle(frame, cv::Point(box.x, box.y), cv::Point(box.x + box.width, box.y + box.height), cv::Scalar(0, 0, 255), 2, 8);
	
	
	std::string label = classes[class_ids[idx]] + ": " + std::to_string(confidences[idx]);
	putText(frame, label.c_str(), Point(box.x, box.y - 10), FONT_HERSHEY_SIMPLEX, 0.9, Scalar(0, 255, 0), 2);
		}
	auto postprocess_end = std::chrono::high_resolution_clock::now();
	
	// 输出各阶段耗时(单位:ms)
	float preprocess_time = std::chrono::duration_cast<std::chrono::milliseconds>(preprocess_end - preprocess_start).count();
	float inference_time = std::chrono::duration_cast<std::chrono::milliseconds>(inference_end - inference_start).count();
	float postprocess_time = std::chrono::duration_cast<std::chrono::milliseconds>(postprocess_end - postprocess_start).count();
	// 计算总耗时(单位:ms)
	auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(postprocess_end - preprocess_start).count();
	std::cout << "预处理: " << preprocess_time << " ms" << std::endl;
	std::cout << "推理: " << inference_time << " ms" << std::endl; 
	std::cout << "后处理: " << postprocess_time << " ms" << std::endl;
	std::cout << "总耗时: " << duration << " ms" << std::endl;
	
	float fps = 1000.0 / duration;  // 计算 FPS
	std::cout << "FPS: " << fps << std::endl;
	// 6. 显示结果(取消视频循环)
	imshow("Object Detection", frame);
	waitKey(0);  // 改为等待按键
    return 0;
}