用VB写高效的图像处理程序

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

用VB写高效的图像处理程序
一、为什么这么慢?
二、DIB的结构
三、DIB访问函数
四、实战练习
五、使用DIBSection和模拟指针
六、结合DirectX
一、为什么这么慢?
自盘古开天地以来(好像夸张了点),一直有人抱怨VB程序速度慢。

特别是图像处理,被认为是VB的禁区。

说起来也是,市面上的关于VB的图像处理的数据都是先讲计算公式,再直接用PSet(或API 函数SetPixel)逐点画(至少我见过的书都是这样)。

效果是办到了,但速度慢得离谱:对一幅640*480的图像进行半透明合并就需要10秒钟;而在PhotoShop中,只要一设置图层的透明度,半透明效果立即呈现。

难怪有人说VB的闲话。

但这并不表示VB不能写高速的图像处理程序,速度慢是因为没有使用正确的方法。

从VB5开始,能以本机代码编译成exe文件,所以不存在代码执行速度的问题。

那么,是什么拖慢了速度呢?就是PSet和SetPixel!PSet 把浮点形式的坐标转为像素单位,再交给SetPixel处理。

而SetPixel呢,坐标系转化、剪裁区域判断、将颜色匹配为设备支持的最接近的,最后还要根据不同的颜色格式寻址、为将颜色写入其所在位进行位运算。

经过这么多层处理,速度不慢才怪。

那么,怎样才能提高处理速度呢?使用DIB,直接对位图所在内存进行操作,速度可以大大提高。

现在看看本文提供的范例程序的执行速度,这只是一个简单的色彩演示程序。

从这个表中可看出:
1.VC比3_DIB、4_DIB_Ptr快一些,这是因为SafeArray结构的数组比真正的指针慢,但也不是某些人所说的70~100倍;
2.4_DIB_Ptr比3_DIB慢一点,这是因为模拟指针本来就是靠SafeArray结构的数组,需要在运行时动态修改数组数据地址,所以速度慢一点;
3.真正差了70~100倍是1_PSet和2_SetPixelV。

4.在VB IDE中解释执行程序非常慢,3_DIB就存在30倍的速度差距。

所以经常一些无聊的人拿 VC Debug编译的程序与 VB IDE中解释执行程序比较速度。

5.4_DIB_Ptr、5_DX7Ptr速度一样,这是因为都是利用模拟指针技术直接访问内存来做图像处理的。

而6_DX7Arr使用的是GetLockedArray返回的二维数组,二维数组比一维数组慢。

以上可证,速度慢的原因是SetPixelV非常低效,而并不是VB的问题。

虽然VC的的确比较快,但是我写这篇文章不是为了讨论速度极限(否则这篇文章会改名为《如何用汇编写高速的图像处理程序》),而是
为了告诉大家如何在VB中写能够实时处理的图像处理程序。

同时,决定代码速度的不是编程语言或编译器,而是算法。

如果算法写得差的话,无论你用什么编程语言或编译器,那程序速度依然很慢(你在VC中用SetPixelV写图像处理程序试试)。

而现在的硬件配置已经足够好,用VB完全可以写高速的图像处理程序。

二、DIB的结构
在 Windows 3.0 以前,Windows系统用的是DDB(设备有关位图)。

DDB没有调色板,显示的颜色依赖硬件,处理色彩很不方便。

所以 Microsoft 在 Windows 3.0中重新定义了BMP文件格式(BMP 3.0),使其支持设备无关位图——也就是DIB。

