OpenCV在基于FPGA的嵌入式系统中的移植研究

自己本科毕设的内容涉及到OpenCV
所以需要移植OpenCV到Nios平台上的嵌入式系统中
可是自己搜遍所有可能的资料
还是没有看到有人做过
即使在我毕设答辩前5天
自己依旧没有做成功

几度犹豫
曾想过腰斩题目改变毕设内容
最后忍住坚持了下来
论文末尾还都写上了移植失败分析
之后的几天
事情就像喜剧结局一样的峰回路转
原本以为不成功的移植居然在小组答辩演示的时候成功了
然后在大组答辩之前找出了不稳定的原因
于是
这篇文章就有了雏形

导师曾让我整理好自己毕设论文中的关于OpenCV的移植研究的部分
可以考虑发表出去
毕竟没人做过还是有些参考价值
自己这一段一是休息外加忙着聚会
二是不知道这样的技术含量值不值得发表
自知现在浮躁的环境发表个论文就像在刷个人数据
其实不见得文章都有什么价值
自己在查阅资料的时候也曾见过几篇文章的测试数据跟精度居然都能一模一样
只是换到自己要发表的时候
就诸多考虑
终究不想敷衍了事
只是想保持自己心中学术思想之正直
听起来多少有点自视清高的酸味
酸就酸吧
得瑟归得瑟,终究对得起自己

唠叨这么久终于开始要说正题了
感谢诸位忍到此刻的耐心
由于之前没人做过这方面的移植研究
所以本文能参考的资料不多
限于笔者的水平
若有错误之处还望见谅

移植整体步骤

  1. 配置交叉编译开发环境,移植uClinux系统
  2. 获取OpenCV源码,配置并编译,生成目标平台的静态链接库
  3. 自行编写文件读写以及图片显示函数,摒除对Highgui库的调用依赖
  4. 将改写后的程序与生成的静态链接库链接一起,下载到目标平台上测试运行

对于第一步,给个自己写的文章当参考——《uClinux在Nios II平台上的移植》,我将从第二步开始说起
值得注意的是:

  • Highgui是造成移植程序运行不稳定的一个原因,毕竟不同嵌入式硬件平台之间都有诸多区别,更何况与Pc的差异。所以要移植OpenCV程序到Nios II的嵌入式系统中,请修改源程序,摒除对Highgui库的调用依赖,自行编写文件读写以及对IplImage这种OpenCV特有数据结构的初始化。

本文使用nios2-gcc-3.4.6 + OpenCV 1.1 pre测试,OpenCV的1.0、2.0版本我也尝试过编译,均告失败,只有1.1测试通过。

配置编译OpenCV

获取OpenCV源码

你可以从http://sourceforge.net/projects/opencvlibrary/files/这里下载OpenCV的源码
点击View all files那个按钮,然后下载opencv-unix 1.1pre1,在终端输入

tar -zxvf opencv-1.1pre1.tar.gz

解压供后面使用
如果你还尝试了别的版本并且也成功了,请不吝告诉在下

配置OpenCV编译信息

进入刚才解压出来的源码文件夹,输入配置命令

$ ./configure --host=nios2-linux --without-gtk --without-carbon --without-quicktime --without-1394libs --without-ffmpeg --without-python --without-swig --enable-static --disable-shared --disable-apps CXX=nios2-linux-g++ --prefix=/home/lee/uccv

–host 指定要编译的目标平台

–witout-* 禁用各种函数库,一是uClinux本来就不支持这里的大部分库,除了gtk,其他的其实你不填也是一样不会编译进去;二是出于精简的需要,说不定日后真支持某个库了,这些我们不需要的东西还是明令去除的好

–enable-static –disable-shared 启动静态链接库,禁用动态链接,因为嵌入式系统对内核的大小要求很看重,不适合将OpenCV库编译成动态链接库,因为这样生成的库都是完整且较为庞大,所以在移植程序数量较少的情况下,通过静态链接库,只将程序用到的部分与程序编译链接到一起,是相对比较理想的选择。另外一个原因是我曾经想使用动态链接库来运行,可是编译出错,暂时无解。

–disable-apps 禁止编译范例程序,因为要编译可以在FPGA上的uClinux系统运行的OpenCV程序,需要在编译的时候额外添加参数,而且我们等会还要自行测试编译,这里一定得禁用掉,否则会导致之后make出错。

CXX 指定编译器。其实这个可以不加,只要host里面指定的平台系统能识别,最后生成的makefile里面的编译器配置信息都是一样的,之所以还保留是因为之前参考ARM下的移植文章,在此留着给大家之后编译若有错误当做一个切入点。

–prefix 可以设置OpenCV编译结果想要安装的路径,这里设置为笔者Fedora中用户所在的个人文件夹。

然后经过一堆输出信息刷新,你可以看到如下信息:

v4l那是因为我毕设要做双目测距程序,需要用到摄像头,所以我留着(不过最后还是失败了,西奈……uClinux下摄像头驱动的移植又是个头痛的问题)。

一切正常的话,我们就开始编译

$ make

如果make没有出错,就开始安装目标平台上的OpenCV函数库吧

$ make install

然后在我们设置的路径下找到lib文件夹,进入就可以看到编译好的静态链接库了

摒除Highgui的依赖

由于不能再用Highgui库了,如果你的程序有用到Highgui,我们就得自己重写文件读写以及图片显示函数,这里着重讲下如何对图片文件读取以及之后如何使用自己读取的图片数据来初始化IplImage,以此为例,其他部分按照这样的思路同样去修改。

  • 删除所有Highgui的头文件引用

