Cross-compile dlib for raspberry using cmake

Nowadays AI/ML is so popular, as a raspberry pi fan, I also want my little pi to run some model of ML. After a survey, I found that it’s easy to run Tensorflow or caffe on raspberrypi, but as a more efficient choice, dlib is not that easy to use. For most cases, people compile dlib on raspberrypi , it’s low efficient, and they have to enlarge their swap space on tf card for the insufficient memory while compiling. So I tried cross compiling dlib on x86 machine, but it came up with a lot of problems…

人工智能大火,自然大家都会想要在树莓派上跑个模型折腾折腾。在树莓派上有跑TF的,有跑CAFFE的,自然少不了dlib,想到dlib性能比较好,我也想在树莓派上试试dlib,但是网上查了半天,发下树莓派上跑dlib的,基本都是靠在树莓派上直接编译dlib源码生成的库来弄,而dlib编译又需要很多的内存,树莓派的1G内存有显得很紧张,所以网上的方法基本都是增加swap区,把tf卡上的空间用来和内存交换,这么做效率低不说,还很伤tf卡,所以我决定另辟蹊径,用交叉编译的方法去解决,从此跳入了一个大坑。

首先交叉编译需要环境,这个好办,树莓派的交叉编译环境还是很成熟的,github上有工具仓库,https://github.com/raspberrypi/tools/,不过windows上的交叉编译以失败告终,试过mingw, cygwin,都没成功,还折腾了很久很久,只能转向用linux交叉编译,毕竟平台相似,坑少些。于是我转向采用我的centos 7的机器交叉编译。

主要参考的是这篇:https://stackoverflow.com/questions/19162072/how-to-install-the-raspberry-pi-cross-compiler-on-my-linux-host-machine

先弄清交叉编译的原理,因为我的centos是x86的架构,而树莓派是arm的,所以不通过交叉编译,在其中一个平台上编译的程序显然无法在另一个平台上运行,毕竟指令集都不一样,即使操作系统一样,那只代表操作系统的线程管理、内存管理、文件系统等一样,但是程序执行还依赖硬件环境。所以交叉编译就是使用专用的编译器,在主机平台(centos)上编译目标平台(raspberry pi)上的程序,这里专用编译器就是https://github.com/raspberrypi/tools/里的GCC,经过改造,使得该GCC能在特定主机平台运行,并能够编译出在目标平台上运行的程序。所以交叉编译第一步就是是的编译的命令能通过交叉编译工具里的GCC去执行。

对了,前述参考链接里有说到,需要先安装的软件

git、cmake就不多说了,rsync是用来树莓派和主机平台依赖库文件传输的,ia32-libs是32位兼容库,没有的话无法编译32位的程序。因为这个库比较老,一些新版本的linux摒弃了该库,那么就安装

libc6-i386  lib32z1  lib32stdc++6 以替代ia32-libs

在执行下面的命令前,我现在home目录下新建一个文件夹raspberrypi3b+,在其下再新建rootfs文件夹,此后交叉编译工具会将此位置作为根目录进行库文件的查找/重定向,用来存放和交叉编译相关的文件,后面都会引用这个路径,当然放在其他位置都是可以的,进入这个路径,执行

工具在arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian路径下,如果主机系统是64位的,那么要使用arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64。

接下来,要使得默认GCC指向交叉编译的gcc,需要export PATH环境变量。

可以添加到 /etc/profile或者 ~/.bashrc, 完事用source /etc/profile 或者source ~/.bashrc让环境生效。

然后测试前述交叉编译gcc是否配置成功

如果看到正确的版本信息,恭喜你可以进入下一步了。否则再回顾下前面的步骤,看看哪里出了问题。

往往到这一步,交叉编译环境搭建差不多了,此时可以随便找个hello world程序编译下拷贝到树莓派上试试,往往是成功的。

但是由于我们要编译dlib,这个包含很多依赖库,所以光有上述的步骤不行,因为上述步骤里只提供了最基本的依赖库的交叉编译支持,如果需要更多的库支持怎么办,rsync来解决。

这个命令将树莓派上的/lib /usr文件夹整个拷贝到rootfs根目录下,这样在树莓派上能完成的编译,交叉编译也能完成,因为所依赖的库基本都在这两个文件夹下。