时至今日,BMP的版本号已升至5.0(Windows NT 4.0、Windows95 定义了BMP 4.0,Windows 98、Windows 2000 定义了 BMP 5.0),但基本结构没有变——仍是 BMP文件头和 DIB 组成:
(#代表可以不填(=0)的项目)
三、DIB访问函数
四、实战练习
用DIB写图像处理程序的时候,首先要明确一点:DIB并不是图像处理算法,而是一种绘图方法。

图像处理算法是DIB高级,管理坐标和颜色的运算;而DIB只是为了绘制。

所以此时处理算法的效率是速度的关键。

利用DIB绘制图像并没有比用PSet/SetPixel绘制
差多少,它只是把坐标运算改成地址运算而已。

很多人知道指针是一个危险的东西,就是因为它能直接访问内存,如果指针不小心指错地方的话,Windows立即报告一般保护性错误。

所以,在地址运算的时候一定要小心,同时要注意随时保存,因为此时的非法操作的发生率非常高,否则辛辛苦苦写的代码一瞬间没了可别怪我没提醒啊。

好了,现在开始!由于处理算法起指导作用,所以现在先讲解1_PSet。

所有的代码都在FrmMain.frm中。

其他的过程的代码可以不看,现在将注意力集中在“DrawIt”中,它就是管绘制的。

其实我这个演示程序蛮简单的:R分量延着水平方向增加,G分量延着垂直方向增加,B分量则从右往左滚动。

什么?!“And &HFF”是什么意思?!这可是基础啊……(下略&HFFFF...字)。

“&H”表示十六进制数,而And表示按位与。

&HFF是二进制的“1111 1111”,正好覆盖了低8位,这时用And进行按位与,只会得到低8位,与RGB分量需要的8位正好符合(对于“(J + K) And &HFF”来说,可以实现滚动效果)。

If Not看明白了Then Goto前两段好!现在打开3_DIB。

(由于24位能直接指定RGB分量,所以这里是用的是24位DIB)看了前面的“DIB的结构”,是不是有点昏呢?其实DIB也没什么,就是一个表述位图信息的BITMAPINFO结构和一个存储位图数据的数据缓冲区,顶多再用SetDIBitsT oDevice绘制,所以3_DIB与1_PSet相比只是多了SetDIBitsToDevice、BITMAPINFOHEADER(24位DIB没有调色板,所以用BITMAPINFOHEADER就行)和一些常数的声明而已。

由于这个演示程序不需要改变图像大小和色深,所以可以把有关变量作为窗体级变量,再在Form_Load中初始化。

由于DIB并没有向系统申请资源(数组的内存是VB分配的,会自动释放),所以不需要写释放代码。

现在来看DrawIt。

1.虽然可以逐点把坐标映射成地址再写,但是这样效率太低了,可以利用坐标处理的连续性进行优化。

2.由于我这里用的是数组,所以这里用(数组元素)索引代替地址。

3.最开始要注意DIB是逆序存储的,要将索引设为最后一行第一个像素的索引。

4.由于DIB的RGB顺序是B、G、R,所以“m_MapData(CurIdx + ?)”的顺序是2、1、0。

5.设置好一个像素的颜色后,要注意把索引改为下个像素的索引。

6.由于DIB是逆序存储的,移到下一个扫描行是“iLinePtr = iLinePtr - m_LineBytes”
“好了,代码看懂了,按F5运行看看效果。


“咦?速度好像没快多少啊?”
这是由于程序在VB环境下是以解释方式运行的,而解释方式对做图像处理所需要大规模循环和大量的算术运算的执行效率很低,所以要编译成(本机代码)exe再运行。

此时还要注意编译优化,可以把“高级优化”的所有勾打上,速度可提升20%左右。

五、使用DIBSection和模拟指针
虽然有GetDIBits/SetDIBits函数,但是DIB与GDI位图之间的数据交换还是很不方便,特别处理过程中需要调用GDI函数来处理的时候。

而且就算你不怕麻烦,但这样做的处理效率很低。

所以Windows为我们提供了DIBSection。

DIBSection是一种特殊的DIB,它除了可以像DIB一样直接对位图数据所占内存进行操作,它还可以选入DC、能用GDI函数绘制,非常灵活。

但在VB下使用DIBSection 还是有困难的,因为用CreateDIBSection创建DIBSection时,得到的是位图数据的地址,而VB没有指针。

所幸在VB下可以利用SafeArray结构的数组模拟指针。

关于模拟指针的原理、方法,网上的资料多的是,比如AdamBear 的文章“VB真是想不到系列之四:VB 指针葵花宝典之SafeArray ”。

但这些文章都只是讲一般性的应用,不能像真正的指针一样随意改变地址(他们都是使用CopyMemory 改的)。

而在图像处理中,由于点运算的频繁,“像真正的指针一样随意改变地址”的功能很重要。

其实“像真正的指针一样随意改变地址”并没有技术难度(对于已经学会模拟指针的人来说),就看想得到不:直接将一个动态数组(设pByte)指向一个SAFEARRAY结构体变量(设pBytePtr)。

模拟指针模块的代码在mPoint.bas 。

现在来看4_DIB_Ptr:
1.为了演示DIBSection能够像HBITMAP一样,我在Form_Load 中创建了DC、将DIBSection选入DC。

同时为了释放,Form_UnLoad中写了释放代码。

2.在DrawIt中,注意处理部分的代码并没有与VB_DIB差多少,只不过把索引计算改为地址运算而已。

再来比较vc的代码,感觉跟真正的指针用法差不多嘛:
六、结合DirectX
在GDI中,我们可以用DIB直接操作图像数据,那么号称能直接访问显存的DirectX呢?一查DirectX SDK,发现IDirectDrawSurface接口的Lock函数可以锁定表面,从而直接访问该位图数据。

再在VB的对象浏览器中仔细观察“DirectX 7 for Visual Basic Type Library”,发现DirectDrawSurface7对象也有该方法。

看看5_DX7Ptr:
1.由于硬件设备性能不同,所以不要指望像DIB那样可以认定RGB字节顺序。

应该根据Lock方法传回的DDSURFACEDESC2结构来确认RGB字节顺序(iIdxR、iIdxG、iIdxB)、像素所占字节(cbPixel)及宽距(cbPitch)。

2.由于我们有了模拟指针技术,所以这段代码与跟先前4_DIB_Ptr 差不多,特别是与在VC中使用IDirectDrawSurface::Lock的处理代码差不多。

仔细观察对象浏览器的人会发现,DirectDrawSurface7对象GetLockedArray可以取得锁定后的位图数据:
是就是因为其使用的是二维数组,所以它比一维数组的模拟指针慢一些。

如果你不想使用非常规技术(模拟指针),可以使用GetLockedArray。

注意到这点没有,GetLockedArray是一个方法而不是一个属性,我们需要将数组变量传递过去,然后就可以利用该数组直接操作位图数据了。

啊哈!明白了GetLockedArray也是利用模拟指针技术实现的,只不过它填充的是2维的SAFEARRAY结构而已。

注意:只操作位于系统内存的表面,千万别操作对显存中的表面。

这是因为CPU访问显存比返问内存要慢许多!这个建议并不是绝对的,如果对该表面的Blt的调用次数远超过自己写的图像处理操作的话,可以将该表面放在显存,或者是在内存中计算好后再一次性提交到显存。

本程序建立图像处理操作的表面的代码:。

相关文档
最新文档