AI部署篇---交叉编译aarch64的OpenCV4.5.5+YOLOv5Face+RK3588人脸检测并部署到RK3588实现
这里写自定义目录标题
一、交叉编译OpenCV4.5.5
本文使用的环境是ubuntu20.04的x86_64架构
$ uname -a
Linux kfb-sy08-031 5.15.0-69-generic #76~20.04.1-Ubuntu SMP Mon Mar 20 15:54:19 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
1.1 安装相关工具
# 安装相关本地依赖库
sudo apt-get install -y unzip bzip2 expat gpgv2 cpp-aarch64-linux-gnu g++-aarch64-linux-gnu
sudo apt-get install build-essential libgtk2.0-dev libgtk-3-dev libavcodec-dev libavformat-dev libjpeg-dev libswscale-dev libtiff5-dev
# 安装cmake、cmake-gui
sudo apt-get install cmake cmake-qt-gui
1.2 下载源相关源代码
OpenCV4.5.5下载link OpenCV_contrib link
# 创建目录,通过命令下载
cd ~ && mkdir -p ai/opencv/ && cd ~/ai/opencv
# 下载和解压
wget -O opencv.zip https://github.com/opencv/opencv/archive/4.x.zip
wget -O opencv_contrib.zip https://github.com/opencv/opencv_contrib/archive/4.x.zip
unzip opencv.zip
unzip opencv_contrib.zip
# 或
git clone https://github.com/opencv/opencv.git
git clone https://github.com/opencv/opencv_contrib.git
mv opencv opencv455 && cd opencv455
git checkout 4.5.5
# 创建编译目录
mkdir -p aarch64_build && cd aarch64_build
1.3 配置cmake环境(使用cmake-gui)
# 启动cmake-gui
cmak-gui
# where is the source code: 就是刚下载的OpenCV解压文件夹
/home/hgh/ai/opencv/opencv-4.5.5
# where to build the binaries: 就是通过上面的OpenCV文件夹编译的保存结果
/home/hgh/ai/opencv/opencv-4.5.5/arch_build
# 增加变量 点击Add Entry
Name: CMAKE_AR
Type: FILEPATH
Value: /usr/bin/aarch64-linux-gnu-ar
# windows 使用mingw
Value D:/ProgramData/mingw64/bin/ar.exe
#下一步的
"Specify the generator for this project" 选"MinGW Makefiles"
# gcc和g++ 选择自己安装的mingw中bin目录下的gcc.exe 和 g++.exe
# Value 通过which命令查询路径
$ which aarch64-linux-gnu-ar
/usr/bin/aarch64-linux-gnu-ar
# Windows可以安装everthing软件,搜万物!
链接: link.

