关于16位色转成灰度的问题

ltng 2003-07-13 12:03:44
0-65535的颜色索引值和R,G,B三色数值是如何对应的?
我怎么把0-65535的索引值转成256级灰度(灰度,R:x,G:x,B:x,0<=x<=255)显示呢?
...全文
117 3 打赏 收藏 转发到动态 举报
写回复
用AI写文章
3 条回复
切换为时间正序
请发表友善的回复…
发表回复
Skt32 2003-07-13
  • 打赏
  • 举报
回复
首先看一段实现24位色图像灰度化转换的代码

procedure Grayscale(const Bitmap:TBitmap);
var
X: Integer;
Y: Integer;
R,G,B,Gray: Byte;
Color: TColor;
begin
for Y := 0 to (Bitmap.Height - 1) do
begin
for X := 0 to (Bitmap.Width - 1) do
begin
Color := Bitmap.Canvas.Pixels[X,Y];
R := Color and $FF;
G := (Color and $FF00) shr 8;
B := (Color and $FF0000) shr 16;
Gray := Trunc(0.3 * R + 0.59 * G + 0.11 * B);
Bitmap.Canvas.Pixels[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是与(.)运算,0.1=0,0.0=0,1.1=1,以取绿色为例:$FF00实际上就是$00FF00,它与一个TColor类型数按位进行与运算后,表示红色和绿色的位都变为了$00,而表示绿色的部分不变(0,1和1进行与运算值都不变),再右移8位,自然就获得了绿色值的8位表示!

  再获得三原色的值后,就是计算灰度值,0.3 * Red + 0.59 * Green + 0.11 * 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代码可知调用Bitmap.Canvas.Pixels获取,写入像素的颜色信息实际上是利用了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[0..PixelCountMax-1] OF TRGBTriple;
var
Row: pRGBTripleArray;
X: Integer;
Y: Integer;
Gray: Byte;
begin
for Y := 0 to (Bitmap.Height - 1) do
begin
Row := Bitmap.ScanLine[Y];
for X := 0 to (Bitmap.Width - 1) do
begin
Gray := Trunc(0.3 * Row^[X].rgbtRed + 0.59 * Row^[X].rgbtGreen + 0.11 * 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 (Bitmap.Height - 1) do
begin
PRGB := Bitmap.ScanLine[Y];
for X := 0 to (Bitmap.Width - 1) do
begin
Gray := Trunc(0.3 * PRGB^.rgbtRed + 0.59 * PRGB^.rgbtGreen + 0.11 * 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 (Bitmap.Height - 1) do
begin
p := Bitmap.ScanLine[Y];
for X := 0 to (Bitmap.width - 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 Bitmap.Height-1 DO
 begin
 Row15 := Bitmap15.Scanline[j];
 Row24 := Bitmap24.Scanline[j];
 for i := 0 TO Bitmap.Width-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 (Bitmap.Height - 1) do
begin
PRGB := Bitmap.ScanLine[Y];
for X := 0 to (Bitmap.Width - 1) do
begin
Gray := Trunc(0.3 * PRGB^.rgbtRed + 0.59 * PRGB^.rgbtGreen + 0.11 * PRGB^.rgbtBlue);
PRGB^.rgbtRed:=Gray;
PRGB^.rgbtGreen:=Gray;
PRGB^.rgbtBlue:=Gray;
Inc(PRGB);
end;
end;
end;

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

Gray := Trunc(0.3 * Red + 0.59 * Green + 0.11 * Blue);//这句用的是浮点运算

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

Gray := (30 * Red + 59 * Green + 11 * Blue) div 100;
虽然这样一改,运算次数多了一次,但在我的雷鸟1.1G上,处理速度大概能提高5%左右!而同主频下(或略低,如Athlon 1600+相当于P4 1.6G)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);

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

jvcit 2003-07-13
  • 打赏
  • 举报
回复
STUDY
无敌魔仙 2003-07-13
  • 打赏
  • 举报
回复
Y(灰度值)=0.299*R+0.587*G+0.114*B
索引 就是一个字节的 0-255
1.该程序用QT开发,实现图片导入、显示、缩放、拖动及处理(冷暖色、灰度、亮度、饱和、模糊、锐化)。 经实测,我写的这个软件在导入10000*7096像素的超大图片的时候,缩放的速度比2345看图软件还快,2345缩放超大图会卡顿,但本软件不会^_^ 关于程序中缩放拖动部分的说面参见我的博客https://blog.csdn.net/weixin_43935474/article/details/89327314; 2.载入图片后,鼠标移动的时候可以显示鼠所在点的图像的坐标以及灰度; 3.缩放的时候,图片右上角可以显示当前图片的缩放比例; 4.用户可导入16位深的tiff灰度图文件(一般来说是由相机拍摄的灰度图数据),导入16位深的tiff的时候,用户需要先点击界面左上角的checkbox,然后再导入tiff图片,否则图片解析不出来。 注:Qt自带的QImage只能导入8位深的tiff灰度图,如果用qt的QImage导入16位深的灰度图,图像数据会被强制转换成argb格式的图像,数据就被更改了,所以我自己编写一个解析tiff文件的功能,我翻阅了很多博客,其中如下链接给我的帮助最大: https://blog.csdn.net/chenlu5201314/article/details/56276903 上述博客作为详细解析tiff文件结构的说明文档,写的非常详细,我也是根据上面的内容,自己编写了一个解析tiff文件的类(当然功能很少,只能解析符合特定条件的tiff文件) //************************************************************ //by Bruce Xu //注:解析tiff的类只解析特定的tiff文件! //1.解析的tiff文件中只存在一幅图,如果文件中存在多幅图,本类不支持解析! //2.图像数据为8位或16位深度的灰度图,如果是其他类型的图片,本类不支持解析! //3.图片没有被压缩过! //************************************************************

19,468

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 图形处理/算法
社区管理员
  • 图形处理/算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