怎样把图像灰度化

怎样把图像灰度化,第1张

由Delphi中的图像灰度代码看基本图像处理

基础篇]

首先看一段实现24位色图像灰度化转换的代码

procedure Grayscale(const Bitmap:TBitmap);

var

X: Integer;

Y: Integer;

R,G,B,Gray: Byte;

Color: TColor;

begin

for Y := 0 to (BitmapHeight - 1) do

begin

for X := 0 to (BitmapWidth - 1) do

begin

Color := BitmapCanvasPixels[X,Y];

R := Color and $FF;

G := (Color and $FF00) shr 8;

B := (Color and $FF0000) shr 16;

Gray := Trunc(03 R + 059 G + 011 B);

BitmapCanvasPixels[X,Y] := Gray shl 16 or Gray shl 8 or Gray;

end

end

end;

{这段代码效率是非常低的,但可以方便我们理解同时一些问题}

Delphi的帮助中对TColor已经有了详细的描述,这可以方便我们理解上面的代码!

首先看:

R := Color and $FF;

G := (Color and $FF00) shr 8;

B := (Color and $FF0000) shr 16;

这是段常见的从TColor中提取三原色的代码,但它是什么意思呢?

首先应该知道and是与()运算,01=0,00=0,11=1,以取绿色为例:$FF00实际上就是$00FF00,它与一个TColor类型数按位进行与运算后,表示红色和绿色的位都变为了$00,而表示绿色的部分不变(0,1和1进行与运算值都不变),再右移8位,自然就获得了绿色值的8位表示!

再获得三原色的值后,就是计算灰度值,03 Red + 059 Green + 011 Blue 这是求加权平均值的公式。(因为人眼对颜色的敏感度不同,所以权值不同,就像在pf16bit中用了6位表示绿色,其它两种颜色只用了5位,这问题以后另写文章说明)

然后就是像素颜色信息的写回,刚才是右移,现在自然就是左移,而或(+)运算就是(0+1=1,0+0=0,1+1=1),举个简单例子就是:($FF shl 16 = $FF0000) or ($FF shl 8 = $FF00) or $FF = $FFFFFF ,其实这里的或运算当然也可以用 + 代替。

虽然上面的代码实现了24位色图像的灰度化,但当图像比较大时,速度非常慢,为什么?查看相关VCL代码可知调用BitmapCanvasPixels获取,写入像素的颜色信息实际上是利用了API GetPixel、SetPixel,这种方法是非常低效的!(唯一的好处是在进行一些和颜色无关的 *** 作,如图像的旋转,翻转时不需要因为PixelFormat的不同而修改代码)所以应该换一种更高效的访问像素点数据的方法,如用API GetDIBits、SetDIBits,但这种方法比较复杂,好在Delphi3以后版本的TBitmap中提供了Scanline。利用Scanline可以快速对像素进行访问!

还是以24位色(PixelFormats=pf24bit)为例,可改写为:

procedure Grayscale(const Bitmap:TBitmap);

const

PixelCountMax = 32768;

type

pRGBTripleArray = ^TRGBTripleArray;

TRGBTripleArray = ARRAY[0PixelCountMax-1] OF TRGBTriple;

var

Row: pRGBTripleArray;

X: Integer;

Y: Integer;

Gray: Byte;

begin

for Y := 0 to (BitmapHeight - 1) do

begin

Row := BitmapScanLine[Y];

for X := 0 to (BitmapWidth - 1) do

begin

Gray := Trunc(03 Row^[X]rgbtRed + 059 Row^[X]rgbtGreen + 011 Row^[X]rgbtBlue);

Row^[X]rgbtRed:=Gray;

Row^[X]rgbtGreen:=Gray;

Row^[X]rgbtBlue:=Gray;

end;

end;

end;

上面的例子用了一个TRGBTriple数组

PRGBTriple = ^TRGBTriple;

tagRGBTRIPLE = packed record

rgbtBlue: Byte;

rgbtGreen: Byte;

rgbtRed: Byte;

end;

TRGBTriple = tagRGBTRIPLE;

这种方法会限制位图的大小,但一般不用理会,直接用TBitmap可处理不了那么大的位图

当然也可用指针的移动实现,实测结果这样更快~~~

procedure Grayscale(const Bitmap:TBitmap);

var

X: Integer;

Y: Integer;

PRGB: pRGBTriple;

Gray: Byte;

begin

for Y := 0 to (BitmapHeight - 1) do

begin

PRGB := BitmapScanLine[Y];

