2015-10-06 19:06:06 jaych 阅读数 1331

在手机端的图像处理程序中,为了提升算法运行速度,需要对程序代码进行优化。而如何优化?需要在哪些地方进行优化?这些都是我们需要思考的问题。

因此,这篇文章主要对代码优化进行相关总结,并将不定期更新,作为长期的总结笔记。

for循环中,用”!=0”代替”小于N”进行条件判断。


图像算法实现的过程中,会利用for循环语句用重复的操作处理连续的数据。
而在for循环语句中,需要进行条件判断,用“!=0”代替“小于N” 进行条件判断,能够减少1个指令,这是因为转换为汇编指令时,与零比较跳转(CBZ/CBNZ)比跳转指令(B/BL/BX/BLX/BXJ)效率高,在进行N次循环的循环体内则可以减少N个指令的执行。

for(i=0;i<256;i++)

可以修改成:

for(i=256;i!=0i--)

用指针自增代替数组寻址来获取数据。


一般情况下,我们习惯用array[]来获取图像中某个像素点的数据。但是这种方式获取数据的过程需要涉及:
1、获取array的首地址;
2、首地址加上数据位置得到新的内存地址;
3、获取数据。

而指针自增的方式则是,
1、用ptr++指针指向待获取数据的地址;
2、获取数据时直接调用*ptr即可。

另外,在图像处理中,图像一般都比较大(即图像数据很多),这样的情况下,用指针自增的方式可以显著的提升代码运行速度。

如:

for(i=0;i<256;i++)
{
    array[i] ...  ;
}

可以修改为:

 for(i=256;i!=0;i--)
 {
     *ptr ... ;
     prt++;
 }

用查表法对三角函数进行优化


使用三角函数时,可以:
1、按照所需精度预先计算出需要覆盖的范围
2、根据范围内的角度算出三角函数值,在使用时进行查表即可得到相应值。

如,
需要计算到0.001的精度时,角度的取值范围为-90到90。
因此需要预先计算出:90-(-90)/0.001=180000+1个角度对应的三角函数值。

然后在取值时,如果要取sin(3°),则是 sinValue[(3+90)/0.0001].

另外,如果三角函数涉及到除法的计算,或者说需要应用到三角函数的倒数,也可以用查表法的方式进行优化。
即将“1/三角函数”如 1/sin(…) 预先计算好作为查表用。


判断是否为偶数


之前一直都用 y%2==0来判断是否为偶数,其实还可以通过判断 y&1==0 来判断是否为偶数。

2009-02-12 23:03:00 housisong 阅读数 16109

               图形图像处理-之-彩色转化到灰度的速度优化
                   HouSisong@GMail.com  2009.02.08

tag:灰度算法,速度优化,定点数优化,MMX,SSE,SSE2,CPU缓存优化

摘要:
  彩色转化到灰度的速度优化文章包括图形图像处理简单Demo框架和灰度转换的实
现及其速度优化,并演示其使用SIMD指令集的优化;
   本篇文章将第一次提供完整的可以编译的图像处理完整项目代码;
   (以后会用这个框架逐步改写以前的图形图像处理文章)

正文:  
  为了便于讨论,这里只处理32bit的ARGB颜色;代码使用C++;使用的编译器为vc2008;
(经过测试代码也可以在DevC++和xcode下编译通过) 测试使用的CPU为AMD64x2 4200+(2.33G);

速度测试说明:
  只测试内存数据到内存数据的ARGB32颜色的灰度转化;
  测试图片是800*600; fps表示每秒钟的帧数,值越大表示函数越快;

A: 图形图像处理简单Demo框架
   
  我以前写的图形图像处理方面的blog文章都没有完整的可以编译运行的代码,
而仅仅列出了关键的核心代码;经常有网友看了我的文章,但因为不能实际运行看看,
从而对代码的理解不深,也不能把代码移植到自己的项目中使用; 所以决定为我的图形
图像处理系列blog文章建立一个简单的小型的框架;我把它命名为hGraphic32,
它会尽量的小,演示为主,仅支持ARGB32颜色,能够加载和保存bmp图片文件,能够在
多个编译器和平台下编译和运行;
   现在就下载完整项目源代码吧:  完整项目源代码


  <hGraphic32>文件夹里的文件说明:
    "hColor32.h"  : 里面定义了32bitARGB颜色类型Color32,它占用4字节,代表一个颜色;
        TPixels32Ref是图像数据区的描述信息,可以把它理解为一个"指针",指向了Color32构成的像素区;
        IPixels32Buf是图像数据区接口,用于描述一个图像的缓冲区;
    "hPixels32.h" : 里面定义了TPixels32类,它实现了IPixels32Buf接口,用于申请和管理一块内存像素;
    "hStream.h"   : 里面定义了IInputStream输入流接口;
        IBufInputStream数据区输入流接口,继承自IInputStream;
        TFileInputStream文件输入流类,它实现了IBufInputStream接口;
        IOutputStream输出流接口;
        TFileOutputStream文件输出流类,它实现了IOutputStream接口;
     "hBmpFile.h" : 里面定义了TBmpFile类,它负责加载bmp和保存bmp;
     "hGraphic32.h" 文件include了上面的*.h头文件,所以使用的时候,只要#include "hGraphic32.h"就可以了

B: 灰度转化项目
  所有的转换和测试代码都在"ColorToGray/ColorToGray.cpp"文件中(带有main函数的命令行程序);
  "ColorToGray/win_vc/ColorToGray.sln"是windows系统下的vc2008项目文件(测试的时请设定调试运行目录为"..");
  "ColorToGray/win_DevC++/ColorToGray.dev"是windows系统下的DevC++项目文件;
  "ColorToGray/macosx_xcode/ColorToGray.xcodeproj"是macosx系统下的xcode项目文件;
  你也可以自己建立项目,包含ColorToGray.cpp文件和<hGraphic32>文件夹下的所有文件,就可以编译了;

