【Compute Vision】【Color】图像RGB与YUV转换优化

来源:互联网 发布:曦力 mac 编辑:程序博客网 时间:2024/06/07 15:43


转载之:http://blog.csdn.net/songzitea

本文主要介绍如何优化您自己的CODE,实现软件的加速。我们一个图象模式识别的项目,需要将RGB格式的彩色图像先转换成黑白图像。图像转换的公式如下:

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. Y = 0.299 * R + 0.587 * G + 0.114 * B  

图像尺寸640*480*24bit,RGB图像已经按照RGBRGB顺序排列的格式,放在内存里面了。以下是输入和输出的定义:

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. #define XSIZE 640  
  2. #define YSIZE 480  
  3. #define IMGSIZE XSIZE * YSIZE  
  4.   
  5. typedef struct RGB  
  6. {  
  7.        unsigned char R;  
  8.        unsigned char G;  
  9.        unsigned char B;  
  10.   
  11. }RGB;  
  12. struct RGB in[IMGSIZE]; //需要计算的原始数据  
  13. unsigned char out[IMGSIZE]; //计算后的结果  

优化原则:图像是一个2D数组,我用一个一维数组来存储。编译器处理一维数组的效率要高过二维数组。第一步,先写一个代码:

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. void calc_lum()  
  2. {  
  3.     int i;  
  4.     for(i = 0; i < IMGSIZE; i++)  
  5.     {  
  6.        double r,g,b,y;  
  7.        unsigned char yy;  
  8.   
  9.         r = in[i].r;  
  10.         g = in[i].g;  
  11.         b = in[i].b;  
  12.   
  13.         y = 0.299 * r + 0.587 * g + 0.114 * b;  
  14.         yy = y;  
  15.         out[i] = yy;  
  16.     }  
  17. }  

这大概是能想得出来的最简单的写法了,实在看不出有什么毛病,好了,编译一下跑一跑吧。这个代码分别用vc6.0和gcc编译,生成2个版本,分别在pc上和我的embedded system上面跑。速度多少?在PC上,由于存在硬件浮点处理器,CPU频率也够高,计算速度为20秒。我的embedded system,没有以上2个优势,浮点操作被编译器分解成了整数运算,运算速度为120秒左右。

去掉浮点运算

上面这个代码还没有跑,我已经知道会很慢了,因为这其中有大量的浮点运算。只要能不用浮点运算,一定能快很多。Y = 0.299 * R + 0.587 * G + 0.114 * B;这个公式怎么能用定点的整数运算替代呢?0.299 * R可以如何化简?

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. Y = 0.299 * R + 0.587 * G + 0.114 * B;  
  2. Y = D + E + F;  
  3. D = 0.299 * R;  
  4. E = 0.587 * G;  
  5. F = 0.114 * B;  
我们就先简化算式D吧!RGB的取值范围都是0~255,都是整数,只是这个系数比较麻烦,不过这个系数可以表示为:0.299 = 299 / 1000;所以 D = ( R * 299) / 1000;

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. Y = (R * 299 + G * 587 + B * 114) / 1000  
这一下,能快多少呢? Embedded system上的速度为45秒; PC上的速度为2秒;

0.299 * R可以如何化简

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. Y = 0.299 * R + 0.587 * G + 0.114 * B;  
  2. Y = (R * 299 + G * 587 + B * 114) / 1000;  