for X := 0 to (BitmapWidth - 1) do

begin

Gray := Trunc(03 PRGB^rgbtRed + 059 PRGB^rgbtGreen + 011 PRGB^rgbtBlue);

PRGB^rgbtRed:=Gray;

PRGB^rgbtGreen:=Gray;

PRGB^rgbtBlue:=Gray;

Inc(PRGB);

end;

end;

end;

[颜色篇]

在上面提到了,那灰度化代码只能适用于24位色(PixelFormats=pf24bit),为什么?看看记录类型tagRGBTRIPLE,正好24位,所以这样只能处理24位色图!

那怎么处理其他的位图呢?

先对这各种类型的位图做些简单的介绍~~~

pf1bit:

每个像素只需要用一位表示,如调色板定义的是黑白两种颜色(0为黑,1为白),这时只能用位 *** 作访问像素信息!如定义

var P:PByte

for Y := 0 to (BitmapHeight - 1) do

begin

p := BitmapScanLine[Y];

for X := 0 to (Bitmapwidth - 1) DIV 8 + 1 do

begin

p^:=1 or 2 or 4 or 8 or 16 or 32 or 64 or 128;

Inc(PRGB,3);

end;

end;

p^:=1 or 2 or 4 or 8 or 16 or 32 or 64 or 128;

这行代码什么意思呢?1=1(二进制),2=10(二进制),4=100(二进制),8=1000(二进制)

结合上篇中解释了的或运算,很容易理解就以八个字位为单位,给其赋上颜色信息!

pf4bit:

和pf1bit位图一样, *** 作pf4bit位图也需要用位 *** 作。

pf8bit:

可直接利用Byte、TByteArray,但用Scanline取的值表示的只是调色板上颜色的索引。

pf15bit和pf16bit:

这两种位图都是16位的,pf15bit是第一位为0,后15位的每5位分别表示红、绿、蓝。而pf16bit中绿色占6位,其它两种颜色占用5位(人眼对绿色比较敏感)!

pf24bit位图转pf15bit位图代码

var

Row24:pRGBTriple;

Row15:PWord;

for j := 0 TO BitmapHeight-1 DO

begin

Row15 := Bitmap15Scanline[j];

Row24 := Bitmap24Scanline[j];

for i := 0 TO BitmapWidth-1 DO

begin

with Row24^ do

Row15^ := (rgbtRed Shr 3) Shl 10 or (rgbtGreen Shr 3) Shl 5 or (rgbtBlue Shr 3);

Inc(Row24);

Inc(Row15);

end

end;

pf24bit和pf32bit:

pf24bit上面的已多次用到,就不多说了。而pf32bit和pf24bit一样,用24位(前24位)来记录三原色的颜色信息!

PRGBQuad = ^TRGBQuad;

tagRGBQUAD = packed record

rgbBlue: Byte;

rgbGreen: Byte;

rgbRed: Byte;

rgbReserved: Byte;

end;

TRGBQuad = tagRGBQUAD;

如果要修改上面的程序,就是简单的PRGBQuad替换PRGBTriple,TRGBQuad替换TRGBTriple的过程~

测试表明在pf32bit中利用Scanline处理图像要比pf24bit快。

所以除了单色图(PixelFormats=pf1bit)外(没必要),其它都可转外32位色实现灰度化。这也是一种比较可行的方法!

[优化篇]

还以上篇中给出的灰度化代码为例

procedure Grayscale(const Bitmap:TBitmap);

var

X: Integer;

Y: Integer;

PRGB: pRGBTriple;

Gray: Byte;

begin

for Y := 0 to (BitmapHeight - 1) do

begin

PRGB := BitmapScanLine[Y];

for X := 0 to (BitmapWidth - 1) do

begin

Gray := Trunc(03 PRGB^rgbtRed + 059 PRGB^rgbtGreen + 011 PRGB^rgbtBlue);

PRGB^rgbtRed:=Gray;

PRGB^rgbtGreen:=Gray;

PRGB^rgbtBlue:=Gray;

Inc(PRGB);

end;

end;

end;

实际应用中,这种方法已经很快了,但实际上还存在可以优化的余地,什么呢?

Gray := Trunc(03 Red + 059 Green + 011 Blue);//这句用的是浮点运算

在图像处理中,速度就是生命,能不用浮点运算,就最好不要用!

Gray := (30 Red + 59 Green + 11 Blue) div 100;

