天天看點

24位真彩色轉換為8位灰階圖檔(完整代碼)

圖像的灰階與二值化

http://www.cnblogs.com/maozefa/archive/2011/12/09/2281656.html

    圖像的灰階化與二值化是圖像進行中最常見的處理方法,也是很多圖像處理方法的基礎,如圖像灰階統計、圖像識别等。

    圖像的灰階化與二值化方法較多,處理過程也比較簡單。但切不可因其簡單而忽視效率。如常用的圖像灰階計算公式:gray = red * 0.299 + green * 0.587 + blue * 0.114,如果在程式代碼中直接套用了這個公式,因浮點數的緣故導緻代碼執行效率較低,如改為定點整數運算,可使執行效率大大提高。

    下面是圖像的灰階與二值化代碼:

// 定義ARGB像素結構

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+圖像資料實作圖像灰階和二值化的例子代碼:

// 鎖定GDI+位位圖掃描線到data

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;

}

轉化效果如下圖

24位真彩色轉換為8位灰階圖檔(完整代碼)
24位真彩色轉換為8位灰階圖檔(完整代碼)

在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#中保留了特定情況下的指針操作。

圖像效果如下:

24位真彩色轉換為8位灰階圖檔(完整代碼)
24位真彩色轉換為8位灰階圖檔(完整代碼)

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位真彩色和轉換後的灰階圖

24位真彩色轉換為8位灰階圖檔(完整代碼)
24位真彩色轉換為8位灰階圖檔(完整代碼)

上邊的兩組圖就是用那段代碼處理的結果

另外一種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 )

.NET Framework 3.5

  • .NET Framework 3.0
  • .NET Framework 2.0

定義包含 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) 開始并應用下面的變換:

  1. 将紅色分量乘以 2。
  2. 将 0.2 添加到紅色、綠色和藍色分量中。

下面的矩陣乘法将按照列出的順序進行這對變換。

24位真彩色轉換為8位灰階圖檔(完整代碼)

顔色矩陣的元素按照先行後列(從 0 開始)的順序進行索引。 例如,矩陣 M 的第五行第三列由 M[4][2] 表示。

5×5 機關矩陣(在下面的插圖中顯示)在對角線上為 1,在其他任何地方為 0。 如果用機關矩陣乘以顔色矢量,則顔色矢量不會發生改變。 形成顔色變換矩陣的一種簡便方法是從機關矩陣開始,然後進行較小的改動以産生所需的變換。

24位真彩色轉換為8位灰階圖檔(完整代碼)

有關矩陣和變換的更詳細的讨論,請參見坐标系統和變形。

示例

下面的示例采用一個使用一種顔色 (0.2, 0.0, 0.4, 1.0) 的圖像,并應用上一段中描述的變換。

下面的插圖在左側顯示原來的圖像,在右側顯示變換後的圖像。

24位真彩色轉換為8位灰階圖檔(完整代碼)

下面示例中的代碼使用以下步驟進行重新着色:

  1. 初始化 ColorMatrix 對象。
  2. 建立一個 ImageAttributes 對象,并将 ColorMatrix 對象傳遞給 ImageAttributes 對象的 SetColorMatrix 方法。
  3. 将 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);