C: 灰度转化公式和代码实现
  文章中用的灰度公式: Gray = R*0.299 + G*0.587  + B*0.114;
 
代码实现:


////////////////////////////////////////////////////////////////////////////////
//速度测试
//==============================================================================
// colorToGray_float           145.49 FPS
////////////////////////////////////////////////////////////////////////////////

D: 将浮点运算转化为定点数(整数)运算
    


////////////////////////////////////////////////////////////////////////////////
//速度测试
//==============================================================================
// colorToGray_int16           355.33 FPS
////////////////////////////////////////////////////////////////////////////////

E: 做一个简单的循环代码展开


////////////////////////////////////////////////////////////////////////////////
//速度测试
//==============================================================================
// colorToGray_int16_expand4   413.22 FPS
////////////////////////////////////////////////////////////////////////////////

F: 一个特别的版本
   在高级语言范围内进行单条指令多数据流计算,减少需要的乘法量;
在乘法运算代价比较高昂的cpu上应该效果不错; (x86上速度可能慢)

////////////////////////////////////////////////////////////////////////////////
//速度测试
//==============================================================================
// colorToGray_int8_opMul      387.97 FPS
////////////////////////////////////////////////////////////////////////////////

G: 内联汇编的MMX实现版本
   注意:这里的MMX代码都只支持x86CPU(奔腾MMX以上CPU);
   在x64下不再有MMX寄存器,而应该使用SEE的XMM寄存器;
   而且在x64模式下vc2008编译器还没有提供内联汇编的直接支持,而必须使用函数指令方式的实现;
   GCC编译器也支持内联汇编模式,但是汇编语法不同,请参考相应的说明;



////////////////////////////////////////////////////////////////////////////////
//速度测试
//==============================================================================
// colorToGray_MMX             590.84 FPS
////////////////////////////////////////////////////////////////////////////////

H: 优化写缓冲的内联汇编的MMX实现版本
  该版本相应于上面的MMX版本只改写了两句:
   一是写内存的movq [edx+ecx*4],mm0 改成了 movntq [edx+ecx*4],mm0 绕过缓存
   二是函数结束的时候调用sfence刷新写入
  完整代码如下:




////////////////////////////////////////////////////////////////////////////////
//速度测试
//==============================================================================
// colorToGray_MMX2            679.50 FPS
////////////////////////////////////////////////////////////////////////////////

I: 使用MMX函数指令方式的实现
  MMX/SSE等特殊指令除了内联汇编来使用外,也可以使用函数指令方式的实现,从而在多种
编译器下都可以使用SIMD相关指令,可移植性也会好很多;
  但现在看来,vc对此的优化还不够,还可能遇到编译器的实现bug;
  (可以考虑使用intel的编译器编译这些代码,感觉优化能力很不错)



////////////////////////////////////////////////////////////////////////////////
//速度测试
//==============================================================================
// colorToGray_MMX_mmh         508.69 FPS
////////////////////////////////////////////////////////////////////////////////


 优化写缓冲的使用MMX函数指令方式的实现



////////////////////////////////////////////////////////////////////////////////
//速度测试
//==============================================================================
// colorToGray_MMX2_mmh        540.78 FPS
////////////////////////////////////////////////////////////////////////////////

J:把测试成绩放在一起:

////////////////////////////////////////////////////////////////////////////////
//CPU: AMD64x2 4200+(2.33G)  800*600 to 800*600
//==============================================================================
// colorToGray_float           145.49 FPS
// colorToGray_int16           355.33 FPS
// colorToGray_int16_expand4   413.22 FPS
// colorToGray_int8_opMul      387.97 FPS
// colorToGray_MMX             590.84 FPS
// colorToGray_MMX2            679.50 FPS
// colorToGray_MMX_mmh         508.69 FPS
// colorToGray_MMX2_mmh        540.78 FPS
////////////////////////////////////////////////////////////////////////////////

ps:用SSE的浮点指令的版本/用SSE2整数指令的版本/利用SSE3的水平加指令等的实现版本有机会时再补充
ps:SIMD特殊指令集的使用框架请参见我的<YUV视频格式到RGB32格式转换的速度优化 中篇>一文,从而
根据CPU对指令集的支持情况动态的调用最优的实现函数版本;

2013-06-28 21:35:58 sonikk 阅读数 8336

1. 使用NDK_DEBUG=1后,编译出来的程序比不使用慢很多,大概效率会降低2.5倍,这个数字真不小了!

2. 在NDK_DEBUG=0的情况下,Java调用一次Native函数,耗费时间在0.147~0.233ms左右

3. 使用系统提供的函数,比自己手动copy内存效率快得多,如使用memcpy或者memset可以提高很多速度

4. 尽量避免除法,比较好的做法是使用倒数进行乘法计算,而倒数可以实现用计算器进行求值,直接写入代码

5. Android的一些cpu在处理float类型的时候非常缓慢,下面是我的一个测试程序,程序跑的是求灰度值,可以明显的对比出来优化前后的效果,简直是立竿见影!

	
测试条件:图片分辨率500x750
[pad] 设备:gt_p6800 双核1.4GHz
[phone] 设备:nexus s 单核1.0GHz
[优化前]-------------------------------------------
        // [pad][debug=0] 
	// [time] 14.132000 (ms)
	// [time] 15.066000 (ms)
	// [time] 25.767000 (ms)


	// [phone][debug=0]
	// [time] 175.110992 (ms)
	// [time] 196.481003 (ms)
	// [time] 113.485001 (ms)
	// [time] 162.052994 (ms)
	// [time] 178.488998 (ms) (平均=164.8)
