在ubuntu环境下交叉编译riscv架构的opencv
在ubuntu环境下交叉编译riscv架构的opencv
任务概述
想要在risc-v架构上使用第三方库,大致的流程是:

难点在于中间的各种编译过程。
安装交叉编译工具链
clone仓库
有人在gitee上放了一个包含所有子模块的镜像,可以直接从这个仓库clone:
git clone https://gitee.com/yushulx/riscv-gnu-toolchain注意,这一步必须用gitclone来获得,否则后面在编译时会有报错说这不是一个git仓库。
安装依赖
apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev编译GCC
这一步需要编译动态链接库版本,进入刚才clone的仓库根目录下,
./configure --prefix=/opt/riscv && make linux添加环境变量
如果之后想用riscv64-unknown-linux-gnu-g++ -o这种命令,就要把刚才编译好的可执行程序加到环境里。具体做法是:
echo 'export PATH=/opt/riscv/bin:$PATH' >> ~/.bashrc
source ~/.bashrc其中/opt/riscv64是默认安装目录,不用改。第二行使环境变量文件立即生效。在终端输入
riscv64-unknown-linux-gnu-g++ --version可以看到交叉编译工具链的版本信息。
编译opencv
下载opencv源码
首先查看licheerv的/lib64目录,发现它预装的opencv版本是4.9.0。由于使用的是动态链接,要求编译时使用的库文件和运行环境中的一样,所以我也安装这个版本。
在宿主机创建如下的目录结构:
📁opencv
├─ 📁config
├─ 📁install
└─ 📁opencv-4.9.0其中opencv-4.9.0是刚才放上去的源码。
安装桌面
这一步不是必做,但是网上能找到的教程都是用gui做的,所以我也不知道怎么用纯命令行来编译。
更新包:
sudo apt update && sudo apt upgrade安装桌面包:
apt install ubuntu-desktop安装显示管理器,后面会有个紫色的窗口让选,就选那个light版本:
apt install lightdm启动显示管理器:
service lightdm start通过vnc登录后就可以进入桌面了。
编译源码
在Ubuntu上安装cmake-gui:
apt install cmake-qt-gui输入cmake-gui启动。
在where is the source code一栏输入opencv-4.9.0的路径;在where to build the binaries中输入刚创建的config文件夹地址:

然后点击configure,选择使用交叉编译:

点next,在Operating System中输入Linux,在Version中输入目标平台的版本。然后编译器C选择安装好的riscv64-unknown-linux-gnu-gcc编译器,C++选择riscv64-unknown-linux-gnu-g++。Target Root选择riscv-gnu-toolchain安装目录:

隐患
在使用4.9.0版本的opencv时,configure会报错失败。我暂时没能找到具体原因,所以我在实际操作时使用的是4.1.1版本的opencv(github直接下载),后续再把4.1.1的lib放在板子上。尚不清楚这样做会有什么后果。
点击Finish,即可开始配置,自动配置后如下图所示。需要手动去掉WITH_GTK和WITH_TIFF选项,将CMAKE_INSTALL_PREFIX的值改为上面创建的install目录。
之后点击Generate即可生成Makefile,生成完成后,进入config目录,输入:
make -jx开始编译。其中x为处理器的线程数。
中途可能遇到一个报错:
[ 21%] Built target libjasper
make: *** [Makefile:163: all] Error 2这是头文件找不到的问题,其实这些头文件是存在于/usr/include目录中的,将其在riscv-gnu-toolchain安装目录的include目录中创建软连接:
ln -s /usr/include /opt/riscv/include注意,这样其实是把整个include文件夹链接到了riscv的include文件夹内,所以需要做一个手动拆包:
cp -r /opt/riscv/include/include/* /opt/riscv/include另一个可能遇到的问题是:
fatal error: zlib.h: No such file or directory解决方法:在opencv4.1.1目录下,修改最顶层的CMakeList.txt,找到ocv_include_directories(${OPENCV_CONFIG_FILE_INCLUDE_DIR}),并在下面添加:
ocv_include_directories(./3rdparty/zlib)做完这些再编译,一般就不会有问题了。最后运行安装命令:
make install之后就可以看到install文件夹中有库文件了。
编译opencv程序
编译命令
riscv64-unknown-linux-gnu-g++ -o test test.cpp -I/usr/local/include -I/root/opencv/install/include/opencv4 -L/root/opencv/install/lib -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_ml -lpthread -lopencv_calib3d -lopencv_dnn -lopencv_features2d -lopencv_flann -lopencv_imgcodecs -lopencv_objdetect -lopencv_photo -lopencv_stitching -lopencv_videoio -lopencv_video其中-I指导编译器找头文件;-L指导编译器找到lib库文件。
链接opencv
由于这个东西最后要放在板上跑,我就先跳过虚拟环境测试直接上板。上板前,需要先同步板上的opencv库。找到/lib64目录,把刚才编译好的install目录下的所有lib库都放在里面。
把在宿主机编译好的可执行文件发到板上,直接./运行,但是报错:
-sh: ./test: not found这是因为还没有把opencv库和当前项目链接起来。
首先先写一个小demo测试一下链接:
做一个Project文件夹,里面放三个文件,分别是:
// lib.c
#include "stdio.h"
#include "lib.h"
void Hello_World()
{
printf("Hello World\n");
}
void Hello_Gcc()
{
printf("Hello Gcc\n");
}
void Hello_Arm()
{
printf("Hello Arm\n");
}// main.c
#include "lib.h"
int main() {
Hello_World();
Hello_Gcc();
Hello_Arm();
}// lib.h
#ifndef __LIB_H__
#define __LIB_H__
void Hello_World();
void Hello_Gcc();
void Hello_Arm();
#endif之后,先执行:
riscv64-unknown-linux-gnu-gcc -fPIC -shared -o lib.so lib.c“-fPIC”是为了生成位置无关代码,这是动态库的要求。然后是“-shared”是为了告诉编译器要将这个文件编译成动态链接库,“-o lib.so”是为了指定生成的二进制文件的名字,这里生成的二进制名字就叫“lib.so”,最后“lib.c”就是我们输入的源文件了。
编译完成以后,就可以看到我们的项目目录下多了一个.so的文件,这个就是我们通过.c文件编译出的动态链接库。
然后就可以利用这个链接库来生成可执行文件:
aarch64-linux-gnu-gcc main.c -o main -L./ lib.so这样生成的文件放在板子上是无法运行的,因为riscv架构默认从/lib64里面找库,所以要先指定额外的找库路径:
export LD_LIBRARY_PATH=./另外,此时还可能遇到两个非常坑人的报错,第一个是重点:
在使用./main来执行时,可能遇到这样的报错,即使你已经chmod授权过了:
-sh: ./main: Permission denied这其实是因为执行操作本身也是需要用到一个链接库的,哪个库呢?查看这个文件的属性:
file main发现它会用到一个叫/lib/ld-linux-riscv64-lp64d.so.1的链接,显然开发板上是没有这玩意的,从宿主机的lib里抠出来放在板子的/lib64下面,并且用chmod授权执行,就不会报权限不足了。没错,它报的权限不足并不是这个可执行文件,而是它用到的链接库。
main: ELF 64-bit LSB executable, UCB RISC-V, RVC, double-float ABI, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-riscv64-lp64d.so.1, for GNU/Linux 4.15.0, with debug_info, not stripped第二个可能会报:
./main: error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory显而易见这是由于缺少libc.so.6,这个文件可以从宿主机的/opt/riscv/sysroot/lib/libc.so.6找到。复制到/lib64就ok了,再次执行可以看到运行结果。
现在开始真正地链接opencv。上面我已经编译安装好了opencv,现在它的所有库文件都在opencv/install里躺着呢。
选用如下测试程序:
#include <opencv2/opencv.hpp>
#include <iostream>
int main()
{
cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
if (image.empty()) {
std::cerr << "无法读取图片!" << std::endl;
return -1;
}
// 二值化处理
cv::Mat binary_image;
double thresh_value = 128; // 设定阈值
cv::threshold(image, binary_image, thresh_value, 255, cv::THRESH_BINARY);
// 保存结果
cv::imwrite("test_output.jpg", binary_image);
std::cout << "二值化完成,结果保存在 test_output.jpg" << std::endl;
}在项目目录下放一个makeFile:
# 编译器设置
CXX = riscv64-unknown-linux-gnu-g++
CXXFLAGS = -std=c++11
# OpenCV 路径
OPENCV_PATH = /root/opencv/install
OPENCV_INC = /root/opencv/install/include/opencv4/
OPENCV_LIB = /root/opencv/install/lib
# 程序名称
TARGET = main
SRC = main.c
OPENCV_LIBS = -lopencv_core -lopencv_imgcodecs -lopencv_imgproc
$(TARGET): $(SRC)
$(CXX) $(CXXFLAGS) -I$(OPENCV_INC) $< -o $@ -L$(OPENCV_LIB) $(OPENCV_LIBS)这其实就是上面写的那个巨复杂的编译命令。
之后直接在项目根目录输入
make编译。发现此时会缺少一堆so文件,恰巧它们都是宿主机上/opt/riscv/sysroot/lib里面的。所以直接把里面的文件全部scp到板子的/lib64里,再执行一下上面的脚本。注意要放一张图片。
发现此时图片已经被二值化了。
至此,交叉编译opencv的任务彻底完成。