虽然这样一改,运算次数多了一次,但在我的雷鸟11G上,处理速度大概能提高5%左右!而同主频下(或略低,如Athlon 1600+相当于P4 16G)AMD的CPU浮点运算能力比Intel的较强,整数运算能力较弱,所以用Intel的CPU在这里更能体现出优势!

注:x div 100 和 Trunc(x/100)的效果是相同的,但查看其汇编代码可知一个用的指令是div,而另一个是fdiv(即进行浮点运算),还要调用函数Trunc,其处理速度差距非常大,所以能用 x div 100 的时候就不要用 Trunc(x/100)。

但这还不是最快的,再看一个:

Gray := HiByte(77 Red + 151 Green + 28 Blue);

Gray := (77 Red + 151 Green + 28 Blue) shr 8;

(建议用后一种,不要调用函数)

这种方法比最原始的方法快了近3/4!

什么意思呢?用77,151,28分别除以256试试~~~

移位是什么意思呢,和10进制的进位,退位联系一下,是不是可以近似的理解为乘除2的n次方呢?当然这和真正意义的乘除法是不一样的!比如shr(右移),和真正的除法相比,比如shr 1,只有最后一个字位为0时(既为2的倍数),它才等于除2!如二进制数110(6)右移1位变为11(3),和6/2=3结果相同。

当然这和一开始的灰度化效果有了些误差!

如果允许存在更大的误差,还可以考虑另一种方法:

Gray := (Red shr 2) + (Red shr 4) + (Green shr 1) + (Green shr 4) + (Blue shr 3);

连乘法都没用,完全用移位实现,结合上面的解释,用除法来理解该表达式,其值只是约等于(03125 Red + 05625 Green + 0125 Blue),和一开始的加权平均值有了比较大的误差!但如果对速度有苛刻的要求的话,可以怎么用!这比上一种方法还能再快5%!

空间域处理主要分为灰度变换和空间滤波两类:

其中,r代表处理前的像素值,s代表处理后的像素值,[0,L-1]表示灰度级范围。

图像反转的用途:

其中,r代表处理前的像素值,s代表处理后的像素值,c代表一个常数,如下所示为对数变换以及一些基本灰度变换的函数曲线:

对数变换的用途:

由于幂律变换中的指数常称为gamma,所以幂律变换也被称为伽马变换。

其中,r代表处理前的像素值,s代表处理后的像素值,c和γ为正常数,如下所示为幂律变换的函数曲线:

幂律变换的用途:

对比度拉伸是扩展图像灰度级动态范围的处理,如下图所示是一个用于对比度拉伸的典型变换。

灰度级分层用于突出图像中特定灰度范围的亮度,其主要实现方式有两种:

像素其实质是由比特组成的数字,比如在8位灰度图像中,每个像素的灰度是由8比特(1字节)组成。也就是说,一幅8比特图像可以看成是由8个1比特平面组成,如下图所示:

如下图所示为一幅8比特的灰度图像,以及它的8个比特平面:

1、数字图像处理中,二值化是最简单的。 所谓的二值化一般就是指 将真彩色或者灰度图转化为黑白两色,一般说来是处理灰度图。

2、例如处理灰度图,灰度图像素是从0~255的,假设120是分解,可以将灰度值小于120的像素的灰度值置为0,将灰度值大于120的像素的灰度值置为1,这样整个图像就成了黑白两色了。 二值化的难点不是编程,而是找阈值,这里120就是阈。

这代码写的很低效,不过还算清晰。

灰度处理那个很简单吧,基本上C#都自动帮你做了,你那代码就是手动遍历其中每个像素然后读取其灰度值(这里代码偷懒直接让C#帮忙计算了)然后重新把像素设置为RGB都是灰度值的颜色而已。

二值化的其实也不复杂,也是逐个遍历像素,判断某个像素的亮度是否大于给定阈值,如果大于则设置为白色否则设置为黑色。唯一有点麻烦的是要把8个像素合并到一个字节里面,于是代码里面搞了个scan数组,然后八个像素为一组填进去,每个像素占字节的一个位(白1黑0)。使用位运算0x80

>>

(x

%

8)保证像素从左到右依次保存在字节的8个位上。你这个二值化代码回填像素信息的时候用的是Marshal的字节拷贝而已,你只要知道了位图的结构这个理解也很简单。

以上就是关于怎样把图像灰度化全部的内容,包括:怎样把图像灰度化、【图像处理】灰度变换、求一个c语言对图像进行二值化的源码....要vs能直接打开就能用的。。。等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/9700811.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-01
下一篇 2023-05-01

发表评论

登录后才能评论

评论列表(0条)

保存