[优化后]-------------------------------------------
 	// [pad][debug=0] 
 	// [time] 12.625000 (ms)
 
 	// [phone][debug=0] 
 	// [time] 48.099998 (ms)
 	// [time] 52.244999 (ms)
 	// [time] 27.365000 (ms)
 	// [time] 34.356998 (ms)
 	// [time] 23.222000 (ms)
 	// [time] 31.214001 (ms) (平均=35.8)

结论:优化后提升的效率:164.8/35.8 = 4.6   提升360%

6. 在必要的时候可以使用多线程进行优化,这样也可以提升cpu的利用率,从而达到优化的目的:

	
测试条件:图片分辨率500x750
[pad] 设备:gt_p6800 双核1.4GHz
[phone] 设备:nexus s 单核1.0GHz
[优化前]-------------------------------------------
        // [pad][debug=0] 
	// [time] 14.132000 (ms)
	// [time] 15.066000 (ms)
	// [time] 25.767000 (ms)


	// [phone][debug=0]
	// [time] 175.110992 (ms)
	// [time] 196.481003 (ms)
	// [time] 113.485001 (ms)
	// [time] 162.052994 (ms)
	// [time] 178.488998 (ms) (平均=164.8)
[优化后]-------------------------------------------
	// [pad][debug=0]
	// [time] 19.000000 (ms)
	// [time] 10.667000 (ms)
	// [time] 13.659000 (ms)
	// [time] 15.953000 (ms)
	// [time] 28.219999 (ms)
	// [time] 17.202999 (ms)


	// [phone][debug=0]
	// [time] 192.744003 (ms)
	// [time] 124.049004 (ms)
	// [time] 91.658997 (ms)
	// [time] 88.318001 (ms)
	// [time] 92.111000 (ms)
	// [time] 91.551003 (ms)
 	// [time] 31.214001 (ms) (平均=101.7)

结论:优化后提升的效率:164.8/101.7 = 1.62  提升:62%

7. 同时使用int优化以及多线程,效果得到进一步提升!:

	
测试条件:图片分辨率500x750
[pad] 设备:gt_p6800 双核1.4GHz
[phone] 设备:nexus s 单核1.0GHz
[优化前]-------------------------------------------
        // [pad][debug=0] 
	// [time] 14.132000 (ms)
	// [time] 15.066000 (ms)
	// [time] 25.767000 (ms)


	// [phone][debug=0]
	// [time] 175.110992 (ms)
	// [time] 196.481003 (ms)
	// [time] 113.485001 (ms)
	// [time] 162.052994 (ms)
	// [time] 178.488998 (ms) (平均=164.8)
[优化后]-------------------------------------------
	// [pad][debug=0]
	// [time] 7.036000 (ms)
	// [time] 8.646000 (ms)
	// [time] 11.264000 (ms)
	// [time] 15.105000 (ms)
	// [time] 8.559000 (ms)
	// [time] 10.316000 (ms) 平均=10.154333


	// [phone][debug=0]
	// [time] 35.727001 (ms)
	// [time] 42.995998 (ms)
	// [time] 25.725000 (ms)
	// [time] 19.454000 (ms)
	// [time] 27.643000 (ms)
	// [time] 29.118000 (ms) 平均=30.110

结论:优化后提升的效率:164.8/30.110 = 5.47  提升:447%


8. 如果追求更快的速度,可以使用neon技术,以及汇编


9. 使用查表法进行RGB <-> HSY 空间的转换,先用预处理剔除重复像素


10.  分散时间执行,不一定就是点击处理按钮的当时进行,可以开多线程在后台做一些操作,当到达这个点的时候其实大部分任务量已经计算好了


11. 缩小进行计算,然后放大进行混合计算


12. 对实时性要求高的场合,显示的时候可以先用粗质



下面是我最近工作中算法的效率

优化前期甚至

[pad] 4.5s

[phone] 25-30s


优化中期:

	// [pad] [debug=0]
	// [time] 760.336975 (ms)
	// [time] 681.442993 (ms)	//684.579669毫秒

	// [time] 623.708984 (ms)	//624.158042毫秒
	// [time] 633.445984 (ms)	//634.257毫秒
	// [time] 597.619019 (ms)	//597.847208毫秒
	// [time] 627.309998 (ms)	//627.49325毫秒
	// [time] 570.181030 (ms)	//570.961041毫秒

	// [phone] [debug=0]
	// [time] 4956.109863 (ms)	//4962.556毫秒
	// [time] 7306.398926 (ms)	//7313.900167毫秒
	// [time] 5068.775879 (ms)	//5071.079875毫秒

	// [time] 7162.111816 (ms)	//7163.000041毫秒
	// [time] 6936.270996 (ms)	//6937.797708毫秒
	// [time] 5545.577148 (ms)	//5549.424749毫秒
	// [time] 5578.754883 (ms)	//5579.310792毫秒
	// [time] 5208.092773 (ms)	//5214.470333毫秒
	// [time] 5067.886230 (ms)	//5068.809999毫秒
	// [time] 5506.863770 (ms)	//5507.421083毫秒

优化后期:

// [pad] [debug=0]
// [time] 477.792999 (ms)	//483.404708毫秒
// [time] 181.477997 (ms)	//182.157458毫秒
// [time] 179.610001 (ms)	//180.286042毫秒
// [time] 191.210007 (ms)	//191.778毫秒
// [time] 177.595001 (ms)	//179.568376毫秒
// [time] 175.041000 (ms)	//175.818125毫秒

