环境:
Ubuntu10.04
OK6410开发板
ARM Linux3.01系统
QT4.7.1
OpenCV-2.0.0
arm-linux-g++ 4.3.2 / arm-linux-gcc 4.3.2
CMake-gui 2.8.0
(建议先参考我的另外一篇博文《Ubuntu上安装Opencv-2.0.0》)
OpenCV2.0.0交叉编译过程:
1、在usr/local新建文件夹
# mkdir opencv
把下载的OpenCV-2.0.0.tar.bz2解压到 usr/local/opencv 目录下
2、然后在usr/local/opencv 新建一个 opencv-arm 文件夹,作为CMake编译arm版本的工作目录
# mkdir opencv-arm
如下图所示:
3、在终端里调出CMake gui界面:
# cmake-gui
按照下图方式选择源码目录和build目录
然后点击Configure按钮,保持generator为Unix Makefiles,选择Specify options for cross-compiling,点击Next
按照如下方式配置:
注:/usr/local/arm/4.3.2 为交叉编译工具 arm-linux-g++/gcc 的所在包含文件夹(在bin文件夹里面)
然后点击 “Finish” 按钮;
修改默认配置,默认安装目录为/usr/local,但我想对它统一归类,所以我在/usr/local/arm/4.3.2/lib目录下新建了一个opencv文件夹,在Cmake-gui里修改CMAKE_INSTALL_PREFIX变量改为/usr/local/arm/4.3.2/lib/opencv/
(另外,如果没有安装tiff图像的支持,请去掉WITH_TIFF)
然后点击Generate按钮生成Makefile;
4、在终端界面中,进入目录/usr/local/opencv/opencv-arm,运行make编译opencv
编译时发现如下错误:
Linking CXX executable ../../bin/opencv_createsamples
../../lib/libcxcore.so: undefined reference to `clock_gettime'
../../lib/libcxcore.so: undefined reference to `pthread_key_create'
../../lib/libcxcore.so: undefined reference to `pthread_getspecific'
../../lib/libcxcore.so: undefined reference to `pthread_setspecific'
原因是cmake不认识我定义的arm-linux系统标记,没有加上库pthread和rt的链接选项
此时需要修改CMakeCache.txt,CMAKE_EXE_LINKER_FLAGS原来为空,加上-lpthread -lrt,如下图:重新make编译,错误消除,编译成功之后的界面如下:
5、然后运行make install,将opencv生成的库和头文件安装到目录/usr/local/arm/4.3.2/lib/opencv/,结果如下:
6、把这5个 .so 库文件拷贝到ARM板系统中的 /lib 目录下面:(如下是添加之后的截图)
7、下来就是编写验证程序了:
首先得确保摄像头在ARM板上的使用是正常的,具体情况请查阅我的另外一篇博文:
《摄像头在liunx上的QT显示和OK6410 ARM开发板上的使用》
在这篇文章里我曾提到过要使用opencv,但是摄像头出来的是UVC格式,所以我要走一个图像转换流程:UVC转QImage转IplImage;
8、还是那个简单思路:现在ubuntu PC上实现,然后再移植至ARM上;
具体工程代码下载请看附录。
主要涉及opencv的代码如下:
void Widget::paintEvent(QPaintEvent *) { uchar * pImgBuf; unsigned int len; camReturn = m_camera->get_frame((void **)&pImgBuf,&len); convert_yuv_to_rgb_buffer(pImgBuf,imgBuf,image_width,image_height); frame->loadFromData((uchar *)imgBuf,/*len*/image_width * image_height * 3 * sizeof(char)); IplImage* src = QImageToIplImage(frame); if (!src) { printf("img error!"); return; } //更改图像大小(后期对人脸检测时间控制会有很大帮助) double sizeScale = imgSizeScaleSmall; CvSize img_cvsize; img_cvsize.width = src->width * sizeScale; img_cvsize.height = src->height * sizeScale; IplImage* dst = cvCreateImage(img_cvsize, src->depth, src->nChannels); cvResize(src, dst, CV_INTER_LINEAR); //opencv函数更改图片大小 // cvSaveImage("jason.jpg", src); //ARM对opencv的highgui支持极其差,这个函数不能使用 //更改图像大小,清晰度会下降 sizeScale = imgSizeScaleBig; img_cvsize.width = dst->width * sizeScale; img_cvsize.height = dst->height * sizeScale; IplImage* img = cvCreateImage(img_cvsize, dst->depth, dst->nChannels); cvResize(dst, img, CV_INTER_LINEAR); QImage qimage = QImage((uchar *)img->imageData, img->width,img->height, image_Format); //IplImage为BGR格式,QImage为RGB格式,所以要交换B和R通道显示才正常 //可以用OpenCV的cvConcertImage函数交换,也可以用QImage的rgbSwapped函数交换; qimage = qimage.rgbSwapped(); ui->m_imgLabel->setPixmap(QPixmap::fromImage(qimage)); camReturn = m_camera->unget_frame(); cvReleaseImage(&img); //释放图片内存 cvReleaseImage(&src); }
其中,QImage转IplImage的处理函数如下:(在此感谢此篇博文的帮助:关于QImage和IplImage之间转换的实现)
IplImage* Widget::QImageToIplImage(const QImage * qImage) { int width = qImage->width(); int height = qImage->height(); CvSize Size; Size.height = height; Size.width = width; IplImage *IplImageBuffer = cvCreateImage(Size, IPL_DEPTH_8U, 3); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { QRgb rgb = qImage->pixel(x, y); cvSet2D(IplImageBuffer, y, x, CV_RGB(qRed(rgb), qGreen(rgb), qBlue(rgb))); } } return IplImageBuffer; }运行结果如下图所示:
这里得注意一个问题,就是摄像头的名词,我的ubuntu上的名称为:/dev/video0,在ARM上为:/dev/video2;注意修改
一、可能的组合:
(1)const char*p
(2)char const*p
(3)char *const p
(4)const char **p
(5)char const**p
(6)char *const *p
(7)char **const p
当然还有在(5)、(6)、(7)中再插入一个const的若干情况,不过分析了以上7中,其他的就可类推了!
二、理解助记法宝:
1。关键看const 修饰谁。
2。由于没有 const *的运算,若出现 const * 的形式,则const实际上是修饰前面的。
比如:char const*p,由于没有const*运算,则const实际上是修饰前面的char,因此char const*p等价于const char*p。也就是说上面7种情况中,(1)和(2)等价。 同理,(4)和(5)等价。在(6)中,由于没有const*运算,const实际上修饰的是前面的char*,但不能在定义时转换写成 const(char *)*p,因为在定义是"()"是表示函数。
三、深入理解7种组合
(0)程序 在执行时为其开辟的空间皆在内存(RAM)中,而RAM里的内存单元是可读可写 的;指针只是用来指定或定位要操作的数据的工具,只是用来读写RAM里内存单元的工作指针 。若对指针不加任何限定,程序中一个指针可以指向RAM中的任意位置(除了系统敏感区,如操作系统内核所在区域)并对其指向的内存单元进行读和写操作(由RAM的可读可写属性决定);RAM里内存单元的可读可写属性不会因为对工作指针的限定而变化(见下面的第4点),而所有对指针的各种const限定说白了只是对该指针 的 读写权限 (包括读写位置)进行了限定 。
(1)char *p:p是一个工作指针,可以用来对任意位置 (非系统敏感区域)进 行读操作和写操作 ,一次读写一个字节(char占一个字节)。
(2)const char*p或者char const *p(因为没有const*p运算,因此const修饰的还是前面的char):可以对任意位置(非系统敏感区域)进行“只读” 操作。(“只读”是相对于char *p来说所限定的内容)
(3)char *const p(const 修饰的是p):只能对“某个固定的位置” 进 行读写操作,并且在定义p时就必须初始化(因为在后面不能执行“p=..”的操作,因此就不能在后面初始化,因此只能在定义时初始化)。(“某个固定的位 置”是相对于char
*p来说所限定的内容)
可以总结以上3点为:char *p中的指针p通常是”万能”的工作指针 ,而(2)和(3)只是在(1)的基础上加了些特定的限制 ,这些限制在程序中并不是必须的,只是为了防止程序员的粗心大意而产生事与愿违的错 误。
另 外,要明白“每块内存空间都可有名字;每块内存空间内容皆可变(除非有所限) ” 。比如函数里定义的char s[]="hello";事实上在进程的栈内存里开辟了6个变量共6个字节的空间,其中6个字符变量的名字分别为:s1[0]、s1[1]、 s1[2]、s1[3]、s1[4]、s1[5](内容是'\0')
{
待验证 : 还有一个4字节的指针变量s 。不过s是“有所限制”的,属于char *const类型,也就是前面说的 (3)这种情况,s一直指向s[0], 即(*s==s[0]=='h'),可以通过*s='k'来改变s所指向的 s[0]的值,但不能执行(char *h=“aaa”;s=h;)来对s另外赋值。
}
(4)上面的(2)和(3)只是对p进行限定,没有也不能对p所指向的空间进行限定,对于"char s[]="hello";const char*p=s;" 虽然不能通过*(p+i)='x'或者p[i]='x'来修改数组元素s[0]~s[4]的值,但可以通过*(s+i)='x'或者s[i]='x'来修 改原数组元素的值--RAM里内存单元的可读可写属性不因对工作指针的限定而改变,而只会因对其本身的限定而改变。如const char c=‘A’,c是RAM里一个内存单元(8字节)的名字,该内存单元的内容只可读,不可写。
(5)const char **p或者char const**p :涉及两个指针p和 *p。由于const修饰char ,对指针p没有任何限定,对指针*p进行了上面情况(2)的限定。
(6)char *const *p:涉及两个指针p和 *p。由于const修饰前面的char*,也就是对p所指向的内容*p进行了限定(也属于前面的情况(2))。而对*p来说,由于不能通过"*p=..."来进行另外赋值,因此属于前面的情况(3)的限定。
(7)char **const p : 涉及两个指针p和 *p,const修饰p,对p进行上面情况(3)的限定,而对*p,没有任何限定。
四、关于char **p 、const char **p的类型相容性问题
一、在res/drawable/下建一个xml文件,例如:shape_background_grey.xml:
①.简单的类型。
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <gradient android:startColor="#3A3C39" android:endColor="#181818" android:angle="270" /> <corners android:radius="0dp" /> </shape>
其中 android:angle="270" 代表方向, 270表示从上到下,180表示从右到左,默认从左
②.复杂一点的类型。
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:type="radial" android:gradientRadius="250" android:startColor="#E9E9E9" android:endColor="#D4D4D4" /> </shape> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <gradient android:angle="0" android:startColor="#FFdaf3fc" android:centerColor="#FFd4e9a9" android:endColor="#FFdaf3fc"/> </shape>
在这里要注意android:type="radial"类型的使用会有不同的效果,android:centerColor="#FFd4e9a9" 通常这个也不是被人常用。
③.再复杂一点的类型。
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:startColor="#509245" android:centerColor="#3e8532" android:endColor="#509245" android:type="linear" android:angle="90" android:centerX="0.5" android:centerY="0.5" /> <padding android:left="7dp" android:top="7dp" android:right="7dp" android:bottom="7dp" /> <corners android:radius="4dp" /> </shape>
android:shape 配置的是图形的形式,主要包括方形、圆形等,上边代码为方形。gradient节点主要配置起点颜色、终点颜色、中间点的坐标、中间点的颜色、渐变角度(90度为上下渐变,0为左右渐变),padding节点主要配置上下左右边距,corners节点配置四周园角的半径。
二、使用方法:android:background="@drawable/shape_background_grey"