24位真彩色轉換為8位灰度圖片(完整程式碼)

pamxy發表於2013-03-23

轉自:http://blog.csdn.net/jiangxinyu/article/details/6222349

  影象的灰度化與二值化是影象處理中最常見的處理方法,也是很多影象處理方法的基礎,如影象灰度統計、影象識別等。

    影象的灰度化與二值化方法較多,處理過程也比較簡單。但切不可因其簡單而忽視效率。如常用的影象灰度計算公式: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;

}

轉化效果如下圖

 

 

 

 

 

 

 

 

在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 )

.NET Framework 3.5

定義包含 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 新增到紅色、綠色和藍色分量中。

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

 

 

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

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

 

 

有關矩陣和變換的更詳細的討論,請參見座標系統和變形

示例


下面的示例採用一個使用一種顏色 (0.2, 0.0, 0.4, 1.0) 的影象,並應用上一段中描述的變換。

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

 

 

 

下面示例中的程式碼使用以下步驟進行重新著色:

  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);


相關文章