// [phone] [debug=0]
// [time] 795.302002 (ms)	//802.008249毫秒
// [time] 1108.545044 (ms)	//1113.701375毫秒
// [time] 932.200012 (ms)	//954.378375毫秒
// [time] 939.596008 (ms)	//940.612125毫秒
// [time] 834.835022 (ms)	//835.261416毫秒
// [time] 766.768982 (ms)	//767.523749毫秒
// [time] 539.195007 (ms)	//540.059709毫秒
// [time] 561.531006 (ms)	//562.323375毫秒

可以看到,从优化中期到优化后期,[phone]几乎变快了7倍!内存的占用率也降低了很多!

@sonikk 2013-6-30 13:32:05 



研究资料:

Neon的文档:

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0406c/index.html


Neon 原理的ppt介绍:

http://www.arm.com/files/pdf/AT_-_NEON_for_Multimedia_Applications.pdf


ARM NEON 指令(用例)

http://blog.csdn.net/tonyfield/article/details/8597549


ARM NEON Optimization. An Example

http://hilbert-space.de/?p=22


关于neon

http://blog.csdn.net/ccjjnn19890720/article/details/7291228关于neon


ARM-NEON-Intrinsics

http://www.doc88.com/p-703867169475.html


转贴ARM NEON 优化的例子

http://www.cnblogs.com/c6000/archive/2010/09/14/1826324.html


Neon 使用小结

http://blog.csdn.net/chenji001/article/details/4798754


Android NDK 之NEON优化

http://blog.csdn.net/zwcai/article/details/6843531


Android NDK(ARM开发)使用NEON优化

http://luofl1992.is-programmer.com/posts/38686.html


图像处理常用算法

http://www.rosoo.net/a/201108/14913.html


ARM首席工程师:关于Android NDK的10个技巧

http://www.chinaaet.com/article/index.aspx?id=133752


ARM(Android NDK)混编C/C++汇编优化

http://www.2cto.com/kf/201304/200755.html


Android为何要用据说效率很低的Java呢?(二)  

http://blog.163.com/liutian945@126/blog/static/16813804820111182123485/


【好文】iOS - How to convert BGRA video streams to Grayscale SUPER fast.

http://teh1337.nfshost.com/blog.py?post=198


a-very-fast-bgra-to-grayscale-conversion-on-iphone

http://computer-vision-talks.com/2011/02/a-very-fast-bgra-to-grayscale-conversion-on-iphone/


ARM NEON Optimization. An Example

http://hilbert-space.de/?p=22


Introduction to NEON on iPhone

http://wanderingcoder.net/2010/06/02/intro-neon/


Android NDK使用NEON优化,SIMD优化

http://blog.csdn.net/luofl1992/article/details/8759145


详解Android jit

http://hi.baidu.com/cool_parkour/item/570886f9d0732e14e3e3bd7f


 [置顶] Android开发性能优化简介
http://blog.csdn.net/h3c4lenovo/article/details/7669749


jni加载、卸载动态链接库文件

http://ppjava.com/?p=1273


android JNI处理图片的例子

http://blog.csdn.net/xjwangliang/article/details/7065670


bitmap.h

http://mobilepearls.com/labs/native-android-api/include/android/bitmap.h


Android c 通过skia图形库绘制文字

http://www.360doc.com/content/13/0401/16/7891085_275323215.shtml


Chrome for Mac 将换用 Skia 2D 图形库
http://www.guao.hk/tag/skia


skia源码下载:

https://code.google.com/p/skia/

http://www.chromium.org/developers/design-documents/graphics-and-skia


skia官方文档:

https://sites.google.com/site/skiadocs/


【好文】Android有效解决加载大图片时内存溢出的问题

http://www.cnblogs.com/wanqieddy/archive/2011/11/25/2263381.html


Android 读取本地(SD卡)图片

http://blog.csdn.net/knowheart/article/details/7334966


2013-7-1 0:17:59补充:


android官方ndk文档说明:

http://developer.android.com/tools/sdk/ndk/index.html#Docs


Android: NDK编程入门笔记

http://www.cnblogs.com/hibraincol/archive/2011/05/30/2063847.html


给出c&c++程序优化的几个建议,希望对你有帮助

http://blog.csdn.net/wangjiaoyu250/article/details/9185591


采用泰勒级数展开法编写的定点化的开方运算,定点化精度为Q15

http://www.pudn.com/downloads188/sourcecode/math/detail884298.html


提高专业技能之 “Codec定点化”

http://www.cnblogs.com/huaping-audio/archive/2010/07/30/1788753.html


[转]vlc android 代码编译  

http://3792615.blog.163.com/blog/static/778210942012927103347556/


package manager service是怎样选择armeabi/armeabi-v7a中的库文件的?

http://www.cnblogs.com/loveisbug/archive/2013/04/25/3042950.html


开发笔记:android ndk 开发之Application.mk

http://www.2cto.com/kf/201207/143406.html


Android NDK学习 <四> Application.mk简介

http://blog.sina.com.cn/s/blog_602f877001014ptu.html


Android NDK学习 <五> C++ 支持

http://blog.sina.com.cn/s/blog_602f877001014qe5.html


android 代码优化

http://hilary3113.iteye.com/blog/1018700


Android平台上的浮点优化

http://task.zhubajie.com/1804783/


Android开发性能优化浅论之一

http://www.cnblogs.com/huang1986513/archive/2013/03/09/2951742.html


Android arm处理器优化

http://www.360doc.com/content/13/0618/10/8204997_293662997.shtml


浮点优化选项 -ffast-math:极大地提高浮点运算速度

http://blog.csdn.net/zjujoe/article/details/2604157


Android应用程序优化都有哪些需要注意的?

http://www.mianwww.com/html/2012/05/16497.html


Android如何避免自己的应用程序被破解和反编译?

http://www.mianwww.com/html/2012/05/16500.html


Android开发过程中如何进行算法与界面的优化?

