【性能优化】优化笔记之一:图像RGB与YUV转换优化

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

 

Y = 0.299 * R + 0.587 * G + 0.114 * B

 

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

 

#define XSIZE 640
#define YSIZE 480
#define IMGSIZE XSIZE * YSIZEtypedef struct RGB
{unsigned char R;unsigned char G;unsigned char B;}RGB;
struct RGB in[IMGSIZE]; //需要计算的原始数据
unsigned char out[IMGSIZE]; //计算后的结果

 

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

 

void calc_lum()
{int i;for(i = 0; i < IMGSIZE; i++){double r,g,b,y;unsigned char yy;r = in[i].r;g = in[i].g;b = in[i].b;y = 0.299 * r + 0.587 * g + 0.114 * b;yy = y;out[i] = yy;}
}

 

这大概是能想得出来的最简单的写法了,实在看不出有什么毛病,好了,编译一下跑一跑吧。这个代码分别用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可以如何化简?

 

Y = 0.299 * R + 0.587 * G + 0.114 * B;
Y = D + E + F;
D = 0.299 * R;
E = 0.587 * G;
F = 0.114 * B;

我们就先简化算式 D吧! RGB的取值范围都是 0~255,都是整数,只是这个系数比较麻烦,不过这个系数可以表示为:0.299 = 299 / 1000;所以 D = ( R * 299) / 1000;

 

 

Y = (R * 299 + G * 587 + B * 114) / 1000

这一下,能快多少呢? Embedded system上的速度为 45秒; PC上的速度为 2秒;

 

0.299 * R可以如何化简

 

Y = 0.299 * R + 0.587 * G + 0.114 * B;
Y = (R * 299 + G * 587 + B * 114) / 1000;

这个式子好像还有点复杂,可以再砍掉一个除法运算。前面的算式D可以这样写:

 

 

0.299=299/1000=1224/4096

所以  D = (R * 1224) / 4096

Y=(R*1224)/4096+(G*2404)/4096+(B*467)/4096

再简化为:

Y=(R*1224+G*2404+B*467)/4096

 

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

 

