自己动手,丰衣足食——一个简单却高效的图像旋转算法

    这几天开始接触一个摄像头驱动程序,碰到了一个棘手的问题——就是摄像头出来的图像数据和LCD屏幕的分辨率倒是一致,但是宽和高刚好颠倒。比如,摄像头支持QVGA的分辨率,也就是得到的图像大小为320×240。LCD也刚好是QVGA的分辨率,不过它的尺寸为240×320。摄像头的数据必须旋转90度,才能正好放到LCD里面。旋转90度?那不是屏幕上显示的数据就是颠倒了的吗?呵呵,处理这个问题很简单,安装镜头的时候对应转一下就可以了,这样可以保证旋转了的数据刚好和取景的方向一致。不过,由于摄像头总线出来的数据必定是320个像素一行,此摄像头没有办法进行硬件变换,让出来的数据变成240个像素一行,这个转换就必须借助软件来完成了!
    用软件旋转有很多种做法,不管用什么做法,一定要保证效率很高,这样才有实用性。因为这个旋转实际上是为摄像头取景预览准备的,预览时的数据每秒15帧,数据流量还是比较大的,如果整个算法比较慢,预览本身就要耗费掉大量的CPU时间,势必影响CPU做其他同样重要的事情。而且,整个开发都是针对ARM进行的,在嵌入式平台上,每一点CPU资源,都要充分节省。我的朋友在之前用过两种算法,一种是直接像素拷贝,比如把坐标(a,b)的像素点拷贝到(b,a)处,一个双重循环下来,整个屏幕的内容都旋转了一遍。但是这样做一遍要100多个毫秒,想一想,每秒需要15帧的预览图像,这个值只能让系统显示几帧图像,且CPU也耗费了100%!由于是在INTEL的平台上面开发,所以我们自然想到了用INTEL的IPP函数进行旋转。不过得到的结果也不让人满意,因为INTEL没有提供单独大图像旋转函数,提供的函数是一个复合功能函数,在能够进行旋转的同时,还包含了RESIZE(调整图像大小)的功能,想必没有为这种旋转操作进行单独的优化,所以转一幅图像也要100毫秒左右。太慢,太慢!要么用汇编写一个函数?又觉得维护起来挺麻烦,不便于以后的移植。先还是考虑一下用纯C能不能把这个问题搞定吧。
    顺便说一下,我们处理的图像数据是YCbCr数据,每个通道的数据单独处理,每个像素占用1个字节。在32位处理器上面,一次读写4个字节可能是最高效的。考虑到ARM上面读写内存是系统效率的一个瓶颈(在其他系统上面也是如此),所以我们又把对前面的算法进行了改进。IPP函数我们没有办法改,所以就只有在自己的土办法上进行加工。以前一次写入1个像素,也就是1个字节,这次我们先算出4个相邻像素的值,然后一次性写入。得到的测试结果让我们鼓舞,成绩大概为80毫秒一帧。80毫秒!快了一些,但是远远不能满足我们的要求。
    但是我的信心还是得到了很大的提高,对性能的追求让我泡了一杯咖啡,端端正正地坐在办公桌前面,开始了新的思考当中。既然一次可以写入4个字节,那么一次也可以读出4个字节!我又重新调整了代码,由于读和写的方向不一致(一个是沿像素多的那个方向,一个是沿像素少的那个方向),所以要兼顾两个方向的读写,代码写起来就稍微繁杂一些,必须把屏幕划成4×4的小矩形块处理,处理的时候用到了大量的局部变量以及移位操作,看起来代码有点乱。但是得到的结果令我们惊叹——每帧数据处理只要26毫秒!这个值已经非常快了,不仅仅可以每秒处理足够多的帧数,且能够让CPU腾出更多的时间来干别的事情。它比一个单纯的memcpy操作慢不了多少。爽哉!
    为什么减少了读内存操作的次数,比增加了写内存操作的次数,给系统性能的提升更多呢?也许是一次读4个字节,更利于流水线的处理吧!呵呵
    当然,如果摄像头硬件支持图像90度旋转,也就没有上面这些过程了。我也最希望有“硬办法”解决这个问题,让CPU能够充分解放出来。硬的不行就来软的吧,呵呵,至于采用什么算法,一定要根据具体的条件来选择最合适的,这不,自己动手,丰衣足食。
   

你可能感兴趣的:(自己动手,丰衣足食——一个简单却高效的图像旋转算法)