这个式子好像还有点复杂,可以再砍掉一个除法运算。前面的算式D可以这样写:

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. 0.299=299/1000=1224/4096  
所以 D = (R * 1224) / 4096
[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. Y=(R*1224)/4096+(G*2404)/4096+(B*467)/4096  
再简化为:
[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. Y=(R*1224+G*2404+B*467)/4096  

这里的/4096除法,因为它是2的N次方,所以可以用移位操作替代,往右移位12bit就是把某个数除以4096了。

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. void calc_lum()  
  2. {  
  3.     int i;  
  4.     for(i = 0; i < IMGSIZE; i++){  
  5.         int r,g,b,y;  
  6.         r = 1224 * in[i].r;  
  7.         g = 2404 * in[i].g;  
  8.         b = 467 * in[i].b;  
  9.   
  10.         y = r + g + b;  
  11.         y = y >> 12; //这里去掉了除法运算  
  12.         out[i] = y;  
  13.     }  
  14. }  

这个代码编译后,又快了20%。虽然快了不少,还是太慢了一些,20秒处理一幅图像。

查表方式

我们回到这个式子:

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. Y = 0.299 * R + 0.587 * G + 0.114 * B;  
  2. Y=D+E+F;  
  3. D=0.299*R;  
  4. E=0.587*G;  
  5. F=0.114*B;  
RGB的取值有文章可做,RGB的取值永远都大于等于,小于等于255,我们能不能将DEF都预先计算好呢?然后用查表算法计算呢?我们使用3个数组分别存放DEF256种可能的取值,然后......

查表数组初始化

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. int D[256],F[256],E[256];  
  2. void table_init( ){  
  3.     int i;  
  4.     for(i=0;i<256;i++){  
  5.         D[i]=i*1224;   
  6.         D[i]=D[i]>>12;  
  7.   
  8.         E[i]=i*2404;   
  9.         E[i]=E[i]>>12;   
  10.   
  11.         F[i]=i*467;   
  12.         F[i]=F[i]>>12;  
  13.     }  
  14. }  
  15.   
  16. void calc_lum(){  
  17.     int i;  
  18.     for(i = 0; i < IMGSIZE; i++){  
  19.         int r,g,b,y;  
  20.         r = D[in[i].r];//查表  
  21.         g = E[in[i].g];  
  22.         b = F[in[i].b];  
  23.         y = r + g + b;  
  24.         out[i] = y;  
  25.     }  
  26. }  

这一次的成绩把我吓出一身冷汗,执行时间居然从30秒一下提高到了2秒!在PC上测试这段代码,眼皮还没眨一下,代码就执行完了。一下提高15倍,爽不爽?

继续优化.很多embedded system的32bit CPU,都至少有2个ALU,能不能让2个ALU都跑起来?

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. void calc_lum(){  
  2.     int i;  
  3.     for(i = 0; i < IMGSIZE; i += 2){ //一次并行处理2个数据  
  4.         int r,g,b,y,r1,g1,b1,y1;  
  5.         r = D[in[i].r];//查表 //这里给第一个ALU执行  
  6.         g = E[in[i].g];  
  7.         b = F[in[i].b];  
  8.   
  9.         y = r + g + b;  
  10.         out[i] = y;  
  11.   
  12.         r1 = D[in[i + 1].r];//查表 //这里给第二个ALU执行  
  13.         g1 = E[in[i + 1].g];  
  14.         b1 = F[in[i + 1].b];  
  15.   
  16.         y = r1 + g1 + b1;  
  17.         out[i + 1] = y;  
  18.   
  19.     }  
  20. <span style="font-family:SimSun;">}</span>   
2ALU处理的数据不能有数据依赖,也就是说:某个ALU的输入条件不能是别的ALU的输出,这样才可以并行。这次成绩是1秒。查看这个代码:

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. int D[256],F[256],E[256]; //查表数组  
  2. void table_init(){  
  3.     int i;  
  4.     for(i=0;i<256;i++) {  
  5.         D[i]=i*1224;   
  6.         D[i]=D[i]>>12;  
  7.   
  8.         E[i]=i*2404;   
  9.         E[i]=E[i]>>12;   
  10.   
  11.         F[i]=i*467;   
  12.         F[i]=F[i]>>12;  
  13.     }  
  14. }  

到这里,似乎已经足够快了,但是我们反复实验,发现,还有办法再快!可以将

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. int D[256],F[256],E[256]; //查表数组  
更改为
[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. unsigned short D[256],F[256],E[256]; //查表数组  

这是因为编译器处理int类型和处理unsigned short类型的效率不一样。再改动

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. inline void calc_lum(){  
  2.     int i;  
  3.     for(i = 0; i < IMGSIZE; i += 2){ //一次并行处理2个数据  
  4.         int r,g,b,y,r1,g1,b1,y1;  
  5.         r = D[in[i].r];//查表 //这里给第一个ALU执行  
  6.         g = E[in[i].g];  
  7.         b = F[in[i].b];  
  8.         y = r + g + b;  
  9.   
  10.         out[i] = y;  
  11.         r1 = D[in[i + 1].r];//查表 //这里给第二个ALU执行  
  12.         g1 = E[in[i + 1].g];  
  13.         b1 = F[in[i + 1].b];  
  14.   
  15.         y = r1 + g1 + b1;  
  16.         out[i + 1] = y;  
  17.     }  
  18. }  
将函数声明为inline,这样编译器就会将其嵌入到母函数中,可以减少CPU调用子函数所产生的开销。这次速度:0.5秒。其实,我们还可以飞出地球的!如果加上以下措施,应该还可以更快:1) 把查表的数据放置在CPU的高速数据CACHE里面;2)把函数calc_lum()用汇编语言来写.其实,CPU的潜力是很大的,1) 不要抱怨你的CPU,记住一句话:“只要功率足够,砖头都能飞!”2)同样的需求,写法不一样,速度可以从120秒变化为0.5秒,说明CPU的潜能是很大的!看你如何去挖掘。

