图像的二值化简单来说就是把整张图片弄成只有两种颜色——通常是黑白两色。恩,图像处理的初阶呢。——ZwqXin.com上两篇为:
Bmp文件的结构与基本操作(逐像素印屏版)
认识HBITMAP与Bmp操作(整内存拷贝版)
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/image-processing/image-intensity-binarization.html
通常是有很多方法,针对应用吧,这里只讨论简单的一种“战术”。通常我处理的是彩色图片而不是灰度图片,要将整张图片的丰富颜色统统划分为黑白,关键要确定分界线——难道是(127,127,127)?这很粗糙,应该考虑各个颜色的亮度。亮度不是RGB系统描述的,但是可以经过一段粗糙的转化。一种RGB颜色的亮度与各分量之间的关系:
IntenSity = 0.2990 * R + 0.5870 * G + 0.1140 * B
用上篇的方法载入BMP后,对其处理如下(24BIT,32BIT)
- void ImageBinaryzation::BmpBinary24_32(OperateBMP& image, int Hdis, int Wdis, int step)
- {
- UCHAR blue, green, red;
- UCHAR result;
- int Hmax = (Hdis-1)*step;
- for(int j = 0; j < Hmax; j++)
- for(int i = 0; i < Wdis; i++)
- {
- blue = image.GetImageData().buffer[i*step + 0 + Wdis * j];
- green = image.GetImageData().buffer[i*step + 1 + Wdis * j];
- red = image.GetImageData().buffer[i*step + 2 + Wdis * j];
- result = 0.2990*red+0.5870*green+0.1140*blue;
- if(result>128)
- result = 255;
- else if(result<=128)
- result = 0;
- image.GetImageData().buffer[i*step + 0 + Wdis * j]
- = image.GetImageData().buffer[i*step + 1 + Wdis * j]
- = image.GetImageData().buffer[i*step + 2 + Wdis * j]
- = result;
- }
- }
如之前所说,逐个读图片的像素,逐个处理的手段在图像处理里是经常有的。这里step就是BMPBIT/8,对于24BIT图片是3(因为数据区每三个数据为一组,接下来移动3单位读下组),32BIT是4(注意这里没用上ALPHA分量)。通常边界问题不是那么重要.....以上操作一看就明白,最后得出的亮度两分后赋给原像素。当然如果不两分而直接给的话,就直接是张类似灰度图的真·256色图了。
8BIT的版本涉及读调色板:
- void ImageBinaryzation::BmpBinary8(OperateBMP& image, int Hdis, int Wdis)
- {
- UCHAR data = 0;
- UCHAR WhiteIndex = 0;
- UCHAR BlackIndex = 0;
- UCHAR blue, green, red;
- UCHAR result;
- for(int k = 0; k <256; k++)
- {
- red = image.GetImageData().palette[k].peRed;
- green = image.GetImageData().palette[k].peGreen;
- blue = image.GetImageData().palette[k].peBlue;
- if(red == 0 && green == 0 && blue == 0)
- BlackIndex = k;
- else if(red == 255 && green == 255 && blue == 255)
- WhiteIndex = k;
- }
- for(int j = 0; j < Hdis; j++)
- for(int i = 0; i < Wdis; i++)
- {
- data = image.GetImageData().buffer[i + Wdis * j];
- red = image.GetImageData().palette[data].peRed& 0xFF;
- green = image.GetImageData().palette[data].peGreen& 0xFF;
- blue = image.GetImageData().palette[data].peBlue& 0xFF;
- result = 0.2990*red+0.5870*green+0.1140*blue;
- if(result>128)
- result = WhiteIndex;
- else if(result<=128)
- result = BlackIndex;
- image.GetImageData().buffer[i + Wdis * j] = result;
- }
- HBITMAP temp = CreateDIBitmap(image.GetDrawDeviceHandler(),
- &image.GetImageData().bitmapinfoheader, CBM_INIT, image.GetImageData().buffer,
- (BITMAPINFO*)&image.GetImageData().bitmapinfoheader, DIB_RGB_COLORS);
- image.SetBitmapHandler(temp);
- }
首先是遍历调色板,找出黑色和白色对应的索引。然后遍历数据区,根据数据区里每个BIT的值调出调色板对应的值的RGB描述。接下来就跟之前一样了。但最后要刷新DDB,不然系统调色板不帮你弄的~方法是重新调用CreateDIBitmap建立DDB,并用HBITMAP描述,把该HBITMAP覆盖原来那个,关联到显示函数。
16位版本是比较麻烦的,先不说565格式的16BITBMP我还没载入显示成功,555格式的16BIT图片的像素读取也挺麻烦的。关键是设定好掩码后移位。要弄清楚的是,16BIT图片每组为2BYTE,因此读取时的步长是2。读出来的这16个BIT,前一位不管,通过掩码把它和后10位屏蔽掉,然后就是对应B分量有意义的那5位了,移位后就可以读取它了,其他分量类似(注意是*BGR顺序)。
- void ImageBinaryzation::BmpBinary16(OperateBMP& image, int Hdis, int Wdis)
- {
- UCHAR data1,data2;
- WORD data = 0;
- UCHAR blue, green, red;
- UCHAR result;
- int Hmax = (Hdis-1)*2;
- for(int j = 0; j < Hmax; j++)
- for(int i = 0; i < Wdis; i++)
- {
- data1 = image.GetImageData().buffer[i*2 + 0 + Wdis * j];
- data2 = image.GetImageData().buffer[i*2 + 1 + Wdis * j];
- data = data1 | data2<<8; //16BIT图片一次处理两字节,注意数据是低位在前高位在后
- if(image.GetImageData().bitmapinfoheader.biCompression == BI_RGB) //*555格式
- {
- blue = ( (data&0x7C00)>>10 )<<3;
- green = ( (data&0x03E0)>>5 )<<3;
- red = data&0x001F<<3;
- }
- else if(image.GetImageData().bitmapinfoheader.biCompression == BI_BITFIELDS)
- {
- blue = ( (data&0xF800)>>11 )<<3;
- green = ( (data&0x07E0)>>5 )<<3;
- red = data&0x001F<<3;
- }
- result = 0.2990*red+0.5870*green+0.1140*blue;
- if(result>128)
- result = 255;
- else if(result<=128)
- result = 0;
- if(image.GetImageData().bitmapinfoheader.biCompression == BI_RGB) //*555格式
- data = ((result>>3)<<10) | ((result>>3)<<5) | (result>>3);
- else if(image.GetImageData().bitmapinfoheader.biCompression == BI_BITFIELDS)
- data = ((result>>3)<<11) | ((result>>3)<<5) | (result>>3);
- image.GetImageData().buffer[i*2 + 0 + Wdis * j] = LOBYTE(data);
- image.GetImageData().buffer[i*2 + 1 + Wdis * j] = HIBYTE(data);
- }
- }
实现出来的只有BI_RGB) //*555格式,看看是怎样先把两个BYTE的数据合成一个WORD的(因为要在一个WORD上划分才行~),然后掩码+移位。注意移位到底端后要再向上移3位。因为对一个描述分量的UCHAR来说数据要满足占好一个BYTE(8IT)。正因为16BIT对全彩色是有损的,数据的有损(低位缺损)也是必然的咯。求出亮度,应用到颜色分量的时候,记得要复位。
原图片:
处理后:
其实我是真不想天空也有部分变黑的。但它在恰好亮度分界线(128)以下,可见实际应用中分界线的选取其实很重要的。
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/image-processing/image-intensity-binarization.html