接下来写个cmake脚本方便配置交叉编译环境,新建cmake脚本,~/home/raspberrypi3b+/pi.cmake

文件内容:

前两行设置目标系统属性,第三四行设置c\cxx编译器路径。第四行设置依赖库的根目录,比如要找/usr/lib/xxx.so,那么就去路径  ROOT_PATH/usr/lib/xxx.so 找。

接下来是三个CMAKE_FIND_ROOT_PATH_MODE_xxx 是用来设置前述根目录的查找范围的,分别设置位NEVER\BOTH\ONLY,指的是对应的program\library\include三种路径是只在设置的ROOT_PATH下找,还是同时在主机系统的真实根目录下找,还是只在主机系统根目录下找。

接下来,要编译什么只需要 -D CMAKE_TOOLCHAIN_FILE=$HOME/raspberrypi/pi.cmake

以编译一个hello world为例

看起来很美好对不对,但是实际中会出现各种各样的问题。

先参考dlib官方教程里关于dlib编译的说明

http://dlib.net/compile.html

g++的方法:

cmake的方法:

注意里面提示了如果用vs编译,cmake默认编译生成32bit程序,这样内存空间只能使用2GB,虽然树莓派这里并不影响,但是其他场景使用还是要注意,所以要设置64bit编译模式,

此外还提示了由于显示的需要,编译gui相关的示例,还要安装 X11库支持

好在树莓派系统里是自带了的,所以rsync同步的/lib /usr里面都带了相关的库。

然后正式开始编译:

但是,大家在cmake的时候会发现,dlib gui support disable balabala…原因是没找到x11,

如果我们后续不需要gui相关的example编译,则可以忽略,假如我们还想编译gui的程序,比如人脸检测画出检测框,那么这个问题就必须解决。从cmake的脚本CMakeLists.txt中分析,

锁定了判断源于X11_FOUND变量,而该变量未在本脚本中定义,说明是cmake的全局变量,去cmake安装路径中查找终于找到了相关的cmake全局引用的脚本FindX11.cmake,其实如果是对cmake很熟悉的话不用这么折腾,但我是新手啊,所以费劲精力才找到。/usr/share/cmake/Modules/FindX11.cmake

查看这里面的脚本,发现该脚本会从指定的目录中去查找对应的库和头文件,显然我们应该把交叉编译的路径添加进来,这里添加相对路径/usr/lib/arm-linux-gnueabihf

然后执行 make

结果报错找不到<bits/stdlib-bsearch.h>

在本机平台上查找下 find / -name stdlib-bsearch.h

发现是在/home/raspberrypi3b+/rootfs/usr/include/arm-linux-gnueabihf/bits/stdlib-bsearch.h路径下

那么就需要吧这个路径添加到包含文件的检索路径里,也就是要在pi.cmake里添加

这里添加完整路径而不是基于前述rootfs的相对路径,这个跟cmake的机制有关,cmake里的include_directories在设置根目录前已经先加载了路径。