http://www.mianwww.com/html/2012/05/16495.html


Android浮点基础概念浅谈

http://developer.51cto.com/art/201001/180521.htm


ARM 浮点运算详解

http://blog.csdn.net/haomcu/article/details/7677460


CPU浮点运算与整点运算分别决定其什么方面性能?

http://www.zhihu.com/question/20086019


arm芯片中的浮点运算

http://blog.chinaunix.net/uid-27875-id-3453816.html


RTTI技术

http://baike.baidu.com/view/1042388.htm






2006-11-22 16:50:00 housisong 阅读数 15262

        图形图像处理-之-高质量的快速的图像缩放 上篇 近邻取样插值和其速度优化
                   HouSisong@GMail.com  2006.11.22

(2015.08.15  更精确的边界公式推导)

(2009.03.07  可以到这里下载缩放算法的完整的可以编译的项目源代码:  http://blog.csdn.net/housisong/archive/2009/03/07/3967270.aspx  )

( 2007.06.06 更新测试数据,编译器由vc6改为vc2005,CPU由赛扬2G改为AMD64x2 4200+(2.1G) )

(2007.01.02更新)


tag:图像缩放,速度优化,定点数优化,近邻取样插值,二次线性插值,三次线性插值,
   MipMap链,三次卷积插值,MMX,SSE,SSE2,CPU缓存优化

摘要:首先给出一个基本的图像缩放算法,然后一步一步的优化其速度和缩放质量;

高质量的快速的图像缩放 全文 分为:
     上篇 近邻取样插值和其速度优化
     中篇 二次线性插值和三次卷积插值
     下篇 三次线性插值和MipMap链
     补充 使用SSE2优化

 

正文:  

  为了便于讨论,这里只处理32bit的ARGB颜色;
  代码使用C++;涉及到汇编优化的时候假定为x86平台;使用的编译器为vc2005;
  为了代码的可读性,没有加入异常处理代码;
  测试使用的CPU为AMD64x2 4200+(2.37G)  和 Intel Core2 4400(2.00G);


速度测试说明:
  只测试内存数据到内存数据的缩放
  测试图片都是800*600缩放到1024*768; fps表示每秒钟的帧数,值越大表示函数越快

////////////////////////////////////////////////////////////////////////////////
//Windows GDI相关函数参考速度:
//==============================================================================
// BitBlt             544.7 fps  //is copy 800*600 to 800*600
// BitBlt             331.6 fps  //is copy 1024*1024 to 1024*1024
// StretchBlt         232.7 fps  //is zoom 800*600 to 1024*1024
////////////////////////////////////////////////////////////////////////////////

A: 首先定义图像数据结构: 


#define asm __asm

typedef unsigned 
char TUInt8; // [0..255]
struct TARGB32      //32 bit color
{
    TUInt8  B,G,R,A;          
// A is alpha
};

struct TPicRegion  //一块颜色数据区的描述,便于参数传递
{
    TARGB32
*    pdata;         //颜色数据首地址
    long        byte_width;    //一行数据的物理宽度(字节宽度);
                
//abs(byte_width)有可能大于等于width*sizeof(TARGB32);
    long        width;         //像素宽度
    long        height;        //像素高度
};

//那么访问一个点的函数可以写为:
inline TARGB32& Pixels(const TPicRegion& pic,const long x,const long y)
{
    
return ( (TARGB32*)((TUInt8*)pic.pdata+pic.byte_width*y) )[x];
}

 

 

 B: 缩放原理和公式图示:

    缩放后图片             原图片
   (宽DW,高DH)          (宽SW,高SH)

  (Sx-0)/(SW-0)=(Dx-0)/(DW-0)   (Sy-0)/(SH-0)=(Dy-0)/(DH-0)
 =>   Sx=Dx*SW/DW                    Sy=Dy*SH/DH

 

C: 缩放算法的一个参考实现

//给出一个最简单的缩放函数(插值方式为近邻取样,而且我“尽力”把它写得慢一些了:D)
//Src.PColorData指向源数据区,Dst.PColorData指向目的数据区
//函数将大小为Src.Width*Src.Height的图片缩放到Dst.Width*Dst.Height的区域中

 

void PicZoom0(const TPicRegion& Dst,const TPicRegion& Src)
{
    if (  (0==Dst.width)||(0==Dst.height)
        
||(0==Src.width)||(0==Src.height)) return;
    for (long x=0;x<Dst.width;++x)
    {
        
for (long y=0;y<Dst.height;++y)
        {
            
long srcx=(x*Src.width/Dst.width);
            
long srcy=(y*Src.height/Dst.height);
            Pixels(Dst,x,y)
=Pixels(Src,srcx,srcy);
        }
    }
}

////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
// PicZoom0            19.4 fps
////////////////////////////////////////////////////////////////////////////////


D: 优化PicZoom0函数

   a.PicZoom0函数并没有按照颜色数据在内存中的排列顺序读写(内部循环递增y行
索引),将造成CPU缓存预读失败和内存颠簸导致巨大的性能损失,(很多硬件都有这种特性,
包括缓存、内存、显存、硬盘等,优化顺序访问,随机访问时会造成巨大的性能损失)
所以先交换x,y循环的顺序:

void PicZoom1(const TPicRegion& Dst,const TPicRegion& Src)
{
    if (  (0==Dst.width)||(0==Dst.height)
        
||(0==Src.width)||(0==Src.height)) return;
    for (long y=0;y<Dst.height;++y)
    {
        
for (long x=0;x<Dst.width;++x)
        {
            
long srcx=(x*Src.width/Dst.width);
            
long srcy=(y*Src.height/Dst.height);
            Pixels(Dst,x,y)
=Pixels(Src,srcx,srcy);
        }
    }
}

////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
// PicZoom1            30.1 fps
////////////////////////////////////////////////////////////////////////////////

  b.“(x*Src.Width/Dst.Width)”表达式中有一个除法运算,它属于很慢的操作(比一般
的加减运算慢几十倍!),使用定点数的方法来优化它;

void PicZoom2(const TPicRegion& Dst,const TPicRegion& Src)
{
    if (  (0==Dst.width)||(0==Dst.height)
        
||(0==Src.width)||(0==Src.height)) return;
    //函数能够处理的最大图片尺寸65536*65536
    unsigned long xrIntFloat_16=(Src.width<<16)/Dst.width; //16.16格式定点数
    unsigned long yrIntFloat_16=(Src.height<<16)/Dst.height; //16.16格式定点数
    //可证明: (Dst.width-1)*xrIntFloat_16<Src.width成立
    for (unsigned long y=0;y<Dst.height;++y)
    {
        for (unsigned long x=0;x<Dst.width;++x)
        {
            unsigned long srcx=(x*xrIntFloat_16)>>16;
            unsigned long srcy=(y*yrIntFloat_16)>>16;
            Pixels(Dst,x,y)=Pixels(Src,srcx,srcy);
        }
    }
}

////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
// PicZoom2           185.8 fps
////////////////////////////////////////////////////////////////////////////////

    //边界访问保证要求: (inc*(D-1)+inc/2)>>16 <= S-1

    //即:          inc*(2D-1)>>17 <= (S-1)

    //              inc*(2D-1) <= (S-1)*(1<<17) + ((1<<17)-1)

    //              inc <= ((S<<17)-1) / (2D-1)

    //         (S<<16)/D <= ((S<<17)-1) / (2D-1)

    //              (S<<16)*(2D-1) <= ((S<<17)-1)*D

    //                  (S<<16)*2D-(S<<16) <= (S<<16)*2D-D

    //                  (S<<16) >= D

    //S>0, D<(1<<16)  ok 成立



  c.  在x的循环中y一直不变,那么可以提前计算与y相关的值; 1.可以发现srcy的值和x变量无关,可以提前到x轴循环之前;2.展开Pixels函数,优化与y相关的指针计算;

 

void PicZoom3(const TPicRegion& Dst,const TPicRegion& Src)
{
    if (  (0==Dst.width)||(0==Dst.height)
        
||(0==Src.width)||(0==Src.height)) return;
    unsigned long xrIntFloat_16=(Src.width<<16)/Dst.width;
    unsigned long yrIntFloat_16=(Src.height<<16)/Dst.height;
    unsigned long dst_width=Dst.width;
    TARGB32* pDstLine=Dst.pdata;
    unsigned long srcy_16=0;
    for (unsigned long y=0;y<Dst.height;++y)
    {
        TARGB32* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*(srcy_16>>16)));
        unsigned long srcx_16=0;
        for (unsigned long x=0;x<dst_width;++x)
        {
            pDstLine[x]=pSrcLine[srcx_16>>16];
            srcx_16+=xrIntFloat_16;
        }
        srcy_16+=yrIntFloat_16;
        ((TUInt8*&)pDstLine)+=Dst.byte_width;
    }
}