点击Next 跳到下图界面,选择自己下载的OpenCV解压包中的文件,默认会跳转到选择对应的文件夹中
在Search中搜索: WITH_GTK 勾选选项
继续搜索: OPENCV_EXTRA_MODULES_PATH 编译使用扩展包路径(增加OpenCV_contrib模块)
/home/hgh/ai/opencv/opencv_contrib-4.5.5/modules
查看安装路径:CMAKE_INSTALL_PREFIX (一般为编译文件夹下的install)
1.4 生成OpenCV的makefile配置文件
点击: Configure
再点击:Generate
检查是否报错,网络原因下载某些包问题,多生成几次。不报错可以用终端进行下面操作!
1.5 终端make编译和安装
cd ~/ai/opencv/opencv-4.5.5/arch_build
make -j8
# 按需是否需要安装编译好的OpenCV库
make install
1.6 使用cmake测试
mkdir rknn_demo && cd rknn_demo
touch CMakeLists.txt
CmakeLists.txt 内容【 cmake学习参考:cmake菜谱】
# 设置cmake版本要求
cmake_minimum_required(VERSION 3.4.1)
# 项目工程名称
project(test_opencv)
# 设置编译环境、架构工具 (下面这个设置了,本地的x86架构不能运行)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
message("CMAKE_SOURCE_DIR is ====> ${CMAKE_SOURCE_DIR}")
# set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/../opencv/opencv-linux-aarch64/share/OpenCV) # set(OpenCV_DIR /home/hgh/source/code/face_detection/yolov5-face-landmarks-opencv-v2/3rdparty/opencv/opencv-linux-aarch64/share/OpenCV)
set(OpenCV_DIR /home/hgh/ai/opencv/opencv-4.5.5/arch_build/install/share/opencv4)
# 查找默认环境的OpenCV
find_package(OpenCV REQUIRED)
# include_directories( ${OpenCV_INCLUDE_DIRS})
# # 自己本地编译的opencv_aarch64版本
# set(OpenCV_INCLUDE_DIRS /home/hgh/ai/opencv/opencv-4.5.5/arch_build/install/include/opencv4)
message("OpenCV_DIR is --> ${OpenCV_DIR}")
# # 引用头文件
# include_directories(BEFORE /home/hgh/ai/opencv/opencv-4.5.5/arch_build/install/include/opencv4)
# include_directories(BEFORE /home/hgh/ai/opencv/opencv-4.5.5/arch_build/install/include/opencv4/opencv2)
# # 引用库文件
link_directories(/home/hgh/ai/opencv/opencv-4.5.5/arch_build/install/lib)
message("link_directories is --> ")
# 打印相关信息
message(STATUS "opencv library status: ")
message(STATUS "\t version ${OpenCV_VERSION}")
message(STATUS "\t libraries: ${OpenCV_LIBS}")
message(STATUS "\t include path ${OpenCV_INCLUDE_DIRS}")
message("---------------------------------------")
add_executable(opencv_test
test.cpp
)
# 若上面的 link_directories 指定文件夹中没有对应的链接库,那么会到本地环境搜索对应的库,但是不同架构的so会报错
target_link_libraries(opencv_test
${OpenCV_LIBS}
)
# 也可以这样设置
# target_link_libraries(yolo_test stdc++ opencv_core opencv_imgproc opencv_imgcodecs opencv_dnn dl rt)
test.cpp 通过官方demo修改的
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#define w 400
using namespace cv;
void MyEllipse( Mat img, double angle );
void MyFilledCircle( Mat img, Point center );
void MyPolygon( Mat img );
void MyLine( Mat img, Point start, Point end );
int main( void ){
char atom_window[] = "Drawing 1: Atom";
char rook_window[] = "Drawing 2: Rook";
Mat atom_image = Mat::zeros( w, w, CV_8UC3 );
Mat rook_image = Mat::zeros( w, w, CV_8UC3 );
MyEllipse( atom_image, 90 );
MyEllipse( atom_image, 0 );
MyEllipse( atom_image, 45 );
MyEllipse( atom_image, -45 );
MyFilledCircle( atom_image, Point( w/2, w/2) );
MyPolygon( rook_image );
rectangle( rook_image,
Point( 0, 7*w/8 ),
Point( w, w),
Scalar( 0, 255, 255 ),
FILLED,
LINE_8 );
MyLine( rook_image, Point( 0, 15*w/16 ), Point( w, 15*w/16 ) );
MyLine( rook_image, Point( w/4, 7*w/8 ), Point( w/4, w ) );
MyLine( rook_image, Point( w/2, 7*w/8 ), Point( w/2, w ) );
MyLine( rook_image, Point( 3*w/4, 7*w/8 ), Point( 3*w/4, w ) );
// imshow( atom_window, atom_image );
imwrite( "./atom_window.jpg", atom_image );
// moveWindow( atom_window, 0, 200 );
// imshow( rook_window, rook_image );
imwrite( "./rook_window.jpg", rook_image );
// moveWindow( rook_window, w, 200 );
// waitKey( 0 );
return(0);
}
void MyEllipse( Mat img, double angle )
{
int thickness = 2;
int lineType = 8;
ellipse( img,
Point( w/2, w/2 ),
Size( w/4, w/16 ),
angle,
0,
360,
Scalar( 255, 0, 0 ),
thickness,
lineType );
}
void MyFilledCircle( Mat img, Point center )
{
circle( img,
center,
w/32,
Scalar( 0, 0, 255 ),
FILLED,
LINE_8 );
}
void MyPolygon( Mat img )
{
int lineType = LINE_8;
Point rook_points[1][20];
rook_points[0][0] = Point( w/4, 7*w/8 );
rook_points[0][1] = Point( 3*w/4, 7*w/8 );
rook_points[0][2] = Point( 3*w/4, 13*w/16 );
rook_points[0][3] = Point( 11*w/16, 13*w/16 );
rook_points[0][4] = Point( 19*w/32, 3*w/8 );
rook_points[0][5] = Point( 3*w/4, 3*w/8 );
rook_points[0][6] = Point( 3*w/4, w/8 );
rook_points[0][7] = Point( 26*w/40, w/8 );
rook_points[0][8] = Point( 26*w/40, w/4 );
rook_points[0][9] = Point( 22*w/40, w/4 );
rook_points[0][10] = Point( 22*w/40, w/8 );
rook_points[0][11] = Point( 18*w/40, w/8 );
rook_points[0][12] = Point( 18*w/40, w/4 );
rook_points[0][13] = Point( 14*w/40, w/4 );
rook_points[0][14] = Point( 14*w/40, w/8 );
rook_points[0][15] = Point( w/4, w/8 );
rook_points[0][16] = Point( w/4, 3*w/8 );
rook_points[0][17] = Point( 13*w/32, 3*w/8 );
rook_points[0][18] = Point( 5*w/16, 13*w/16 );
rook_points[0][19] = Point( w/4, 13*w/16 );
const Point* ppt[1] = { rook_points[0] };
int npt[] = { 20 };
fillPoly( img,
ppt,
npt,
1,
Scalar( 255, 255, 255 ),
lineType );
}
void MyLine( Mat img, Point start, Point end )
{
int thickness = 2;
int lineType = LINE_8;
line( img,
start,
end,
Scalar( 0, 0, 0 ),
thickness,
lineType );
}
1.6.1 编译
# 在CMakeList.txt同级目录中
# cmake会创建一个aarch_build文件夹并在里面编译生成文件
cmake -B aarch_build && cd aarch_build
# 不报错直接make
make

