由于之前将OpenCV封装为codec时程序运行极慢,为了使DSP能够运行OpenCV的一些图像处理算法,现在决定采取三个措施:
1,将OpenCV移植到ARM端,ARM端的程序进行基本的内存分配、重要数据结构的初始化、运行一些非常简单的OpenCV函数,而将一些复杂的OpenCV算法放到DSP端执行。
2,将OpenCV集成到C6Accel中,对于一些C6Accel中已经有的算法就不再用OpenCV实现了,降低了移植复杂度,同时,使用VLIB和IMGLIB中优化过的算法那效率会高很多。
3,考虑到我们开发板上进来的视频流是YUV422SP格式,而OpenCV使用BGR格式,如果进行颜色空间转换,那会占用大量的CPU资源,所以,我们现在就放弃该任务,直接使用灰度图像进行处理,降低了算法的复杂度,进而大大降低的DSP负载。
移植OpenCV到ARM有两种方法,一是将源码编译成库(可以是动态库或者静态库),二是直接将源码添加进应用程序工程中。
对于第一种方法,我进行了一些尝试,遇到了各种困难,包括编译无法通过、编译通过却无法编译成静态库,编成静态库之后调用又出现问题。后来经过思考,发现OpenCV源码是不能直接编译成库来使用的,必须进行一定修改,于是放弃了第一种方法,转而直接将OpenCV源码添加到应用程序源码中。
在我们的开发板上,ARM上运行Linux操作系统,指针操作时使用的是虚拟地址,而DSP端使用的是物理地址,所以,在Linux上连续的地址在DSP看来其实是不连续的。因此,我们需要修改OpenCV的内存分配函数,使得在Linux中分配的内存是在物理上连续的。这可以通过TI提供的CMEM模块实现。
对于ARM端OpenCV源程序的修改主要就是在cxalloc.c中修改icvAllocDefault函数和icvFreeDefault函数。OpenCV默认的内存分配管理是使用最常见的malloc和free,这在我们的嵌入式系统中是不行的,所以我们该用CMEM提供的内存分配API来替换之。
修改过后的icvAllocDefault和icvFreeDefault函数如下所示:
CMEM_AllocParams C6accel_cvCmemParams = {CMEM_HEAP, CMEM_CACHED, 8};
static void* icvAllocDefault( size_t size ) { static int cvCmemInitCalled = 0; if (cvCmemInitCalled == 0) { CMEM_init(); cvCmemInitCalled = 1; }
uchar* udata = (uchar*)CMEM_alloc(size + sizeof(void*) + CV_MALLOC_ALIGN, &C6accel_cvCmemParams); if(!udata) return NULL; uchar** adata = cvAlignPtr((uchar**)udata + 1, CV_MALLOC_ALIGN); adata[-1] = udata; return adata; }
static int icvFreeDefault(void* ptr) { if(ptr) { uchar* udata = ((uchar**)ptr)[-1]; CMEM_free(udata, &C6accel_cvCmemParams); } return CV_OK; } |
在编写内存分配函数时需要注意字节对齐的问题,上面的alloc函数通过一点小技巧实现了分配内存按照32bit对齐,具体实现请研究源码。
对于DSP端我们使用EMCV,由于之前已经修改过EMCV并封装成codec移植到了开发板上正常运行,所以现在可以直接拿来用了。不过现在新添加了一些功能,所以需要从OpenCV 1.0中copy一些代码粘贴进EMCV,这个任务太简单,这里不再详细叙述,请直接参考源码。
这部分的修改和集成VLIB到C6Accel很类似,步骤也差不多,区别就是集成VLIB时使用的是库文件,而现在使用源文件,具体过程包括以下步骤:
1, 将EMCV源文件(包括头文件)添加到~/dsp/alg/src目录下。
2, 添加C6accel_ti_opencvFunctionCall.c到~/dsp/alg/src目录下,该文件是用来根据ID匹配对应OpenCV函数的。
3, 修改~/dsp/alg/project/C6Aceel.pjt文件,扩展工程源文件目录以包含新添加的OpenCV源文件。
4, 修改~/dsp/alg/include/C6Accel.h,包含OpenCV头文件,定义各OpenCV函数的ID。
5, 修改~/dsp/alg/include/iC6Accel_ti.h,添加OpenCV各函数的ID定义,添加各OpenCV函数所需参数的结构体的定义。
6, 修改~/soc/packages/ti/c6accel/iC6Accel_ti.h,定义OpenCV各个函数的ID,定义各个OpenCV函数所需参数的结构体。
7, 在~/soc/c6accelw中添加c6accelw_opencv.c和c6accelw_opencv.h,头文件定义各OpenCV函数的参数,c文件是具体的封装实现。
8, 修改~/soc/c6accelw/c6accelw.c,包含c6accelw_opencv.h该头文件。
这里还是在sobel程序的基础上测试OpenCV的移植效果,对该程序的修改还是集中在video.c文件中,需要在其中添加以下代码:
CvSize size = cvSize(envp->imageWidth, envp->imageHeight); // pal frame, 422, YUV422 semi-planar CvPoint point1 = cvPoint(envp->imageWidth / 2 - 50, envp->imageHeight / 2 - 50); CvPoint point2 = cvPoint(envp->imageWidth / 2 + 50, envp->imageHeight / 2 + 50); // draw a rectangle in the middle of an image CvPoint center = cvPoint(envp->imageWidth / 2, envp->imageHeight / 2); CvScalar color = CV_RGB(0, 255, 0); // green IplImage *img = NULL;
img = cvCreateImage(size, IPL_DEPTH_8U, 1);
……
memcpy(img->imageData, Buffer_getUserPtr(hVidBuf), img->imageSize); //cvRectangle(img, point1, point2, color, CV_AA, 8, 0); C6accel_cvRectangle(hC6, img, point1, point2, color, 1, 8/*CV_AA*/, 0); C6accel_cvCircle(hC6, img, center, 50, color, 1, 8 ,0); memcpy(Buffer_getUserPtr(hDispBuf), img->imageData, img->imageSize); |
编译过程和集成VLIB是一样的。
cd /opt/ti/dvsdk_3_10_00_19_c6accel_example make c6accel make codec make demos make install |
将程序下载到开发板行运行,串口输出错误提示信息:
Encode Decode demo ARM Load: 95% DSP Load: 56% Display Type: D1 PAL Video Codec: H.264 BP Video fps: 13 fps Video bit rate: 0 kbps Video resolution: 720x576 Time: 00:00:09
Encode Decode demo ARM Load: 95% DSP Load: 42% Display Type: D1 PAL Video Codec: H.264 BP Video fps: 12 fps Video bit rate: 0 kbps Video resolution: 720x576 Time: 00:00:11
Deallocation error Deallocation error *** glibc detected *** ./sobel: free(): invalid pointer: 0x42c24490 *** |
还是内存管理的问题,分配没问题,释放时出问题了。经过多方排查,发现是修改OpenCV 1.0的内存管理函数时修改错了,有两个个地方都多加了一个取地址符&,
cvReleaseData函数中: cvFree( &ptr ); 改成: cvFree( ptr );
cvReleaseImageHeader函数中: cvFree( &img ); 改成 cvFree( img ); |
修改之后,程序运行正常。