然后删除已生成的,rm ./* -r -f , 再重新cmake  make

ok,终于成功。

虽然还有些问题,但是目前已经算告一段落,后面可以直接拿着这个库文件编译自己的程序了。

如果直接用arm-linux-gnueabihf-g++编译,则不用再看后续的内容了,以编译object_detector_ex.cpp为例:

但如果我们还想用cmake编译dlib自带的examples, 则要经历一些波折。

于我而言,波折肯定是要正面刚的,这么多例子,总不能一个一个g++搞一堆吧。。

首先这里插播一条tips,如果cmake,examples不想重新编译libdlib.a节省时间,则可以先把dlib_build拷贝出来,然后删掉其他文件,运行cmake然后吧dlib_build拷贝进来,但是不要覆盖,这样就不会重复编译libdlib.a。

我写了个简单的脚本 autorebuild.sh 放到上级目录下,第一次拷贝进来,后续每次要重新编译,直接./autorebuild.sh就行


一开始执行cmake examples就遇到了各种错误。。

这个是编译错误,还没链接,所以考虑

然后就是报链接错误:

这个错误折腾了很久很久很久,有个一两个星期吧,第一次弄的时候也是折腾了几天没搞定,最后放弃了,过了大概一个月我又不甘心,再次尝试解决这个问题,终于在近百次的编译尝试后解决了。。按照网上查的大量的资料,包括错误中的提示,问题都似乎是指向rpath的,文末附有几个链接是rpath相关的,但是都没能解决这个问题。错误的提示是无法在libX11.so中找到一些列函数引用,而这些引用都是在libxcb.so.1中,那么先查看下libxcb.so.1的路径,/home/raspberrypi3b+/rootfs/usr/lib/arm-linux-gnueabihf/libxcb.so.1,那么既然找不到我就先尝试直接link_libraries,添加该动态链接库所在路径,但是始终失败告终。然后就是按照网上说的方法,添加rpath,尝试了很多很多命令以及他们的组合:

以及在/etc/ld.so.conf中添加include ld.so.conf.d/*.conf,并在/etc/ld.so.conf.d/*.conf中添加对应路径

然后ldconfig (用来重新查找所有相关路径下的动态链接库,类似于windows下的dll  regist过程)

也在交叉编译路径下尝试了,或者把交叉编译根路径添加进去

还在环境变量 LD_LIBRARY_PATH中添加对应路径

但,都失败了。。。

几乎万念俱灰,最后的最后,我翻看了cmake生成的各种makefile文件等寻找蛛丝马迹,猛然醒悟,makefile里的各种路径还是原始主机路径,而不是交叉编译环境下的路径,自然是找不到的啊,想到编译是可以,但链接是不行的,所以我就在cmake文件里加上了

但是cmake的时候就报了很多错误:

都是提示链接时动态链接库冲突,可能会导致找不到的问题,既然是may not,  那还是要试一试的,所以怀着忐忑的心

make

finally…..

当时的抑制不住的来自菜鸡的鸡动啊。。。但为啥光设一个CMAKE_FIND_ROOT_PATH不行呢。。

赶紧拷贝到树莓派上试试:

由于没有安装JPG等支持库,所以只能输入BMP格式的图片,不过这都简单啦!看上图,图片检测时间3s左右,还是挺不错的。比起用TF的一个模型的6~8秒,已经好很多了。

后面又试了下release版本的编译(好像不对。。)

cmake –build . –config Release

脚本和目录结构参见:

https://github.com/atp798/BlogStraka/tree/master/CrossCompileDlibForRaspberrypi

PS:

此外如果遇到

等之类的错误,说明是ld链接错误,无法找到对应的库文件,这个时候不能像上述问题一样解决, 而是找到对应的libpthread.so.0文件,我们发现原始的文件里是

由于这个路径前缀会导致ld找不到对应的库,所以最后一行直接删掉路径前缀,改成

即可。不过我第二次尝试的时候并没有复现这个问题。

后续再思考下为啥  光设一个CMAKE_FIND_ROOT_PATH不行呢。。或者说该路径为啥LD阶段不起效。

参考链接:

树莓派交叉编译:

https://stackoverflow.com/questions/19162072/how-to-install-the-raspberry-pi-cross-compiler-on-my-linux-host-machine

https://www.cnblogs.com/cursorhu/p/5760415.html

https://www.cnblogs.com/rickyk/p/3875334.html

这篇注意看里面引用的链接:http://gnutoolchains.com/raspberry/tutorial/

VS大法助阵交叉编译:

https://visualgdb.com/tutorials/raspberry/crosscompiler/

还可以VS调试:

https://visualgdb.com/tutorials/raspberry/

故障排查:

https://www.raspberrypi.org/forums/viewtopic.php?f=33&t=66205&p=486079

https://www.raspberrypi.org/forums/viewtopic.php?t=37658

rpath相关:

https://stackoverflow.com/questions/30400362/cmake-cross-compile-target-rpath

https://blog.csdn.net/q1302182594/article/details/42102961

https://gitlab.kitware.com/cmake/community/wikis/doc/cmake/RPATH-handling

https://stackoverflow.com/questions/30400362/cmake-cross-compile-target-rpath

https://www.cnblogs.com/rickyk/p/3884257.html

http://blog.sina.com.cn/s/blog_5623cddb0100e4sq.html

Fixing -rpath-link issues with cross-compilers

更多背景知识参考:

makefile 中指定程序运行时加载的库文件路径:  https://blog.csdn.net/xhoufei2010/article/details/78559409

传统的增加swap区编译dlib方法:

Install dlib on the Raspberry Pi

 

 

发表评论