在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的任务彻底完成。