1.6.2 链接RK3588并进行测试
- 使用type-c 连接板子和PC机
- 安装adb:sudo apt install adb
- adb检测设备(有设备号证明可以):adb devices
(base) hgh@$ adb devices
List of devices attached
(base) hgh@$ adb devices
List of devices attached
8xxxxxxxxxxxx263 device- 使用adb push传输文件: adb push ./your/path/sourceFIle /rk3588/root/demo_test/target_file
5.adb测试:adb shell

# 执行可执行文件
./opencv_test
生成对应的结果图!!
显示结果图为:
二、OpenCV推理YOLOv5Face进行人脸检测
2.1 下载OpenCV的人脸检测算法权重
百度网盘下载 提取码: 6kjd
参考链接:yolov5-face-landmarks-opencv-v2
2.2检测detect_face.cpp代码
#include <fstream>
#include <sstream>
#include <iostream>
// #include <cstring>
#include <opencv2/dnn.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace cv;
using namespace dnn;
using namespace std;
struct Net_config
{
float confThreshold; // Confidence threshold
float nmsThreshold; // Non-maximum suppression threshold
float objThreshold; // Object Confidence threshold
string netname;
};
class YOLO
{
public:
YOLO(Net_config config);
int detect(Mat &frame);
private:
const float anchors[3][6] = {{4, 5, 8, 10, 13, 16}, {23, 29, 43, 55, 73, 105}, {146, 217, 231, 300, 335, 433}};
const float stride[3] = {8.0, 16.0, 32.0};
const int inpWidth = 640;
const int inpHeight = 640;
float confThreshold;
float nmsThreshold;
float objThreshold;
char netname[20];
Net net;
void drawPred(float conf, int left, int top, int right, int bottom, Mat &frame, vector<int> landmark);
void sigmoid(Mat *out, int length);
};
static inline float sigmoid_x(float x)
{
return static_cast<float>(1.f / (1.f + exp(-x)));
}
YOLO::YOLO(Net_config config)
{
cout << "Net use " << config.netname << endl;
this->confThreshold = config.confThreshold;
this->nmsThreshold = config.nmsThreshold;
this->objThreshold = config.objThreshold;
// strcpy_s(this->netname, config.netname.c_str());
strcpy(this->netname, config.netname.c_str());
string modelFile = this->netname;
// modelFile += "-face.onnx";
this->net = readNet(modelFile);
}
void YOLO::drawPred(float conf, int left, int top, int right, int bottom, Mat &frame, vector<int> landmark) // Draw the predicted bounding box
{
// Draw a rectangle displaying the bounding box
rectangle(frame, Point(left, top), Point(right, bottom), Scalar(0, 0, 255), 2);
// Get the label for the class name and its confidence
string label = format("%.2f", conf);
// Display the label at the top of the bounding box
int baseLine;
Size labelSize = getTextSize(label, FONT_HERSHEY_SIMPLEX, 0.5, 1, &baseLine);
top = max(top, labelSize.height);
// rectangle(frame, Point(left, top - int(1.5 * labelSize.height)), Point(left + int(1.5 * labelSize.width), top + baseLine), Scalar(0, 255, 0), FILLED);
putText(frame, label, Point(left, top), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 255, 0), 1);
// // draw featrue points 绘制特征点
// for (int i = 0; i < 5; i++)
// {
// circle(frame, Point(landmark[2 * i], landmark[2 * i + 1]), 1, Scalar(0, 255, 0), -1);
// }
}
void YOLO::sigmoid(Mat *out, int length)
{
float *pdata = (float *)(out->data);
int i = 0;
for (i = 0; i < length; i++)
{
pdata[i] = 1.0 / (1 + expf(-pdata[i]));
}
}
int YOLO::detect(Mat &frame)
{
Mat blob;
blobFromImage(frame, blob, 1 / 255.0, Size(this->inpWidth, this->inpHeight), Scalar(0, 0, 0), true, false);
this->net.setInput(blob);
vector<Mat> outs;
this->net.forward(outs, this->net.getUnconnectedOutLayersNames());
/////generate proposals
vector<float> confidences;
vector<Rect> boxes;
vector<vector<int>> landmarks;
float ratioh = (float)frame.rows / this->inpHeight, ratiow = (float)frame.cols / this->inpWidth;
int n = 0, q = 0, i = 0, j = 0, nout = 16, row_ind = 0, k = 0; /// xmin,ymin,xamx,ymax,box_score,x1,y1, ... ,x5,y5,face_score
for (n = 0; n < 3; n++) /// 特征图尺度
{
int num_grid_x = (int)(this->inpWidth / this->stride[n]);
int num_grid_y = (int)(this->inpHeight / this->stride[n]);
for (q = 0; q < 3; q++) /// anchor
{
const float anchor_w = this->anchors[n][q * 2];
const float anchor_h = this->anchors[n][q * 2 + 1];
for (i = 0; i < num_grid_y; i++)
{
for (j = 0; j < num_grid_x; j++)
{
float *pdata = (float *)outs[0].data + row_ind * nout;
float box_score = sigmoid_x(pdata[4]);
if (box_score > this->objThreshold)
{
float face_score = sigmoid_x(pdata[15]);
// if (face_score > this->confThreshold)
//{
float cx = (sigmoid_x(pdata[0]) * 2.f - 0.5f + j) * this->stride[n]; /// cx
float cy = (sigmoid_x(pdata[1]) * 2.f - 0.5f + i) * this->stride[n]; /// cy
float w = powf(sigmoid_x(pdata[2]) * 2.f, 2.f) * anchor_w; /// w
float h = powf(sigmoid_x(pdata[3]) * 2.f, 2.f) * anchor_h; /// h
int left = (cx - 0.5 * w) * ratiow;
int top = (cy - 0.5 * h) * ratioh;
confidences.push_back(face_score);
boxes.push_back(Rect(left, top, (int)(w * ratiow), (int)(h * ratioh)));
vector<int> landmark(10);
for (k = 5; k < 15; k += 2)
{
const int ind = k - 5;
landmark[ind] = (int)(pdata[k] * anchor_w + j * this->stride[n]) * ratiow;
landmark[ind + 1] = (int)(pdata[k + 1] * anchor_h + i * this->stride[n]) * ratioh;
}
landmarks.push_back(landmark);
//}
}
row_ind++;
}
}
}
}
// Perform non maximum suppression to eliminate redundant overlapping boxes with
// lower confidences
vector<int> indices;
NMSBoxes(boxes, confidences, this->confThreshold, this->nmsThreshold, indices);
for (size_t i = 0; i < indices.size(); ++i)
{
int idx = indices[i];
Rect box = boxes[idx];
this->drawPred(confidences[idx], box.x, box.y,
box.x + box.width, box.y + box.height, frame, landmarks[idx]);
}
return indices.size();
}
int main(int argc, char **argv)
{
char* model_name = NULL;
if (argc != 3)
{
cout << argv[0] << " please input images <jpg> "<< endl;
return -1;
}
model_name = (char*)argv[1];
// Net_config yolo_nets = {0.3, 0.5, 0.3, "weights/yolov5s"}; /// choice = [yolov5s, yolov5m, yolov5l]
Net_config yolo_nets = {0.3, 0.5, 0.3, model_name}; /// choice = [yolov5s, yolov5m, yolov5l]
YOLO yolo_model(yolo_nets);
char *imgpath = argv[2];
cout << "Usage: " << imgpath << "<rknn model> <jpg> \n";
Mat srcimg = imread(imgpath);
int nums = yolo_model.detect(srcimg);
cout << "检测的人数为: " << nums << endl;
imwrite("./result_det.jpg", srcimg);
return 0;
}
2.3同级目录下的CMakeLists.txt文件代码
# 设置cmake版本要求
cmake_minimum_required(VERSION 3.4.1)
# 项目工程名称
project(test_opencv_face)
# 设置编译环境、架构工具 (下面这个设置了,本地的x86架构不能运行)
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
message("CMAKE_SOURCE_DIR is ====> ${CMAKE_SOURCE_DIR}")
set(OpenCV_DIR /home/hgh/ai/opencv/opencv-4.5.5/arch_build/install/share/opencv4)
# 查找默认环境的OpenCV
find_package(OpenCV REQUIRED)
message("OpenCV_DIR is --> ${OpenCV_DIR}")
# # 引用头文件
# include_directories(BEFORE /home/hgh/ai/opencv/opencv-4.5.5/arch_build/install/include/opencv4)
# include_directories(BEFORE /home/hgh/ai/opencv/opencv-4.5.5/arch_build/install/include/opencv4/opencv2)
# # 引用库文件
link_directories(/home/hgh/ai/opencv/opencv-4.5.5/arch_build/install/lib)
message("link_directories is --> ")
# 打印相关信息
message(STATUS "opencv library status: ")
message(STATUS "\t version ${OpenCV_VERSION}")
message(STATUS "\t libraries: ${OpenCV_LIBS}")
message(STATUS "\t include path ${OpenCV_INCLUDE_DIRS}")
message("---------------------------------------")
add_executable(yolo_test
detect_face.cpp
)
# 若上面的 link_directories 指定文件夹中没有对应的链接库,那么会到本地环境搜索对应的库,但是不同架构的so会报错
target_link_libraries(yolo_test
${OpenCV_LIBS}
)
2.4编译源文件并放到rk3588执行
# 在CMakeList.txt同级目录中
# cmake会创建一个aarch_build文件夹并在里面编译生成文件
cmake -B aarch_build && cd aarch_build
# 不报错直接make
make
生成的目录中yolo_test文件
ubuntu20.04与rk开发板使用filezilla进行文件传输:下载链接
使用electrem进行ssh连接开发板(也可以传输文件,开源好用!):下载链接
上传onnx、人脸检测图、OpenCV的so库到rk3588中
在当前目录lib中放入OpenCV的so库,并配置链接环境库变量(不然会报缺失so库):
export LD_LIBRARY_PATH = ./lib
运行可执行文件,格式./yolo_test model.onnx image.jpg :
# 增加可执行权限
chmod +x yolo_test
# 执行
./yolo_test yolov5s-face.onnx selfie.jpg