RGB到YUV的转换算法做以总结。

Y =   0.299R + 0.587G + 0.114B
U = -0.147R - 0.289G + 0.436B
V =  0.615R - 0.515G - 0.100B

[plain] view plaincopyprint?在CODE上查看代码片派生到我的代码片
  1. #deinfe SIZE 256  
  2. #define XSIZE 640  
  3. #define YSIZE 480  
  4.   
  5. #define IMGSIZE XSIZE * YSIZE  
  6.   
  7. typedef struct RGB{  
  8.   
  9.        unsigned char r;  
  10.        unsigned char g;  
  11.        unsigned char b;  
  12.   
  13. }RGB;  
  14.   
  15. struct RGB in[IMGSIZE];   
  16. unsigned char out[IMGSIZE * 3];   
  17.   
  18. unsigned short Y_R[SIZE],Y_G[SIZE],Y_B[SIZE],U_R[SIZE],U_G[SIZE],U_B[SIZE],V_R[SIZE],V_G[SIZE],V_B[SIZE]; //查表数组  
  19. void table_init(){  
  20.     int i;  
  21.     for(i = 0; i < SIZE; i++){  
  22.         Y_R[i] = (i * 1224) >> 12; //Y  
  23.         Y_G[i] = (i * 2404) >> 12;   
  24.         Y_B[i] = (i * 467)  >> 12;  
  25.   
  26.         U_R[i] = (i * 602)  >> 12; //U  
  27.         U_G[i] = (i * 1183) >> 12;   
  28.         U_B[i] = (i * 1785) >> 12;  
  29.   
  30.         V_R[i] = (i * 2519) >> 12; //V  
  31.         V_G[i] = (i * 2109) >> 12;   
  32.         V_B[i] = (i * 409)  >> 12;  
  33.     }  
  34. }   
  35.   
  36. inline void calc_lum(){  
  37.     int i;  
  38.     for(i = 0; i < IMGSIZE; i += 2) {       
  39.         out[i]               = Y_R[in[i].r] + Y_G[in[i].g] + Y_B[in[i].b]; //Y  
  40.         out[i + IMGSIZE]     = U_B[in[i].b] - U_R[in[i].r] - U_G[in[i].g]; //U  
  41.         out[i + 2 * IMGSIZE] = V_R[in[i].r] - V_G[in[i].g] - V_B[in[i].b]; //V   
  42.   
  43.         out[i + 1]                = Y_R[in[i + 1].r] + Y_G[in[i + 1].g] + Y_B[in[i + 1].b]; //Y  
  44.         out[i  + 1 + IMGSIZE]     = U_B[in[i + 1].b] - U_R[in[i + 1].r] - U_G[in[i + 1].g]; //U  
  45.         out[i  + 1 + 2 * IMGSIZE] = V_R[in[i + 1].r] - V_G[in[i + 1].g] - V_B[in[i + 1].b]; //V  
  46.     }  
  47. }  

这种算法应该是非常快的了.


0 0