void calc_lum()
{int i;for(i = 0; i < IMGSIZE; i++){int r,g,b,y;r = 1224 * in[i].r;g = 2404 * in[i].g;b = 467 * in[i].b;y = r + g + b;y = y >> 12; //这里去掉了除法运算out[i] = y;}
}

 

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

查表方式

我们回到这个式子:

 

Y = 0.299 * R + 0.587 * G + 0.114 * B;
Y=D+E+F;
D=0.299*R;
E=0.587*G;
F=0.114*B;

RGB的取值有文章可做, RGB的取值永远都大于等于 ,小于等于 255,我们能不能将 D, E, F都预先计算好呢?然后用查表算法计算呢?我们使用 3个数组分别存放 DEF的 256种可能的取值,然后......

 

查表数组初始化

 

int D[256],F[256],E[256];
void table_init( ){int i;for(i=0;i<256;i++){D[i]=i*1224; D[i]=D[i]>>12;E[i]=i*2404; E[i]=E[i]>>12; F[i]=i*467; F[i]=F[i]>>12;}
}void calc_lum(){int i;for(i = 0; i < IMGSIZE; i++){int r,g,b,y;r = D[in[i].r];//查表g = E[in[i].g];b = F[in[i].b];y = r + g + b;out[i] = y;}
}

 

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

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

 

void calc_lum(){int i;for(i = 0; i < IMGSIZE; i += 2){ //一次并行处理2个数据int r,g,b,y,r1,g1,b1,y1;r = D[in[i].r];//查表 //这里给第一个ALU执行g = E[in[i].g];b = F[in[i].b];y = r + g + b;out[i] = y;r1 = D[in[i + 1].r];//查表 //这里给第二个ALU执行g1 = E[in[i + 1].g];b1 = F[in[i + 1].b];y = r1 + g1 + b1;out[i + 1] = y;}
} 

2个 ALU处理的数据不能有数据依赖,也就是说:某个 ALU的输入条件不能是别的 ALU的输出,这样才可以并行。这次成绩是 1秒。查看这个代码:

 

int D[256],F[256],E[256]; //查表数组
void table_init(){int i;for(i=0;i<256;i++) {D[i]=i*1224; D[i]=D[i]>>12;E[i]=i*2404; E[i]=E[i]>>12; F[i]=i*467; F[i]=F[i]>>12;}
}

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

 

int D[256],F[256],E[256]; //查表数组

更改为

unsigned short D[256],F[256],E[256]; //查表数组

 

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

 

inline void calc_lum(){int i;for(i = 0; i < IMGSIZE; i += 2){ //一次并行处理2个数据int r,g,b,y,r1,g1,b1,y1;r = D[in[i].r];//查表 //这里给第一个ALU执行g = E[in[i].g];b = F[in[i].b];y = r + g + b;out[i] = y;r1 = D[in[i + 1].r];//查表 //这里给第二个ALU执行g1 = E[in[i + 1].g];b1 = F[in[i + 1].b];y = r1 + g1 + b1;out[i + 1] = y;}
}

将函数声明为 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

 

#deinfe SIZE 256
#define XSIZE 640
#define YSIZE 480#define IMGSIZE XSIZE * YSIZEtypedef struct RGB{unsigned char r;unsigned char g;unsigned char b;}RGB;struct RGB in[IMGSIZE]; 
unsigned char out[IMGSIZE * 3]; 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]; //查表数组
void table_init(){int i;for(i = 0; i < SIZE; i++){Y_R[i] = (i * 1224) >> 12; //YY_G[i] = (i * 2404) >> 12; Y_B[i] = (i * 467)  >> 12;U_R[i] = (i * 602)  >> 12; //UU_G[i] = (i * 1183) >> 12; U_B[i] = (i * 1785) >> 12;V_R[i] = (i * 2519) >> 12; //VV_G[i] = (i * 2109) >> 12; V_B[i] = (i * 409)  >> 12;}
} inline void calc_lum(){int i;for(i = 0; i < IMGSIZE; i += 2) {     out[i]               = Y_R[in[i].r] + Y_G[in[i].g] + Y_B[in[i].b]; //Yout[i + IMGSIZE]     = U_B[in[i].b] - U_R[in[i].r] - U_G[in[i].g]; //Uout[i + 2 * IMGSIZE] = V_R[in[i].r] - V_G[in[i].g] - V_B[in[i].b]; //V out[i + 1]                = Y_R[in[i + 1].r] + Y_G[in[i + 1].g] + Y_B[in[i + 1].b]; //Yout[i  + 1 + IMGSIZE]     = U_B[in[i + 1].b] - U_R[in[i + 1].r] - U_G[in[i + 1].g]; //Uout[i  + 1 + 2 * IMGSIZE] = V_R[in[i + 1].r] - V_G[in[i + 1].g] - V_B[in[i + 1].b]; //V}
}

 

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

===========================================================
转载请注明出处:http://blog.csdn.net/songzitea/article/details/10474207
===========================================================

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/265558.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

wxpython使用方法_python图形界面开发之wxPython树控件使用方法详解

wxPython树控件介绍树(tree)是一种通过层次结构展示信息的控件&#xff0c;如下图所示是树控件示例&#xff0c;左窗口中是树控件&#xff0c;在wxPython中树控件类是wx.TreeCtrl。wx.TreeCtrl常用的方法有AddRoot(text, image-1, selImage-1, dataNone)。添加根节点&#xff0…

DSP_SCI

F2833x Serial Communication Interface 簡介 串行通信接口&#xff08;SCI&#xff09;模塊是一個串行I / O端口&#xff0c;允許F2833x與其他外圍設備之間的異步通信。 它通常被稱為UART&#xff08;通用異步接收器發送器&#xff09;&#xff0c;通常根據RS232標準使用。 SC…

如何让自己的写的程序在阿里云一直运行

参考&#xff1a;如何让自己的写的程序在阿里云一直运行 作者&#xff1a;图触靓 发布时间&#xff1a; 2020-08-02 11:53:28 网址&#xff1a;https://blog.csdn.net/bhbhhyg/article/details/107742311 目录1、下载screen&#xff1a;2、运行screen&#xff0c;创建一个scree…

C程序设计语言--第五章:指针与数组

为什么80%的码农都做不了架构师&#xff1f;>>> 指针是一种保存变量地址的变量. 5.1 指针与地址 通常的机器都有一系列连续编号或编址的存储单元,这些存储单元可以单个进行操纵,也可以连续成组的方式操纵.指针是能够存放一个地址的一组存储单元(通常是两个或四个…

java list 自定义类型转换_使用Java Stream API将List按自定义分组规则转换成Map的一个例子...

本文完整测试代码见文末。测试数据是List里的4个员工对象实例&#xff1a;根据员工所在的城市进行分组&#xff1a;结果分成了三组&#xff1a;第一组的员工在上海&#xff1a;第二组的员工在成都&#xff1a;统计每组员工个数&#xff1a;把员工进行分组&#xff0c;得分大于1…

安卓app与阿里云服务器的无线通信(非局域网)

参考&#xff1a;安卓app与阿里云服务器的无线通信&#xff08;非局域网&#xff09; 作者&#xff1a;图触靓 发布时间&#xff1a; 2020-08-01 16:13:14 网址&#xff1a;https://blog.csdn.net/bhbhhyg/article/details/107732156 目录写在阿里云里面的服务器代码&#xff1…

10.继承和派生

2019独角兽企业重金招聘Python工程师标准>>> 类成员访问权限&#xff1a; 成员访问权限&#xff1a;私有成员&#xff1a;只能被类自身的成员和友元访问&#xff1b; 公有成员&#xff1a;可以被任何普通函数和任何类的成员函数或子类访问&#xff1b; 保护成员&a…

1185 威佐夫游戏 V2

1185 威佐夫游戏 V2 有2堆石子。A B两个人轮流拿&#xff0c;A先拿。每次可以从一堆中取任意个或从2堆中取相同数量的石子&#xff0c;但不可不取。拿到最后1颗石子的人获胜。假设A B都非常聪明&#xff0c;拿石子的过程中不会出现失误。给出2堆石子的数量&#xff0c;问最后谁…

java date 判断是否是今天_java——推断日期是否在今天之前

这里说的日期是指字符串的日期格式&#xff0c;如“2014-10-15”&#xff0c;我们要推断这个日期是否在今天之前&#xff0c;网上看到好多推断的方法&#xff0c;都是拿这个日期转换成Date对象 然后与new Date()比較&#xff0c;使用comparetTo() 或者before()方法&#xff0c…

树莓派与阿里云服务器之间的无线通信(非局域网)

参考&#xff1a;Linux虚拟机sqlite数据库安装教程 作者&#xff1a;图触靓 发布时间&#xff1a; 2020-08-01 15:45:026 网址&#xff1a;https://blog.csdn.net/bhbhhyg/article/details/107731166 购买服务器后。我们可以点击控制台&#xff0c;找到实例列表&#xff0c;点击…

IOS开发博客学习

M了个J :http://www.cnblogs.com/mjios/tag/objective-c/ http://www.cnblogs.com/tianjian/p/3358602.html Cocos2d-x VS环境配置 泰然论坛&#xff1a;http://www.ityran.com/archives/category/cocos2d-iphone 1.http://www.cnblogs.com/zilongshanren/archive/2011/09/19…

CLR via C#学习笔记-第十三章-定义接口、继承接口

13.1 类和接口继承 接口是为了实现多继承 所有的类都继承了System.Object&#xff0c;及其四个公共实例方法&#xff1a;ToString、Equals、GetHashCode、GetType。 CLR允许定义接口&#xff0c;它实际只是对一组方法签名进行了统一命名。这些方法不提供任何实现。 类通过指定接…

树莓派与安卓手机app的WIFI通信(局域网通信)

参考&#xff1a;树莓派与安卓手机app的WIFI通信&#xff08;局域网通信&#xff09; 作者&#xff1a;图触靓 发布时间&#xff1a; 2020-07-29 10:18:12 网址&#xff1a;https://blog.csdn.net/bhbhhyg/article/details/107655967 目录树莓派作为服务器安卓端连接服务器的方…

C语言递归练习

1、炮弹一样的球状物体&#xff0c;能够堆积成一个金字塔&#xff0c;在顶端有一个炮弹&#xff0c;它坐落在一个4个炮弹组成的层面上&#xff0c;而这4个炮弹又坐落在一个9个炮弹组成的层面上&#xff0c;以此类推。写一个递归函数CannonBall&#xff0c;这个函数把金字塔的高…

业余剪辑——从拍摄到剪辑到加字幕

工作需要帮公司做一个公司的宣传片&#xff0c;要求不是很高所以就让我这个半吊子“设计”来做视频了。。。 以下设备和软件的使用只是我个人本次的使用的记录&#xff0c;大家按需使用即可 一、拍摄 设备&#xff1a;手机 软件&#xff1a;剪辑大师app&#xff08;应用商店下载…

java mp3 to wav_java实现wavToMP3格式转换详解

这里需要用到一个jar包:jave-1.0.1.jar,下载地址的话自己百度吧.废话不多说直接贴代码&#xff1a;import it.sauronsoftware.jave.AudioAttributes;import it.sauronsoftware.jave.Encoder;import it.sauronsoftware.jave.EncodingAttributes;import java.io.File;public clas…

树莓派作为客户端与WemosD1作为服务器的无线通信(局域网通信)

参考&#xff1a;树莓派作为客户端与WemosD1作为服务器的无线通信&#xff08;局域网通信&#xff09; 作者&#xff1a;图触靓 发布时间&#xff1a; 2020-07-26 21:50:39 网址&#xff1a;https://blog.csdn.net/bhbhhyg/article/details/107599915 目录wemos版作为服务端代码…

Lync Server的环境搭建(五):Lync-Server的安装部署

今天,我们接着来进行Lync-Server服务端的安装。我们分别打开“Lync-DC”和“Lync-Server”这两台虚拟机&#xff0c;切换到“Lync-Server”下&#xff0c;“开始”—“Lync Server安装部署&#xff0c;单击“安装或更新Lync Server系统”。弹出”部署向导“窗口&#xff0c;我们…

NodeMan介绍

近年来&#xff0c;随着nodejs的突飞猛进&#xff0c;node项目数量增长迅猛&#xff0c;node项目完美的阐释了“开箱即用”的理念。小到创业公司&#xff0c;大到阿里这样的巨头&#xff0c;背后均有node的身影。 node项目基于Chrome的V8引擎&#xff0c;使用JavaScript&#x…