////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
// PicZoom3           414.4 fps
////////////////////////////////////////////////////////////////////////////////

  d.定点数优化使函数能够处理的最大图片尺寸和缩放结果(肉眼不可察觉的误差)受到了一
定的影响,这里给出一个使用浮点运算的版本,可以在有这种需求的场合使用:

void PicZoom3_float(const TPicRegion& Dst,const TPicRegion& Src)
{
    
//注意: 该函数需要FPU支持
    if (  (0==Dst.width)||(0==Dst.height)
        
||(0==Src.width)||(0==Src.height)) return;
    double xrFloat=1.000000001/((double)Dst.width/Src.width);
    double yrFloat=1.000000001/((double)Dst.height/Src.height);

    unsigned 
short RC_Old;
    unsigned 
short RC_Edit;
    asm  
//设置FPU的取整方式  为了直接使用fist浮点指令
    {
        FNSTCW  RC_Old             
// 保存协处理器控制字,用来恢复
        FNSTCW  RC_Edit            // 保存协处理器控制字,用来修改
        FWAIT
        OR      RC_Edit, 
0x0F00    // 改为 RC=11  使FPU向零取整     
        FLDCW   RC_Edit            // 载入协处理器控制字,RC场已经修改
    }

    unsigned 
long dst_width=Dst.width;
    TARGB32
* pDstLine=Dst.pdata;
    
double srcy=0;
    
for (unsigned long y=0;y<Dst.height;++y)
    {
        TARGB32
* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*((long)srcy)));
        
/**//*
        double srcx=0;
        for (unsigned long x=0;x<dst_width;++x)
        {
            pDstLine[x]=pSrcLine[(unsigned long)srcx];//因为默认的浮点取整是一个很慢
                                     //的操作! 所以才使用了直接操作FPU的内联汇编代码。
            srcx+=xrFloat;
        }
*/
        asm fld       xrFloat            
//st0==xrFloat
        asm fldz                         //st0==0   st1==xrFloat
        unsigned long srcx=0;
        
for (long x=0;x<dst_width;++x)
        {
            asm fist dword ptr srcx      
//srcx=(long)st0
            pDstLine[x]=pSrcLine[srcx];
            asm fadd  st,st(
1)           //st0+=st1   st1==xrFloat
        }
        asm fstp      st
        asm fstp      st

        srcy
+=yrFloat;
        ((TUInt8
*&)pDstLine)+=Dst.byte_width;
    }

    asm  
//恢复FPU的取整方式
    {
        FWAIT
        FLDCW   RC_Old 
    }
}