图中输出检测结果图result_det.jpg和识别的人脸个数
注意事项
1.cmake编译引用OpenCV动态库so报错
交叉编译好的OpenCV4.5.5版本,在ubuntu系统中的cmakelists.txt中引用了动态库so。先在CMakeLists.txt文件中按照链接的path路径 link_directories(path),若未找到会查找本地的so库。这样会导致编译aarch64架构文件,却使用了x86编译的so,会导致make报错,报错现象为:
error adding symbols: file in wrong format
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/yolo_test.dir/build.make:104:yolo_test] 错误 1
make[1]: *** [CMakeFiles/Makefile2:7

解决方法(该解决办法会导致移植到另一部机子依赖库出问题,OpenCVModules.cmake 里面包含了编译的绝对路径):把编译的aarch_build文件夹中生成的*.cmake文件,拷贝到install/share/opencv4 中。make编译链接就不会报错!!!【见下图】
~~
正确打开方式:设置OpenCV_DIR的路径指定为install中的lib/cmake/opencv4路径,后面的find_package(OpenCV REQUIRED)就会搜索到自己编译的OpenCV的install库中。
比如:set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/3rdparty/rk3588_opencv455/lib/cmake/opencv4)
参考链接:
opencv在 Cmakelist的写法以及编译详解: https://blog.csdn.net/qq_41612863/article/details/122149124
交叉编译aarch64版本opencv-4.5.5: https://blog.csdn.net/heqingchun16/article/details/129054037
ubuntu20.04的vscode编译OpenCV4.5.5: https://blog.csdn.net/xiangfengl/article/details/122945924
更多推荐

所有评论(0)