为了对原来写好的程序进行最低程度的修改,我们只删除Highgui的头文件引用,然后自行添加个头文件与源文件,在其中加入的图片读取函数的名称,跟原来一样为IplImage* cvLoadImage(char* filename, int iscolor = 1)

  • 读取图片函数的编写,将图像数据存在BYTE*指针指向的内存空间

在自行编写cvLoadImage()函数中,根据要读取的图片格式不同,对应自己编写读取算法,唯一相同的步骤是,最后将图片数据读取到一个BYTE指针指向的空间

  • 使用读取的图像数据初始化OpenCV的IplImage数据结构

读取好图片,可以使用OpenCV的cxcore中的两个函数来初始化IplImage

IplImage* cvLoadImage(char* fileName, int iscolor = 1)
{
	/*...省略其他代码...*/

	//创建IplImage变量的信息头
	IplImage* img = cvCreateImageHeader(cvSize(width,height), depth, channel);
	//利用BYTE指针数据初始化IplImage
	cvSetData(img, (void *)blocks, lineByte);

	/*...省略其他代码...*/
	return img;
}

width、height是要创建IplImage图像的宽度与高度
depth是图像的位深
channel是图像的通道数
blocks是读取来的图像数据的BYTE*指针

lineByte是OpenCV中图像每一行的数据宽度,OpenCV规定图像的每一行数据宽度都必须是4的倍数,但是图像不同位数、不同宽度,不一定满足这个条件,所以这个变量之前需要额外进行一个简单的计算处理

lineByte = (width * biBitCount/8)/4*4;
//biBitCount是图像一个像素占用的位宽大小

以上width、height、biBitCount等int变量的值都可以从图片的文件头读取得知,具体方法请查阅不同图片格式的资料说明。

需要说清楚,我们只是自己简单的写了一个读取特定图片格式的函数,比不上原来的highgui原来的API那么好用,各种格式都支持,不过一般应用的时候,格式都比较固定,这点相对不是什么大问题。

  • 最后对IplImage的释放就不像原来一句cvReleaseImage()就搞定,要分2次完成
delete[] img->imageDataOrigin;
cvReleaseImageHeader(&img);

至此,我们就完成了脱离Highgui自行完成图片文件读取函数,至于文件写入乃至图片显示,限于篇幅原因就不详细讲解,基本思想掌握,剩下的就是自由发挥了。
等我工作定下之后,闲暇再找机会补写看看。

这里留下一个自己写的BMP读取的源码,供大家参考下,写的很搓很随意,而且只读取彩色图像,随意瞥下了解就好。
<—传送门—>

链接整合程序

弄到这里,有了OpenCV的静态函数库,也有脱离Highgui的源码,就可以开始编译链接目标平台的程序了

#先编译文件读取程序
$ nios2-linux-g++ -c bmpDecoder.cpp bmpDecoder.o -I/home/lee/uccv/include/opencv

#再编译测试OpenCV程序
$ nios2-linux-g++ -c test.cpp test.o -I/home/lee/uccv/include/opencv

#最后与函数库链接在一起
$ nios2-linux-g++ -o test bmpDecoder.o test.o -L/home/lee/uccv/lib -lcv -lcvaux -lcxcore -lml -lpthread -elf2flt

-I 指定源码中的头文件引用库位置

-L 指定静态链接函数库位置

-lcv -lcvaux -lcxcore -lml 是需要链接的OpenCV的几个静态链接库,顺序很重要,更改顺序很可能导致undefined reference,需要根据自己编写的程序微调这个参数列表的顺序。

-lpthread 支持多线程程序,我是根据错误提示信息加上这个参数,更深入的理由我并未深究。

-elf2flt 编译为flt格式程序,uClinux默认没有MMU,只支持绝对内存地址的二进制格式,这个记得一定要加上

假如没有什么意外的话,你就会获得最后编译出来的目标平台的OpenCV程序文件

最后唠叨

编译好了,链接成功了,按照我之前给的那篇文章的说明,再烧写下载程序到目标板上测试运行。

虽然我觉得以Cyclone II级别的FPGA器件运行OpenCV的效率,还不能到实际应用的程度,不过以现在FPGA的发展势头,多尝试新的SoPC应用开发,待硬件资源足够之时,也未必不是一大优势。

至于一些目前其他高端的FPGA的运行表现,由于自己没有条件测试,在此就不妄加评价了。

Tagged , , , ,

5 thoughts on “OpenCV在基于FPGA的嵌入式系统中的移植研究

  1. Roboter Song说道:

    Great! Thx a lot! I will have a try later, god bless us!

  2. wangyipin说道:

    看了你的文章很受启发,
    想和您有更多的交流,
    能否通过邮件互相交流,
    可以的话请发一封邮件给我,
    万分感谢!

  3. feixiaku说道:

    您好,能看到这篇文章非常感谢您,但是nios2-gcc-3.4.6 + OpenCV 1.1 pre这两个文件实在是太难找了,您能给我邮箱发一份吗?将不胜感激。feixiaku@gmail.com

  4. mydting说道:

    写的很详细 正在画PCB 以后会用得上 谢谢博主

  5. 匿名说道:

    你最后能运行成功吗?我在nios2上运行bmp的程序时,一直卡在fopen那里

发表评论

电子邮件地址不会被公开。