////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
// PicZoom3_float     286.2 fps
////////////////////////////////////////////////////////////////////////////////


  e.注意到这样一个事实:每一行的缩放比例是固定的;那么可以预先建立一个缩放映射表格
  来处理缩放映射算法(PicZoom3_Table和PicZoom3_float的实现等价);

 

void PicZoom3_Table(const TPicRegion& Dst,const TPicRegion& Src)
{
    if (  (0==Dst.width)||(0==Dst.height)
        
||(0==Src.width)||(0==Src.height)) return;
    unsigned long dst_width=Dst.width;
    unsigned 
long* SrcX_Table = new unsigned long[dst_width];
    
for (unsigned long x=0;x<dst_width;++x)//生成表 SrcX_Table
    {
        SrcX_Table[x]
=(x*Src.width/Dst.width);
    }

    TARGB32
* pDstLine=Dst.pdata;
    
for (unsigned long y=0;y<Dst.height;++y)
    {
        unsigned 
long srcy=(y*Src.height/Dst.height);
        TARGB32
* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*srcy));
        
for (unsigned long x=0;x<dst_width;++x)
            pDstLine[x]
=pSrcLine[SrcX_Table[x]];
        ((TUInt8
*&)pDstLine)+=Dst.byte_width;
    }

    delete [] SrcX_Table;
}

////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
// PicZoom3_Table     390.1 fps
////////////////////////////////////////////////////////////////////////////////

  f.为了加快缩放,可以采用根据缩放比例动态生成函数的方式来得到更快的缩放函数;这
  有点像编译器的工作原理;要实现它需要的工作量比较大(或比较晦涩)就不再实现了;
  (动态生成是一种不错的思路,但个人觉得对于缩放,实现它的必要性不大)

   g.现代CPU中,在读取数据和写入数据时,都有自动的缓存机制;很容易知道,算法中生
  成的数据不会很快再次使用,所以不需要写入缓存的帮助;在SSE指令集中增加了movntq
  等指令来完成这个功能;
  (尝试过利用CPU显式prefetcht0、prefetchnta预读指令或直接的mov读取指令等速度反
   而略有下降:(   但预读在copy算法中速度优化效果很明显 )

void PicZoom3_SSE(const TPicRegion& Dst,const TPicRegion& Src)
{
    
//警告: 函数需要CPU支持MMX和movntq指令
    if (  (0==Dst.width)||(0==Dst.height)
        
||(0==Src.width)||(0==Src.height)) return;
    unsigned long xrIntFloat_16=(Src.width<<16)/Dst.width;
    unsigned long yrIntFloat_16=(Src.height<<16)/Dst.height;

    unsigned 
long dst_width=Dst.width;
    TARGB32
* pDstLine=Dst.pdata;
    unsigned 
long srcy_16=0;
    
for (unsigned long y=0;y<Dst.height;++y)
    {
        TARGB32
* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*(srcy_16>>16)));

        asm
        {
            push      ebp
            mov       esi,pSrcLine
            mov       edi,pDstLine
            mov       edx,xrIntFloat_16
            mov       ecx,dst_width
            xor       ebp,ebp           
//srcx_16=0

            and    ecx, (not 
3)    //循环4次展开
            TEST   ECX,ECX   //nop
            jle    EndWriteLoop

            lea       edi,[edi
+ecx*4]
            neg       ecx

              
//todo: 预读

                WriteLoop:
                        mov       eax,ebp
                        shr       eax,
16            //srcx_16>>16
                        lea       ebx,[ebp+edx]
                        movd      mm0,[esi
+eax*4]
                        shr       ebx,
16            //srcx_16>>16
                        PUNPCKlDQ mm0,[esi+ebx*4]
                        lea       ebp,[ebp
+edx*2]
                       
                        
// movntq qword ptr [edi+ecx*4], mm0  //不使用缓存的写入指令
                        asm _emit 0x0F asm _emit 0xE7 asm _emit 0x04 asm _emit 0x8F  

                        mov       eax,ebp
                        shr       eax,
16            //srcx_16>>16
                        lea       ebx,[ebp+edx]
                        movd      mm1,[esi
+eax*4]
                        shr       ebx,
16            //srcx_16>>16
                        PUNPCKlDQ mm1,[esi+ebx*4]
                        lea       ebp,[ebp
+edx*2]
                        
                        
// movntq qword ptr [edi+ecx*4+8], mm1 //不使用缓存的写入指令
                        asm _emit 0x0F asm _emit 0xE7 asm _emit 0x4C asm _emit 0x8F asm _emit 0x08

                        add ecx, 
4
                        jnz WriteLoop

                        
//sfence //刷新写入
                        asm _emit 0x0F asm _emit 0xAE asm _emit 0xF8 
                        emms
                EndWriteLoop:

            mov    ebx,ebp
            pop    ebp

            
//处理边界  循环次数为0,1,2,3;(这个循环可以展开,做一个跳转表,略)
            mov    ecx,dst_width
            and    ecx,
3
            TEST   ECX,ECX
            jle    EndLineZoom

            lea       edi,[edi
+ecx*4]
            neg       ecx
      StartBorder:
            mov       eax,ebx
            shr       eax,
16            //srcx_16>>16
            mov       eax,[esi+eax*4]
            mov       [edi
+ecx*4],eax
            add       ebx,edx

            inc       ECX
            JNZ       StartBorder
      EndLineZoom:
        }

        
//
        srcy_16+=yrIntFloat_16;
        ((TUInt8
*&)pDstLine)+=Dst.byte_width;
    }
}
//=====================================================================
//鉴于有读者反映汇编代码阅读困难,这里给出一个使用intel提供的函数调用方式的实现,
//读者可以相互对照来阅读代码
//要编译PicZoom3_SSE_mmh,需要#include <mmintrin.h> #include <xmmintrin.h>
//并且需要编译器支持
//函数PicZoom3_SSE_mmh速度为 593.7 fps
void PicZoom3_SSE_mmh(const TPicRegion& Dst,const TPicRegion& Src)
{
    //警告: 函数需要CPU支持MMX和movntq指令
    if (  (0==Dst.width)||(0==Dst.height)
        ||(0==Src.width)||(0==Src.height)) return;
    unsigned long xrIntFloat_16=(Src.width<<16)/Dst.width;
    unsigned long yrIntFloat_16=(Src.height<<16)/Dst.height;
    unsigned long dst_width=Dst.width;
    TARGB32* pDstLine=Dst.pdata;
    unsigned long srcy_16=0;
    unsigned long for4count=dst_width/4*4;
    for (unsigned long y=0;y<Dst.height;++y)
    {
        TARGB32* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*(srcy_16>>16)));
        unsigned long srcx_16=0;
        unsigned long x;
        for (x=0;x<for4count;x+=4)//循环4次展开
        {
            __m64 m0=_m_from_int(*(int*)(&pSrcLine[srcx_16>>16]));
            srcx_16+=xrIntFloat_16;
            m0=_m_punpckldq(m0, _m_from_int(*(int*)(&pSrcLine[srcx_16>>16])) );
            srcx_16+=xrIntFloat_16;
            __m64 m1=_m_from_int(*(int*)(&pSrcLine[srcx_16>>16]));
            srcx_16+=xrIntFloat_16;
            m1=_m_punpckldq(m1, _m_from_int(*(int*)(&pSrcLine[srcx_16>>16])) );
            srcx_16+=xrIntFloat_16;
            _mm_stream_pi((__m64 *)&pDstLine[x],m0); //不使用缓存的写入指令
            _mm_stream_pi((__m64 *)&pDstLine[x+2],m1); //不使用缓存的写入指令
        }
        for (x=for4count;x<dst_width;++x)//处理边界
        {
            pDstLine[x]=pSrcLine[srcx_16>>16];
            srcx_16+=xrIntFloat_16;
        }
        srcy_16+=yrIntFloat_16;
        ((TUInt8*&)pDstLine)+=Dst.byte_width;
    }
    _m_empty();
}

