经过了昨天一天苦逼的研究opencv源码、arm-linux编译器工作原理和坚持不懈的make,我终于移植成功了opencv-2.4.7for arm库到OK6410上,遇到了各种问题,研究了很长时间,连上课时候都在想原因和解决方案,都让我想翘课。。。
接下去我会简单分析整个移植过程,讲解一些网上常见问题,不过我建议大家还是自己先去试试移植看看,花点时间就能学到很多。
首先介绍我的开发环境:
主机OS:UBUNTU12.04
宿主机:飞凌OK6410
宿主机内核:linux3.6
opencv版本:opencv2.4.7
cmake版本:cmake2.8.12.1
交叉工具链:Sky arm-linux4.3.3 天嵌
接下去还是先开始利用cmake工具定制opencv,cmake的图像界面比较容易上手,所以这里用cmake-gui讲解cmake过程:
1.虚拟机的童鞋可以先备份虚拟文件
2.su- root切换到root用户忽略权限问题
3.mkdir/home/opencv/opencv-arm/ 存放makefile和一些相关cmake配置文件
4.cmake-gui运行cmake gui
5.跟移植到linux-pc上一样填入sourcecode和build the binaries目标路径
6.点击Configure,保持unixmakefiles选项,选择specify options for corss-compiling来选择编译器路径
7.operating system填入os名,即编译器名arm-linux os version这个可以不填,我不清楚这个填内核版本还是编译器版本,compilers C填入编译器arm-linux-gcc的elf路径,C++填入编译器arm-linux-g++的elf路径,target root是寻找lib和include文件的,这些文件都在arm-linux编译器文件路径下,比如我的编译器路径就是/home/arm/Sky/opt/EmbedSky/4.3.3/
8.finish后会提示:error inconfiguration procss,project files may be invalid,指的是它默认配置对你给定的os是不支持的。这是我遇到的第一个问题,究竟什么不支持呢,为了通过配置,我去了解cmake都为opencv配置了些什么(其实看他提示的出错信息就已经知道问题在哪,但是也不能稀里糊涂的配置,我还是决定看看配置项):
opencv-arm/CMakeCache.txt是cmake记录配置信息的文件,也是makefile重要信息来源,这里的配置选项都有注释,可以方便大家理解配置选项的含义。
重要选项:
BUILD开头的都是构建文件,选上则将寻找对应源码然后构建在opencv库中
WITH开头的都是opencv对相应选项内容的支持,选上则表示opencv库将支持所选内容,所以开发板不支持的选项不要选。
CMAKE_BUILD_TYPE构建类型,一般填入Release构建发布版本
CMAKE_INSTALL_PREFIX安装路径,可以指定自己需要安装的路径
我的安装路径是usr/local/arm/opencv/-> bin/ 存放elf
-> include/ 存放opencv opencv2
-> lib/ 存放.so .a
-> share/ 存放例子中使用的xml资源文件
CMAKE_EXE_LINKER_FLAGS链接标记,-pthread支持线程,-ldl避免未定义dlopen,-lrt避免未定义clock_gettime。这个要在opencv-arm/CMakeCache.txt中修改。
CMAKE_EXE_LINKER_FLAGS:STRING=''
替换成:CMAKE_EXE_LINKER_FLAGS:STRING=-pthread-ldl -lrt 全动态链接
CMAKE_EXE_LINKER_FLAGS是我遇到的第二个问题,不修改这个make编译时会有以下错误:
1.线程和clock符号错误 解决方法: 添加链接器选项-pthread-lrt
../../lib/libopencv_core.so: undefinedreference to `pthread_spin_init'
../../lib/libopencv_core.so: undefinedreference to `pthread_spin_unlock'
../../lib/libopencv_core.so: undefinedreference to `pthread_spin_lock'
../../lib/libopencv_core.so: undefinedreference to `pthread_spin_destroy'
../../lib/libopencv_core.so: undefinedreference to `pthread_once'
../../lib/libopencv_core.so: undefinedreference to `clock_gettime'
../../lib/libopencv_core.so: undefinedreference to `pthread_spin_trylock'
collect2: ld returned 1 exit status
make[2]: *** [bin/opencv_perf_core] 错误 1
make[1]: ***[modules/core/CMakeFiles/opencv_perf_core.dir/all] 错误 2
make: *** [all] 错误 2
2.dlopen符号错误 解决方法: 添加链接器选项-ldl
../../lib/libopencv_ocl.so: undefinedreference to `dlopen'
../../lib/libopencv_ocl.so: undefinedreference to `dlsym'
collect2: ld returned 1 exit status
make[2]: *** [bin/opencv_perf_ocl] 错误 1
make[1]: ***[modules/ocl/CMakeFiles/opencv_perf_ocl.dir/all] 错误 2
理解的差不多了,来看看怎么让cmake过去,首先看错误提示:
CMake Error at cmake/FindCUDA.cmake:762(if):
if given arguments:
"CUDA_VERSION""VERSION_GREATER" "5.0" "AND""CMAKE_CROSSCOMPILING" "AND" "MATCHES" "arm""AND" "EXISTS""CUDA_TOOLKIT_ROOT_DIR-NOTFOUND/targets/armv7-linux-gnueabihf"
Unknown arguments specified
Call Stack (most recent call first):
cmake/OpenCVDetectCUDA.cmake:26(find_package)
cmake/OpenCVFindLibsPerf.cmake:24 (include)
CMakeLists.txt:423 (include)
这个CUDA有问题,看看WITH_CUDA默认确实是选中的,来到CMakeCache.txt查看发现这个选项是显卡NVidiaCuda Runtime support的支持,我也很希望开发板能支持显卡- -,还是把WITH_CUDA去掉,顺便去掉WITH_TIFF以免编译时报tiff error。
总结一下修改的地方:
去掉WITH_TIFF WITH_CUDA
修改CMAKE_BUILD_TYPE为Release
修改CMAKE_INSTALL_PREFIX 路径可以参考我的想法,而且千万不要跟pc的库重叠
到opencv-arm/CMakeCache.txt下找到CMAKE_EXE_LINKER_FLAGS:STRING=''
替换成:CMAKE_EXE_LINKER_FLAGS:STRING=-pthread-ldl -lrt
9.再次configurate 这次还会有许多不支持的选项,但是可以generate,我们就忽略它们,但是其中有一个我可以改 PYTHON_PACKAGES_PATH可以修改路径,到/usr/local/lib下找到这个包,我没试过,但是pkg-config的寻路功能对我们来说还是很有用的。
10.点击generate,如果提示generatedone说明可以拿去make了。
11.进入构建的目录,我的是opencv-arm目录下,运行make,各忙各的思密达。
12.终于100%了,然后还在这个目录下运行makeinstall,如果安装成功不会报错,到安装目录下看lib下是否有.so文件,运行file xxxxxx.so看看是不是支持arm的共享库,是的话编译就基本成功了。
这里我再提个我遇到的问题,我原本以为opencv的gui可以依赖qtgui运行在arm板子上,所以我在配置时选了WITH_QT和WITH_OPENGL,把qt for arm的qmake路径填上,结果编译报错,看到i386字样我心头一紧,赶紧跑去看CMakeCache.txt,结果一搜QT,显示出满屏幕的i386的qt库路径,看看cmake貌似没这配置还是我没找到怎么的,这事情我还是以后再弄吧,opencv的gui貌似需要gtk或qt的支持才能用highgui里的功能,这么一来highgui库就没用了,不知道有没有人可以让highgui在arm板上用起来,我挺喜欢用这里面的功能。我有空也会去看看WITH_QT该怎么去支持。那显示的工作还是让用户主动让opencv移交给qt去做吧。
/
移植和测试:
移植也是比较麻烦的事,过程讲解后会谈谈我遇到的问题
测试没问题的移植过程:
1.将安装目录下/lib/*所有文件拷到开发板/lib下,不要改路径,必须在/lib下。容量不足的可以只拷运行需要的文件
2.把安装目录下/bin/*拷到开发板/bin下,这一步可以不要
3.新建qt工程,使用dialog界面,在.pro文件中加入
- INCLUDEPATH+= /usr/local/arm/4.4.3/opencv/include/opencv \
- /usr/local/arm/4.4.3/opencv/include/opencv2 \
- /usr/local/arm/4.4.3/opencv/include
- LIBS+= /usr/local/arm/4.4.3/opencv/lib/libopencv*
INCLUDEPATH+= /usr/local/arm/4.4.3/opencv/include/opencv \
/usr/local/arm/4.4.3/opencv/include/opencv2 \
/usr/local/arm/4.4.3/opencv/include
LIBS+= /usr/local/arm/4.4.3/opencv/lib/libopencv*
4.新建switch.cpp源文件,复制上IplImage与QImage间转换用代码:
- #include "switch.h"
- ImageCVtoQT::ImageCVtoQT(IplImage *_srcImage)
- :srcImage(_srcImage)
- {
- assert(srcImage != NULL);
- width = srcImage -> width;
- height = srcImage -> height;
- channel = srcImage -> nChannels;
- }
- ImageCVtoQT::~ImageCVtoQT()
- {
- cvReleaseImage(&srcImage);
- }
- const QImage ImageCVtoQT::getQtImage()
- {
- QImage desImage = QImage(width, height, QImage::Format_RGB32);
- for(int i=0; i<height; i++)
- {
- for(int j=0;j<width; j++)
- {
- int r,g,b;
- if(RGB_TYPE == channel)
- {
- b = (int)CV_IMAGE_ELEM(srcImage, uchar , i, j*3+0);
- g = (int)CV_IMAGE_ELEM(srcImage, uchar , i, j*3+1);
- r = (int)CV_IMAGE_ELEM(srcImage, uchar , i, j*3+2);
- }
- else if(GRAY_TYPE == channel)
- {
- b = (int)CV_IMAGE_ELEM(srcImage, uchar, i, j);
- g = b;
- r = b;
- }
- desImage.setPixel(j, i, qRgb(r, g, b));
- }
- }
- return desImage;
- }
- /
- ImageQTtoCV::ImageQTtoCV(QImage _srcImage)
- :srcImage(_srcImage)
- {
- assert(!srcImage.isNull());
- width=srcImage.width();
- height=srcImage.height();
- }
- ImageQTtoCV::~ImageQTtoCV()
- {
- }
- IplImage *ImageQTtoCV::getCvImage()
- {
- IplImage *desImage=cvCreateImage(cvSize(width,height),8,3);
- for(int i=0;i<height;i++)
- {
- for(int j=0;j<width;j++)
- {
- QRgb rgb=srcImage.pixel(j,i);
- CV_IMAGE_ELEM(desImage,uchar,i,j*3+0)=qBlue(rgb);
- CV_IMAGE_ELEM(desImage,uchar,i,j*3+1)=qGreen(rgb);
- CV_IMAGE_ELEM(desImage,uchar,i,j*3+2)=qRed(rgb);
- }
- }
- return desImage;
- }
#include "switch.h"
ImageCVtoQT::ImageCVtoQT(IplImage *_srcImage)
:srcImage(_srcImage)
{
assert(srcImage != NULL);
width = srcImage -> width;
height = srcImage -> height;
channel = srcImage -> nChannels;
}
ImageCVtoQT::~ImageCVtoQT()
{
cvReleaseImage(&srcImage);
}
const QImage ImageCVtoQT::getQtImage()
{
QImage desImage = QImage(width, height, QImage::Format_RGB32);
for(int i=0; i<height; i++)
{
for(int j=0;j<width; j++)
{
int r,g,b;
if(RGB_TYPE == channel)
{
b = (int)CV_IMAGE_ELEM(srcImage, uchar , i, j*3+0);
g = (int)CV_IMAGE_ELEM(srcImage, uchar , i, j*3+1);
r = (int)CV_IMAGE_ELEM(srcImage, uchar , i, j*3+2);
}
else if(GRAY_TYPE == channel)
{
b = (int)CV_IMAGE_ELEM(srcImage, uchar, i, j);
g = b;
r = b;
}
desImage.setPixel(j, i, qRgb(r, g, b));
}
}
return desImage;
}
/
ImageQTtoCV::ImageQTtoCV(QImage _srcImage)
:srcImage(_srcImage)
{
assert(!srcImage.isNull());
width=srcImage.width();
height=srcImage.height();
}
ImageQTtoCV::~ImageQTtoCV()
{
}
IplImage *ImageQTtoCV::getCvImage()
{
IplImage *desImage=cvCreateImage(cvSize(width,height),8,3);
for(int i=0;i<height;i++)
{
for(int j=0;j<width;j++)
{
QRgb rgb=srcImage.pixel(j,i);
CV_IMAGE_ELEM(desImage,uchar,i,j*3+0)=qBlue(rgb);
CV_IMAGE_ELEM(desImage,uchar,i,j*3+1)=qGreen(rgb);
CV_IMAGE_ELEM(desImage,uchar,i,j*3+2)=qRed(rgb);
}
}
return desImage;
}
5.新建switch.h头文件供调用:
- #ifndef SWITCH_H
- #define SWITCH_H
- //#include "highgui.h"
- #include "cv.h"
- #include "cxcore.h"
- #include <QImage>
- #define RGB_TYPE 3
- #define GRAY_TYPE 1
- class ImageCVtoQT
- {
- public:
- ImageCVtoQT(IplImage *_srcImage);
- ~ImageCVtoQT();
- const QImage getQtImage(void);
- private:
- IplImage *srcImage;
- //QImage desImage;
- int width;
- int height;
- int channel;
- };
- class ImageQTtoCV
- {
- public:
- ImageQTtoCV(QImage _srcImage);
- ~ImageQTtoCV();
- IplImage *getCvImage(void);
- private:
- QImage srcImage;
- int width;
- int height;
- //int channel;
- };
- #endif // SWITCH_H
#ifndef SWITCH_H
#define SWITCH_H
//#include "highgui.h"
#include "cv.h"
#include "cxcore.h"
#include <QImage>
#define RGB_TYPE 3
#define GRAY_TYPE 1
class ImageCVtoQT
{
public:
ImageCVtoQT(IplImage *_srcImage);
~ImageCVtoQT();
const QImage getQtImage(void);
private:
IplImage *srcImage;
//QImage desImage;
int width;
int height;
int channel;
};
class ImageQTtoCV
{
public:
ImageQTtoCV(QImage _srcImage);
~ImageQTtoCV();
IplImage *getCvImage(void);
private:
QImage srcImage;
int width;
int height;
//int channel;
};
#endif // SWITCH_H
上述两个类非常棒,感谢网上牛人,我也为了解这两个类的实现而看了半天的源码。QT和OPENCV源码间看的我比较晕。
6.在界面文件中拉入一个lable,将其展开与窗体同大小,我没有太多qt编写经验,显示图只会lable显示,而且画图的效果- -,就不多说了,等这个移植好后看看qt的编程吧。。。
7.在dialog的构造函数中进行图像的导入和转换,过程是QImage导入图片-> 原始IplImage->opencv对原始IplImage进行处理->处理后IplImage->QImage->使用QImage借助qt窗体上显示图片。
dialog.cpp要贴的内容:- #include "dialog.h"
- #include "ui_dialog.h"
- #include "switch.h"
- #include "QtGui"
- Dialog::Dialog(QWidget *parent) :
- QDialog(parent),
- ui(new Ui::Dialog)
- {
- ui->setupUi(this);
- //声明IplImage指针
- IplImage *pImg = NULL;
- QImage *qImg = new QImage;
- //载入图片
- if(!(qImg->load("/home/project/sao22.bmp"))) // 我的开发板支持bmp格式,小心路径
- {
- return;
- }
- //switch
- ImageQTtoCV qtc(*qImg);
- pImg = qtc.getCvImage();
- if(!pImg)
- return;
- IplImage *pGrayImg = NULL;
- pGrayImg = cvCreateImage(cvGetSize(pImg), IPL_DEPTH_8U, 1);
- cvCvtColor(pImg, pGrayImg, CV_BGR2GRAY);
- ImageCVtoQT ctq(pGrayImg);
- *qImg = ctq.getQtImage();
- if(!qImg)
- return;
- ui->label->setPixmap(QPixmap::fromImage(*qImg));
- }
- Dialog::~Dialog()
- {
- delete ui;
- }
#include "dialog.h"
#include "ui_dialog.h"
#include "switch.h"
#include "QtGui"
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
//声明IplImage指针
IplImage *pImg = NULL;
QImage *qImg = new QImage;
//载入图片
if(!(qImg->load("/home/project/sao22.bmp"))) // 我的开发板支持bmp格式,小心路径
{
return;
}
//switch
ImageQTtoCV qtc(*qImg);
pImg = qtc.getCvImage();
if(!pImg)
return;
IplImage *pGrayImg = NULL;
pGrayImg = cvCreateImage(cvGetSize(pImg), IPL_DEPTH_8U, 1);
cvCvtColor(pImg, pGrayImg, CV_BGR2GRAY);
ImageCVtoQT ctq(pGrayImg);
*qImg = ctq.getQtImage();
if(!qImg)
return;
ui->label->setPixmap(QPixmap::fromImage(*qImg));
}
Dialog::~Dialog()
{
delete ui;
}
8.qt用qmake for arm构建工程,完成后将elf文件从debug文件夹中拷贝到开发板中(千万不要在第三级目录以上,否则会报错),将图片也拷到对应路径上
9.写好启动脚本后运行,就可以看到屏幕上显示处理后的gray图片(爱图)了:
这说明opencv库可以正常工作,其他功能我还没试,但基本的创建图片和颜色处理是没问题的,至此opencv2.4.7库移植到arm开发板ok6410成功。
接下来我再来说说我遇到的问题:
1. :-1: 警告:../../lib/libopencv_core.so,needed by /usr/local/arm/4.4.3/opencv/lib/libopencv_calib3d.so,not found (try using -rpath or -rpath-link)
:-1:警告:../../lib/libopencv_flann.so,needed by /usr/local/arm/4.4.3/opencv/lib/libopencv_calib3d.so, not found (tryusing -rpath or -rpath-link)
先是在qt上警告,后在开发板上运行时也报错:
error while loading shared libraries:../../lib/libcxcore.so: cannot open shared object file: No such file ordirectory
还好我那时还冷静,看到了../../lib/这个提示,感觉这个库是不是只能在特定的路径下呢?假设当前运行路径是/home/qt/sd11/,../../所指的文件夹是qt/,但qt/下没有lib文件夹,这时我把运行目录换成/home/,../../所指的文件夹是根目录,根目录下有lib文件夹,而且里面放着libcxcore.so,这样elf就能找到共享文件了。所以这问题连带着qt编译时的报错一起解决。
第一种方案,编译qt的工程也要在二级目录下,不过这样是很难做到的,应为这样会使第二级目录变得很混乱,但是这些警告对产生elf是没影响的,忽略了吧。elf的运行目录在第二级目录下倒是还算没问题。
第二种方案,编译qt的目录可以随意,在../../下建立一个lib文件夹,里面放入对应.so库,运行时也这么做。这么做貌似更好。
2. [root@lin/home]#./test11
OpenCV Error: Unspecified error (The functionis not implemented. Rebuild the library with Windows, GTK+ 2.x or Carbonsupport. If you are on Ubuntu or Debian, install libgtk2.0-dev and pkg-config,then re-run cmake or configure script) in cvNamedWindow, file/home/opencv/opencv-2.4.7/modules/highgui/src/window.cpp, line 483
terminate called after throwing an instanceof 'cv::Exception'
what(): /home/opencv/opencv-2.4.7/modules/highgui/src/window.cpp:483: error:(-2) The function is not implemented. Rebuild the library with Windows, GTK+2.x or Carbon support. If you are on Ubuntu or Debian, install libgtk2.0-devand pkg-config, then re-run cmake or configure script in function cvNamedWindow
出现这个的原因是因为用户使用了highgui中的功能,比如cvloadiamge之类的。GTK是开源gui,但是开发板应该是很难搭建起这个gui的,arm下qt的gui貌似也很难让opencv依赖,所以还是不要使用highgui的功能,就乖乖进行opencv和qt的转换吧。。。
这个转换的问题我很感激学长的一席话,我本来为了解决这个问题还想移植GTK上去呢- -,当他一说不要移植GTK的时候我就突然想到以前做过qt和cv的转换,竟然qt能显示那就让qt去显示吧,哎,茅塞顿开。。。
突然想想写博文好累啊,自己理解了不简单,让别人也理解那就更难了,但是写博文的时候我也学到了很多,我也会继续把我的学习所得分享出来,大家也要多多和别人交流哦,否则就是闭门造车。
今天就到这里,米娜桑,我要回去过清明小长假咯,下次见。
这篇博客有个小错误。
替换成:CMAKE_EXE_LINKER_FLAGS:STRING=-pthread-ldl -lrt
这里少写了一个字母改完
替换成:CMAKE_EXE_LINKER_FLAGS:STRING=-lpthread-ldl -lrt
就可以编译成功了谢谢原作者