24位真彩色转换为8位灰度图片(完整代码)
转自:http://blog.csdn.net/jiangxinyu/article/details/6222349
图像的灰度化与二值化是图像处理中最常见的处理方法,也是很多图像处理方法的基础,如图像灰度统计、图像识别等。
图像的灰度化与二值化方法较多,处理过程也比较简单。但切不可因其简单而忽视效率。如常用的图像灰度计算公式:gray = red * 0.299 + green * 0.587 + blue * 0.114,如果在程序代码中直接套用了这个公式,因浮点数的缘故导致代码执行效率较低,如改为定点整数运算,可使执行效率大大提高。
下面是图像的灰度与二值化代码:
typedef union
{
ARGB Color;
struct
{
BYTE Blue;
BYTE Green;
BYTE Red;
BYTE Alpha;
};
}ARGBQuad, *PARGBQuad;
//---------------------------------------------------------------------------
// 图像数据data灰度化
VOID Gray(BitmapData *data)
{
PARGBQuad p = (PARGBQuad)data->Scan0;
INT offset = data->Stride - data->Width * sizeof(ARGBQuad);
for (UINT y = 0; y < data->Height; y ++, (BYTE*)p += offset)
{
for (UINT x = 0; x < data->Width; x ++, p ++)
p->Blue = p->Green = p->Red =
(UINT)(p->Blue * 29 + p->Green * 150 + p->Red * 77 + 128) >> 8;
}
}
//---------------------------------------------------------------------------
// 图像数据data灰度同时二值化,threshold阀值
VOID GrayAnd2Values(BitmapData *data, BYTE threshold)
{
PARGBQuad p = (PARGBQuad)data->Scan0;
INT offset = data->Stride - data->Width * sizeof(ARGBQuad);
for (UINT y = 0; y < data->Height; y ++, (BYTE*)p += offset)
{
for (UINT x = 0; x < data->Width; x ++, p ++)
{
if (((p->Blue * 29 + p->Green * 150 + p->Red * 77 + 128) >> 8) < threshold)
p->Color &= 0xff000000;
else
p->Color |= 0x00ffffff;
}
}
}
//---------------------------------------------------------------------------
因本文使用的是32位图像数据,所以图像的二值化没有采用通常的赋值操作p->Blue = p->Green = p->Red = 0(或者255),而是采用了位运算。
下面是使用BCB2007和GDI+图像数据实现图像灰度和二值化的例子代码:
FORCEINLINE
VOID LockBitmap(Gdiplus::Bitmap *bmp, BitmapData *data)
{
Gdiplus::Rect r(0, 0, bmp->GetWidth(), bmp->GetHeight());
bmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite,
PixelFormat32bppARGB, data);
}
//---------------------------------------------------------------------------
// GDI+位图扫描线解锁
FORCEINLINE
VOID UnlockBitmap(Gdiplus::Bitmap *bmp, BitmapData *data)
{
bmp->UnlockBits(data);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Gdiplus::Bitmap *bmp = new Gdiplus::Bitmap(L"d:\\source1.jpg");
Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle);
g->DrawImage(bmp, 0, 0);
BitmapData data;
LockBitmap(bmp, &data);
// Gray(&data);
GrayAnd2Values(&data, 128);
UnlockBitmap(bmp, &data);
g->DrawImage(bmp, data.Width, 0);
delete g;
delete bmp;
}
//---------------------------------------------------------------------------
24位真彩色转换为8位灰度图片(完整代码)
//Code By xets007
//转载请注明出处
//
////////////////////////////////////////////////////////////////////////
#include <windows.h>
BOOL BMP24to8(char *szSourceFile,char *szTargetFile);
int main(int argc,char* argv[])
{
//调用这个函数直接把24位真彩色灰度化
BOOL stat=BMP24to8("c://source.bmp","c://target.bmp");
return 0;
}
BOOL BMP24to8(char *szSourceFile,char *szTargetFile)
{
HANDLE hSourceFile=INVALID_HANDLE_VALUE,hTargetFile=INVALID_HANDLE_VALUE;
DWORD dwSourceSize=0,dwTargetSize=0;
PBYTE pSource=NULL,pTarget=NULL;
hSourceFile=CreateFile(szSourceFile,GENERIC_READ,FILE_SHARE_READ,NULL,
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hSourceFile==INVALID_HANDLE_VALUE)
return FALSE;
dwSourceSize=GetFileSize(hSourceFile,NULL);
pSource=(PBYTE)VirtualAlloc(NULL,dwSourceSize,MEM_COMMIT,PAGE_READWRITE);
//分配空间失败或者文件太小(BMP文件不可能小于54个字节)
if(pSource==NULL||dwSourceSize<=54)
{
CloseHandle(hSourceFile);
return FALSE;
}
DWORD dwTemp=0;
ReadFile(hSourceFile,pSource,dwSourceSize,&dwTemp,NULL);
BITMAPFILEHEADER *pSourceFileHeader=(BITMAPFILEHEADER*)pSource;
BITMAPINFOHEADER *pSourceInfoHeader=(BITMAPINFOHEADER*)(pSource+sizeof(BITMAPFILEHEADER));
//不是BMP文件或者不是24位真彩色
if(pSourceFileHeader->bfType!=0x4d42||pSourceInfoHeader->biBitCount!=24)
{
CloseHandle(hSourceFile);
VirtualFree(pSource,NULL,MEM_RELEASE);
return FALSE;
}
CloseHandle(hSourceFile);
LONG nWidth=pSourceInfoHeader->biWidth;
LONG nHeight=pSourceInfoHeader->biHeight;
LONG nSourceWidth=nWidth*3;if(nSourceWidth%4) nSourceWidth=(nSourceWidth/4+1)*4;
LONG nTargetWidth=nWidth;if(nTargetWidth%4) nTargetWidth=(nTargetWidth/4+1)*4;
dwTargetSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*256+nHeight*nTargetWidth;
pTarget=(PBYTE)VirtualAlloc(NULL,dwTargetSize,MEM_COMMIT,PAGE_READWRITE);
memset(pTarget,0,dwTargetSize);
if(pTarget==NULL)
{
VirtualFree(pTarget,NULL,MEM_RELEASE);
return FALSE;
}
BITMAPFILEHEADER *pTargetFileHeader=(BITMAPFILEHEADER *)pTarget;
BITMAPINFOHEADER *pTargetInfoHeader =
(BITMAPINFOHEADER *)(pTarget+sizeof(BITMAPFILEHEADER));
pTargetFileHeader->bfType=pSourceFileHeader->bfType;
pTargetFileHeader->bfSize=dwTargetSize;
pTargetFileHeader->bfReserved1=0;
pTargetFileHeader->bfReserved2=0;
pTargetFileHeader->bfOffBits=sizeof(BITMAPFILEHEADER)
+sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*256;
pTargetInfoHeader->biBitCount=8;
pTargetInfoHeader->biClrImportant=0;
pTargetInfoHeader->biClrUsed=256;
pTargetInfoHeader->biCompression=BI_RGB;
pTargetInfoHeader->biHeight=pSourceInfoHeader->biHeight;
pTargetInfoHeader->biPlanes=1;
pTargetInfoHeader->biSize=sizeof(BITMAPINFOHEADER);
pTargetInfoHeader->biSizeImage=nHeight*nTargetWidth;
pTargetInfoHeader->biWidth=pSourceInfoHeader->biWidth;
pTargetInfoHeader->biXPelsPerMeter=pSourceInfoHeader->biXPelsPerMeter;
pTargetInfoHeader->biYPelsPerMeter=pSourceInfoHeader->biYPelsPerMeter;
RGBQUAD *pRgb;
for(int i=0;i<256;i++)//初始化8位灰度图的调色板信息
{
pRgb = (RGBQUAD*)(pTarget+sizeof(BITMAPFILEHEADER)
+ sizeof(BITMAPINFOHEADER)+i*sizeof(RGBQUAD));
pRgb->rgbBlue=i;pRgb->rgbGreen=i;pRgb->rgbRed=i;pRgb->rgbReserved=0;
}
for (int m=0;m<nHeight;m++)//转化真彩色图为灰度图
{
for(int n=0;n<nWidth;n++)
{
pTarget[pTargetFileHeader->bfOffBits+m*nTargetWidth+n] =
pSource[pSourceFileHeader->bfOffBits+m*nSourceWidth+n*3]*0.114
+pSource[pSourceFileHeader->bfOffBits+m*nSourceWidth+n*3+1]*0.587
+pSource[pSourceFileHeader->bfOffBits+m*nSourceWidth+n*3+2]*0.299;
}
}
hTargetFile = CreateFile(szTargetFile,GENERIC_WRITE,FILE_SHARE_WRITE,
NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
BOOL stat=WriteFile(hTargetFile,pTarget,dwTargetSize,&dwTemp,NULL);
CloseHandle(hTargetFile);
VirtualFree(pSource,NULL,MEM_RELEASE);
VirtualFree(pTarget,NULL,MEM_RELEASE);
return stat;
}
转化效果如下图
在GDI+中将24位真彩色图转换为灰度图(原理、C#调用指针)
在图像处理中,我们经常需要将真彩色图像转换为黑白图像。严格的讲应该是灰度图,因为真正的黑白图像是二色,即只有纯黑,纯白二色。开始之前,我们先简单补充一下计算机中图像的表示原理。计算机中的图像大致可以分成两类:位图(Bitmap)和矢量图(Metafile)。 位图可以视为一个二维的网格,整个图像就是由很多个点组成的,点的个数等于位图的宽乘以高。每个点被称为一个像素点,每个像素点有确定的颜色,当很多个像素合在一起时就形成了一幅完整的图像。我们通常使用的图像大部分都是位图,如数码相机拍摄的照片,都是位图。因为位图可以完美的表示图像的细节,能较好的 还原图像的原景。但位图也有缺点:第一是体积比较大,所以人们开发了很多压缩图像格式来储存位图图像,目前应用最广的是JPEG格式,在WEB上得到了广泛应用,另外还有GIF,PNG等 等。第二是位图在放大时,不可避免的会出现“锯齿”现象,这也由位图的本质特点决定的。所以在现实中,我们还需要使用到另一种图像格式:矢量图。同位图不 同,矢量图同位图的原理不同,矢量图是利用数学公式通过圆,线段等绘制出来的,所以不管如何放大都不会出现变形,但矢量图不能描述非常复杂的图像。所以矢量图都是用来描述图形图案,各种CAD软件等等都是使用矢量格式来保存文件的。
在讲解颜色转换之前,我们要先对位图的颜色表示方式做一了解。位图中通常是用RGB三色方式来表示颜色的(位数很少时要使用调色板)。所以每个像素采用不同的位数,就可以表示出不同数量的颜色。如下图所示:
每像素的位数 |
一个像素可分配到的颜色数量 |
1 |
2^1 = 2 |
2 |
2^2 = 4 |
4 |
2^4 = 16 |
8 |
2^8 = 256 |
16 |
2^16 = 65,536 |
24 |
2^24 = 16,777,216 |
从中我们可以看出,当使用24位色(3个字节)时,我们可以得到1600多万种颜色,这已经非常丰富了,应该已接近人眼所能分辨的颜色了。现在计算机中使用最多的就是24位色,别外在GDI+中还有一种32位色,多出来的一个通道用来描述Alpha,即透明分量。
24位色中3个字节分别用来描述R,G,B三种颜色分量,我们看到这其中是没有亮度分量的,这是因为在RGB表示方式中,亮度也是直接可以从颜色分量中得到的,每一颜色分量值的范围都是从0到255, 某一颜色分量的值越大,就表示这一分量的亮度值越高,所以255表示最亮,0表示最暗。那么一个真彩色像素点转换为灰度图时它的亮度值应该是多少呢,首先我们想到的平均值,即将R+G+B/3。但现实中我们使用的却是如下的公式:
Y = 0.299R+0.587G+0.114B
这个公式通常都被称为心理学灰度公式。这里面我们看到绿色分量所占比重最大。因为科学家发现使用上述公式进行转换时所得到的灰度图最接近人眼对灰度图的感觉。
因为灰度图中颜色数量一共只有256种(1个字节),所以转换后的图像我们通常保存为8位格式而不是24位格式,这样比较节省空间。而8位图像是使用调色板方式来保存颜色的。而不是直接保存颜色值。调色板中可以保存256颜色,所以可以正好可以将256种灰度颜色保存到调色版中。
代码如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
namespace ConsoleApplication2
{
class Program
{
unsafe static void Main(string[] args)
{
Bitmap img = (Bitmap)Image.FromFile(@"E:/My Documents/My Pictures/cherry_blossom_1002.jpg");
Bitmap bit = new Bitmap(img.Width,
img.Height,PixelFormat.Format8bppIndexed);
BitmapData data
= img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadOnly,PixelFormat.Format24bppRgb);
byte* bp = (byte*)data.Scan0.ToPointer();
BitmapData data2 =
bit.LockBits(new Rectangle(0, 0, bit.Width, bit.Height), ImageLockMode.ReadWrite,PixelFormat.Format8bppIndexed);
byte* bp2 = (byte*)data2.Scan0.ToPointer();
for (int i = 0; i != data.Height; i++)
{
for (int j = 0; j != data.Width; j++)
{
//0.3R+0.59G+0.11B
float value = 0.11F * bp[i * data.Stride + j * 3]
+ 0.59F * bp[i * data.Stride + j * 3 + 1]
+ 0.3F * bp[i * data.Stride + j * 3 + 2];
bp2[i * data2.Stride + j] = (byte)value;
}
}
img.UnlockBits(data);
bit.UnlockBits(data2);
ColorPalette palette = bit.Palette;
for (int i = 0; i != palette.Entries.Length; i++)
{
palette.Entries[i] = Color.FromArgb(i, i, i);
}
bit.Palette = palette;
bit.Save(@"E:/TEMP/bb.jpeg", ImageFormat.Jpeg); img.Dispose();
bit.Dispose();
}
}
}
代码中我使用了指针直接操作位图数据,同样的操作要比使用GetPixel, SetPixel快非常多。我们要感谢微软在C#中保留了特定情况下的指针操作。
图像效果如下:
C++代码处理24位图转8位图
代码处理对于宽度和高度都为基数的图形处理会产生形变!
核心部分代码如下:
////代码中m_sourcefile指24位真彩色图片的位置,m_targetfile是转换后的256色BMP灰度图保存的位置
void CMy24Dlg::OnBtnConvert()
{
UpdateData();
if(m_sourcefile==""||m_targetfile=="")
return;
FILE *sourcefile,*targetfile;
//位图文件头和信息头
BITMAPFILEHEADER sourcefileheader,targetfileheader;
BITMAPINFOHEADER sourceinfoheader,targetinfoheader;
memset(&targetfileheader,0,sizeof(BITMAPFILEHEADER));
memset(&targetinfoheader,0,sizeof(BITMAPINFOHEADER));
sourcefile=fopen(m_sourcefile,"rb");
fread((void*)&sourcefileheader,1,sizeof(BITMAPFILEHEADER),sourcefile);//提取原图文件头
if(sourcefileheader.bfType!=0x4d42)
{
fclose(sourcefile);
MessageBox("原图象不为BMP图象!");
return;
}
fread((void*)&sourceinfoheader,1,sizeof(BITMAPINFOHEADER),sourcefile);//提取文件信息头
if(sourceinfoheader.biBitCount!=24)
{
fclose(sourcefile);
MessageBox("原图象不为24位真彩色!");
return;
}
if(sourceinfoheader.biCompression!=BI_RGB)
{
fclose(sourcefile);
MessageBox("原图象为压缩后的图象,本程序不处理压缩过的图象!");
return;
}
//构造灰度图的文件头
targetfileheader.bfOffBits=54+sizeof(RGBQUAD)*256;
targetfileheader.bfSize=targetfileheader.bfOffBits+sourceinfoheader.biSizeImage/3;
targetfileheader.bfReserved1=0;
targetfileheader.bfReserved2=0;
targetfileheader.bfType=0x4d42;
//构造灰度图的信息头
targetinfoheader.biBitCount=8;
targetinfoheader.biSize=40;
targetinfoheader.biHeight=sourceinfoheader.biHeight;
targetinfoheader.biWidth=sourceinfoheader.biWidth;
targetinfoheader.biPlanes=1;
targetinfoheader.biCompression=BI_RGB;
targetinfoheader.biSizeImage=sourceinfoheader.biSizeImage/3;
targetinfoheader.biXPelsPerMeter=sourceinfoheader.biXPelsPerMeter;
targetinfoheader.biYPelsPerMeter=sourceinfoheader.biYPelsPerMeter;
targetinfoheader.biClrImportant=0;
targetinfoheader.biClrUsed=256;
构造灰度图的调色版
RGBQUAD rgbquad[256];
int i,j,m,n,k;
for(i=0;i<256;i++)
{
rgbquad[i].rgbBlue=i;
rgbquad[i].rgbGreen=i;
rgbquad[i].rgbRed=i;
rgbquad[i].rgbReserved=0;
}
targetfile=fopen(m_targetfile,"wb");
//写入灰度图的文件头,信息头和调色板信息
fwrite((void*)&targetfileheader,1,sizeof(BITMAPFILEHEADER),targetfile);
fwrite((void*)&targetinfoheader,1,sizeof(BITMAPINFOHEADER),targetfile);
fwrite((void*)&rgbquad,1,sizeof(RGBQUAD)*256,targetfile);
BYTE* sourcebuf;
BYTE* targetbuf;
//这里是因为BMP规定保存时长度和宽度必须是4的整数倍,如果不是则要补足
m=(targetinfoheader.biWidth/4)*4;
if(m<targetinfoheader.biWidth)
m=m+4;
n=(targetinfoheader.biHeight/4)*4;
if(n<targetinfoheader.biHeight)
n=n+4;
sourcebuf=(BYTE*)malloc(m*n*3);
//读取原图的颜色矩阵信息
fread(sourcebuf,1,m*n*3,sourcefile);
fclose(sourcefile);
targetbuf=(BYTE*)malloc(m*n);
BYTE color[3];
通过原图颜色矩阵信息得到灰度图的矩阵信息
for(i=0;i<n;i++)
{
for(j=0;j<m;j++)
{
for(k=0; k<3; k++)
color[k]=sourcebuf[(i*m+j)*3+k];
targetbuf[(i*m)+j]=color[0]*0.114+color[1]*0.587+color[2]*0.299;
if(targetbuf[(i*m)+j]>255)
targetbuf[(i*m)+j]=255;
}
}
fwrite((void*)targetbuf,1,m*n+1,targetfile);
fclose(targetfile);
MessageBox("位图文件转换成功!");
}
24位真彩色和转换后的灰度图
上边的两组图就是用那段代码处理的结果
另外一种C++代码
BOOL Trans24To8(HDIB m_hDIB)
{
//判断8位DIB是否为空
if(m_hDIB1!=NULL)
{
::GlobalUnlock((HGLOBAL)m_hDIB1);
::GlobalFree((HGLOBAL)m_hDIB1);
m_hDIB1=NULL;
}
BITMAPINFOHEADER* pBmpH; //指向信息头的指针
m_hDIB1=(HDIB)::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT,
sizeof(BITMAPINFOHEADER)+768*576+256*sizeof(RGBQUAD));
lpDIB1=(LPSTR)::GlobalLock(m_hDIB1);
pBmpH=(BITMAPINFOHEADER*)(lpDIB1);
pImageBuffer=(unsigned char*)lpDIB1+40+256*4;
//指向8位DIB图象数据的指针
//写bmp信息头及bmp调色版
WriteBMPHeader(lpDIB1);
LPSTR lpDIB= (LPSTR) ::GlobalLock((HGLOBAL)m_hDIB);
//得到24位DIB的指针
int WidthDIB = (int) : IBWidth(lpDIB);
int HeightDIB= (int) : IBHeight(lpDIB);
LPSTR lp=::FindDIBBits(lpDIB);
unsigned char* l;
for (int j=0;j<HeightDIB;j++)
{
for (int i=0;i<WidthDIB;i++)
{
l=(unsigned char*)lp+(j*768+i)*3;
*pImageBuffer=(int)(((*l)*30+(*(l+1))*59+(*(l+2))*11)/100);
pImageBuffer++;
}
}
pImageBuffer-=768*576;
Invalidate(TRUE);//隐含调用OnDraw()函数绘图,即显示转化后的图象
return TRUE;
}
void CBoKeDetectView::WriteBMPHeader(LPSTR &lpDIB)
{
//写bmp信息头
BITMAPINFOHEADER* pBmpInfoHeader;
pBmpInfoHeader=(BITMAPINFOHEADER*)lpDIB;
pBmpInfoHeader->biSize=sizeof(BITMAPINFOHEADER);
pBmpInfoHeader->biWidth=768;
pBmpInfoHeader->biHeight=576;
pBmpInfoHeader->biBitCount=8;
pBmpInfoHeader->biPlanes=1;
pBmpInfoHeader->biCompression=BI_RGB;
pBmpInfoHeader->biSizeImage=0;
pBmpInfoHeader->biXPelsPerMeter=0;
pBmpInfoHeader->biYPelsPerMeter=0;
pBmpInfoHeader->biClrUsed=0;
pBmpInfoHeader->biClrImportant=0;
//写bmp调色版
RGBQUAD* pRGB=(RGBQUAD*)(lpDIB+40);
for(int i=0;i<256;i++)
{
pRGB.rgbBlue=pRGB.rgbGreen=pRGB.rgbRed=i;
pRGB.rgbReserved=0;
}
}
C#两种获取灰度图像的方法(24位转8位)
在图像处理程序开发中,常会遇到将一幅彩色图像转换成灰度图像的情况,笔者在最近的一个项目中便遇到了这点。经过一翻努力最终解决,想想有必要分享一下,于是便写下此文。在本文中,将向各位读者介绍两种实现这一变换的方法,这也是笔者先后使用的两种方法。本文的例子使用C#语言编写,使用的集成开发环境是Visual Studio 2005。
第一种,直接调用GetPixel/SetPixel方法。
我们都知道,图像在计算机中的存在形式是位图,也即一个矩形点阵,每一个点被称为一个像素。在这种方法中,我们通过GDI+中提供的GetPixel方法来读取像素的颜色,并加以计算,然后再使用SetPixel方法将计算后的颜色值应用到相应的像素上去,这样便可以得到灰度图像。
上边提到的“计算”便是指得到灰度图像的计算,其公式是:
r = (像素点的红色分量 + 像素点的绿色分量 + 像素点的蓝色分量) / 3
最后得到的r便是需要应用到原像素点的值。具体的编程实现也非常的简单,只需要遍历位图的每一个像素点,然后使用SetPixel方法将上边计算得到的值应用回去即可。主要代码如下所示:
Color currentColor;
int r;
Bitmap currentBitmap = new Bitmap(picBox.Image);
Graphics g = Graphics.FromImage(currentBitmap);
for (int w = 0; w < currentBitmap.Width; w++)
{
for (int h = 0; h < currentBitmap.Height; h++)
{
currentColor = currentBitmap.GetPixel(w, h);
r = (currentColor.R + currentColor.G + currentColor.B) / 3;
currentBitmap.SetPixel(w, h, Color.FromArgb(r, r, r));
}
}
g.DrawImage(currentBitmap, 0, 0);
picBox.Image = currentBitmap;
g.Dispose();
以上代码非常简单,不需要做太多的解释。需要注意的是,在使用SetPixel方法的时候,其三色分量值均为我们公式计算得到的结果r。
另外一种写法
private void ToolStripMenuItemGrayR_Click(object sender, EventArgs e)
{
Bitmap b = new Bitmap(pictureBox1 .Image );//把图片框1中的图片给一个bitmap类型
Bitmap b1 = new Bitmap(pictureBox1.Image);
Color c = new Color();
//Graphics g1 = pictureBox1.CreateGraphics();//容器设为图片框1
for (int i = 0; i < pictureBox1.Image.Width ; i++)
for (int j = 0; j < pictureBox1.Image.Height; j++)
{
c = b1.GetPixel(i, j);
//用FromArgb方法由颜色分量值创建Color结构
Color c1 = Color.FromArgb(c.B, c.B, c.B);
b1.SetPixel(i, j, c1);
//pictureBox2.Image = b1;
//pictureBox2.Refresh();
}
pictureBox2.Image = b1;
pictureBox2.Refresh();
}
第二种,使用ColorMatrix 类
如果读者亲自测试过第一种方式,就会发现通过循环遍历位图所有像素,并使用SetPixel方法来修改每个像素的各颜色分量是非常耗时的。而现在介绍的第二种方法则是一种更好的实现方式――使用ColorMatrix类。
在介绍具体的实现之前,有必要先向读者介绍一下相关的背景知识。在GDI+中,颜色使用32位来保存,红色、绿色、蓝色和透明度分别占8位,因此每个分量可以有28=256(0~255)种取值。这样一来,一个颜色信息就可以用一个向量 (Red,Green,Blue,Alpha) 来表示,例如不透明的红色可以表示成为(255,0,0,255)。向量中的Alpha值用来表示颜色的透明度,0 表示完全透明,255 表示完全不透明。到这里读者朋友可能应该想到了,我们只需要按照一定的规则改变这些向量里各个分量的值,便可以得到各种各样的颜色变换效果,所以我们获得灰度图这个要求也就能够实现了。
现在关键问题便是按照什么规则来改变各分量值。在上边介绍的第一种方式中我们提到了计算灰度图像的公式,其实它还有另外一个表示方式,如下:
r = 像素点的红色分量×0.299 + 像素点的绿色分量×0.587 + 像素点的蓝色分量×0.114
这一公式便是我们的规则。我们只需要对每一个颜色向量做上边的变化即可。这里的变换就需要用到ColorMatrix类,此类定义在System.Drawing.Imaging名字空间中,它定义了一个5×5的数组,用来记录将和颜色向量进行乘法计算的值。ColorMatrix对象配合ImageAttributes类一起使用,实际上GDI+里的颜色变换就是通过ImageAttributes对象的SetColorMatrix 进行的。
第二种的主要实现代码如下:
Bitmap currentBitmap = new Bitmap(picBox.Image);
Graphics g = Graphics.FromImage(currentBitmap);
ImageAttributes ia = new ImageAttributes();
float[][] colorMatrix = {
new float[] {0.299f, 0.299f, 0.299f, 0, 0},
new float[] {0.587f, 0.587f, 0.587f, 0, 0},
new float[] {0.114f, 0.114f, 0.114f, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}};
ColorMatrix cm = new ColorMatrix(colorMatrix);
ia.SetColorMatrix(cm, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
g.DrawImage(currentBitmap, new Rectangle(0, 0, currentBitmap.Width, currentBitmap.Height), 0, 0, currentBitmap.Width, currentBitmap.Height, GraphicsUnit.Pixel, ia);
picBox.Image = currentBitmap;
g.Dispose();
细心的读者可能会问,明明颜色是由4个分量组成的,那么与之相乘的矩阵也应该是一个4×4的矩阵啊,为什么这里定义的颜色变换矩阵却是5×5的呢?这个问题可以参考MSDN上的一篇文章(《使用颜色矩阵对单色进行变换》)便可得解。
请读者朋友们输入以上代码并编译执行。大家会发现其变换速度极快,几乎是瞬间完成。其实,只要知道了矩阵各行、列必要的参数值,那么使用ColorMatrix来实现颜色变换是非常方便的。诸如让某色透明这样的功能用这种方式实现起来也是非常方便高效的。
上边简要地介绍了两种获得灰度图像的方法。在实际开发中,使用第二种方式远远优于第一种,其在运算速度方面的优势,是第一种远远没法比的。
ColorMatrix 类(.NET Framework 4 )
定义包含 RGBAW 空间坐标的 5 x 5 矩阵。 ImageAttributes 类的若干方法通过使用颜色矩阵调整图像颜色。 无法继承此类。
命名空间: System.Drawing.Imaging
程序集: System.Drawing(在 System.Drawing.dll 中)
public sealed class ColorMatrix
矩阵系数组成一个 5 x 5 的线性转换,用于转换 ARGB 单色值。 例如,ARGB 向量表示为 Alpha、Red(红色)、Green(绿色)、Blue(蓝色)和 W,此处 W 始终为 1。
例如,假设您希望从颜色 (0.2, 0.0, 0.4, 1.0) 开始并应用下面的变换:
- 将红色分量乘以 2。
- 将 0.2 添加到红色、绿色和蓝色分量中。
下面的矩阵乘法将按照列出的顺序进行这对变换。
颜色矩阵的元素按照先行后列(从 0 开始)的顺序进行索引。 例如,矩阵 M 的第五行第三列由 M[4][2] 表示。
5×5 单位矩阵(在下面的插图中显示)在对角线上为 1,在其他任何地方为 0。 如果用单位矩阵乘以颜色矢量,则颜色矢量不会发生改变。 形成颜色变换矩阵的一种简便方法是从单位矩阵开始,然后进行较小的改动以产生所需的变换。
有关矩阵和变换的更详细的讨论,请参见坐标系统和变形。
下面的示例采用一个使用一种颜色 (0.2, 0.0, 0.4, 1.0) 的图像,并应用上一段中描述的变换。
下面的插图在左侧显示原来的图像,在右侧显示变换后的图像。
下面示例中的代码使用以下步骤进行重新着色:
- 初始化 ColorMatrix 对象。
- 创建一个 ImageAttributes 对象,并将 ColorMatrix 对象传递给 ImageAttributes 对象的 SetColorMatrix 方法。
- 将 ImageAttributes 对象传递给 Graphics 对象的 DrawImage 方法。
前面的示例是为使用 Windows 窗体而设计的,它需要 Paint 事件处理程序的参数 PaintEventArgs e。
Image image = new Bitmap("InputColor.bmp");
ImageAttributes imageAttributes = new ImageAttributes();
int width = image.Width;
int height = image.Height;
float[][] colorMatrixElements = {
new float[] {2, 0, 0, 0, 0}, // red scaling factor of 2
new float[] {0, 1, 0, 0, 0}, // green scaling factor of 1
new float[] {0, 0, 1, 0, 0}, // blue scaling factor of 1
new float[] {0, 0, 0, 1, 0}, // alpha scaling factor of 1
new float[] {.2f, .2f, .2f, 0, 1}}; // three translations of 0.2
ColorMatrix colorMatrix = new ColorMatrix(colorMatrixElements);
imageAttributes.SetColorMatrix(
colorMatrix,
ColorMatrixFlag.Default,
ColorAdjustType.Bitmap);
e.Graphics.DrawImage(image, 10, 10);
e.Graphics.DrawImage(
image,
new Rectangle(120, 10, width, height), // destination rectangle
0, 0, // upper-left corner of source rectangle
width, // width of source rectangle
height, // height of source rectangle
GraphicsUnit.Pixel,
imageAttributes);