////////////////////////////////////////////////////////////////////////////////
//速度测试:
//==============================================================================
// PicZoom3_SSE     711.7 fps 
////////////////////////////////////////////////////////////////////////////////


E: 缩放效果图:

                

     原图       放大图(x轴放大8倍,y轴放大12倍)

 

                                  
     原图        缩小图(缩小到0.66倍)      放大图(放大到1.6倍)

 

 

F: 把测试成绩放在一起:

////////////////////////////////////////////////////////////////////////////////
//CPU: AMD64x2 4200+(2.1G)  zoom 800*600 to 1024*768
//==============================================================================
// BitBlt             544.7 fps  //is copy 800*600 to 800*600
// BitBlt             331.6 fps  //is copy 1024*1024 to 1024*1024
// StretchBlt         232.7 fps  //is zoom 800*600 to 1024*1024
//
// PicZoom0            19.4 fps
// PicZoom1            30.1 fps
// PicZoom2           185.8 fps
// PicZoom3           414.4 fps
// PicZoom3_float     286.2 fps
// PicZoom3_Table     390.1 fps
// PicZoom3_SSE       711.7 fps 
////////////////////////////////////////////////////////////////////////////////

补充Intel Core2 4400上的测试成绩:
////////////////////////////////////////////////////////////////////////////////
//CPU: Intel Core2 4400(2.00G)  zoom 800*600 to 1024*768
//==============================================================================
// PicZoom0            15.0 fps
// PicZoom1            63.9 fps
// PicZoom2           231.2 fps
// PicZoom3           460.5 fps
// PicZoom3_float     422.5 fps
// PicZoom3_Table     457.6 fps
// PicZoom3_SSE      1099.7 fps 
////////////////////////////////////////////////////////////////////////////////

2013-09-29 22:44:00 utimes 阅读数 2334

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

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

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

#define XSIZE 640
#define YSIZE 480
#define IMGSIZE XSIZE * YSIZE

typedef 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.0gcc编译,生成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除法,因为它是2N次方,所以可以用移位操作替代,往右移位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,我们能不能将DEF都预先计算好呢?然后用查表算法计算呢?我们使用3个数组分别存放DEF256种可能的取值,然后......

查表数组初始化

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 system32bit CPU,都至少有2ALU,能不能让2ALU都跑起来?

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;

    }
} 
2ALU处理的数据不能有数据依赖,也就是说:某个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的潜能是很大的!看你如何去挖掘。

RGBYUV的转换算法做以总结。

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 * YSIZE

typedef 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; //Y
        Y_G[i] = (i * 2404) >> 12; 
        Y_B[i] = (i * 467)  >> 12;

        U_R[i] = (i * 602)  >> 12; //U
        U_G[i] = (i * 1183) >> 12; 
        U_B[i] = (i * 1785) >> 12;

        V_R[i] = (i * 2519) >> 12; //V
        V_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]; //Y
        out[i + IMGSIZE]     = U_B[in[i].b] - U_R[in[i].r] - U_G[in[i].g]; //U
        out[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]; //Y
        out[i  + 1 + IMGSIZE]     = U_B[in[i + 1].b] - U_R[in[i + 1].r] - U_G[in[i + 1].g]; //U
        out[i  + 1 + 2 * IMGSIZE] = V_R[in[i + 1].r] - V_G[in[i + 1].g] - V_B[in[i + 1].b]; //V
    }
}

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


关于Image Engineering & Computer Vision的更多讨论与交流,敬请关注本博和新浪微博songzi_tea.

没有更多推荐了,返回首页