【数字图像处理】三.MFC实现图像灰度、采样和量化功能详解
? ? ? ? 本文主要講述基于VC++6.0 MFC圖像處理的應用知識,主要結合自己大三所學課程《數字圖像處理》及課件進行講解,主要通過MFC單文檔視圖實現顯示BMP格式圖片,并通過Bitmap進行灰度處理、圖片采樣和量化功能。
? ? ? ? 個人認為對初學者VC++6.0可能還是很值得學習的工具,所以采用它來講解,而不是VS或C#。同時文章比較詳細基礎,希望該篇文章對你有所幫助~
? ? ? ?【數字圖像處理】一.MFC詳解顯示BMP格式圖片
? ? ? ?【數字圖像處理】二.MFC單文檔分割窗口顯示圖片
? ? ? ??免費資源下載地址:
? ? ? ??http://download.csdn.net/detail/eastmount/8748403
?
一. 單文檔顯示BMP圖片
? ? ? ? 第一步:新建項目"MFC AppWizard(exe)",項目名為ImageProcessing,在應用程序類型中選擇"單個文檔",點擊"確定"。在左欄的"資源視圖"中,點擊"Menu->IDR_MAINFRAM"可以查看并修改菜單視圖。
第二步:向CImageProcessingView類添加成員變量和成員函數。在右欄的"類視圖"右鍵ImageProcessingView添加函數或直接在ImageProcessingView.h中直接添加public成員變量和成員函數。添加代碼如下: // Implementation public://添加成員函數void ShowBitmap(CDC* pDC,CString BmpName); //顯示位圖函數//添加成員變量CString EntName; //圖像文件擴展名CString BmpName; //圖像文件名稱CBitmap m_bitmap; //創建位圖對象 ? ? ? ? 同時采用類視圖添加后,會自動在XXXView.h中添加函數定義,在XXXView.cpp中添加函數實現代碼。 ? 第三步:編輯ImageProcessingView.cpp中ShowBitmap()函數。通過它顯示BMP圖片,其中代碼及詳細注釋如下: //****************顯示BMP格式圖片****************// void CImageProcessingView::ShowBitmap(CDC *pDC, CString BmpName) {//定義bitmap指針 調用函數LoadImage裝載位圖HBITMAP m_hBitmap;m_hBitmap = (HBITMAP) LoadImage(NULL,BmpName,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);/*************************************************************************//* 1.要裝載OEM圖像,則設此參數值為0 OBM_ OEM位圖 OIC_OEM圖標 OCR_OEM光標/* 2.BmpName要裝載圖片的文件名 /* 3.裝載圖像類型: /* IMAGE_BITMAP-裝載位圖 IMAGE_CURSOR-裝載光標 IMAGE_ICON-裝載圖標 /* 4.指定圖標或光標的像素寬度和長度 以像素為單位 /* 5.加載選項:/* IR_LOADFROMFILE-指明由lpszName指定文件中加載圖像/* IR_DEFAULTSIZE-指明使用圖像默認大小/* LR_CREATEDIBSECTION-當uType參數為IMAGE_BITMAP時,創建一個DIB項/**************************************************************************/if( m_bitmap.m_hObject ){m_bitmap.Detach(); //切斷CWnd和窗口聯系}m_bitmap.Attach(m_hBitmap); //將句柄HBITMAP m_hBitmap與CBitmap m_bitmap關聯//邊界CRect rect;GetClientRect(&rect);//圖片顯示(x,y)起始坐標int m_showX=0;int m_showY=0;int m_nWindowWidth = rect.right - rect.left; //計算客戶區寬度int m_nWindowHeight = rect.bottom - rect.top; //計算客戶區高度//定義并創建一個內存設備環境DCCDC dcBmp;if( !dcBmp.CreateCompatibleDC(pDC) ) //創建兼容性的DCreturn;BITMAP m_bmp; //臨時bmp圖片變量m_bitmap.GetBitmap(&m_bmp); //將圖片載入位圖中CBitmap *pbmpOld = NULL; dcBmp.SelectObject(&m_bitmap); //將位圖選入臨時內存設備環境//圖片顯示調用函數stretchBltpDC->StretchBlt(0,0,m_bmp.bmWidth,m_bmp.bmHeight,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);/*******************************************************************************//* BOOL StretchBlt(int x,int y,int nWidth,int nHeight,CDC* pSrcDC, /* int xSrc,int ySrc,int nSrcWidth,int nSrcHeight,DWORD dwRop );/* 1.參數x、y位圖目標矩形左上角x、y的坐標值 /* 2.nWidth、nHeigth位圖目標矩形的邏輯寬度和高度 /* 3.pSrcDC表示源設備CDC指針 /* 4.xSrc、ySrc表示位圖源矩形的左上角的x、y邏輯坐標值 /* 5.dwRop表示顯示位圖的光柵操作方式 SRCCOPY用于直接將位圖復制到目標環境中 /*******************************************************************************/dcBmp.SelectObject(pbmpOld); //恢復臨時DC的位圖DeleteObject(&m_bitmap); //刪除內存中的位圖dcBmp.DeleteDC(); //刪除CreateCompatibleDC得到的圖片DC/*** 面代碼為后面顯示第二張圖片*/} ? ? ? ? 第四步:設置打開BMP圖片函數。"查看"->"建立類向導"(Ctrl+W)->選擇"類名"CImageProcessing->在命令對象ID中雙擊"ID_FILE_OPEN"->自動生成默認成員函數OnFileOpen,消息為COMMAND。雙擊成員函數(Member Functions)進入函數編輯。 ? ? ? ? 編輯ImageProcessingView.cpp函數實現打開圖片,代碼如下: //****************打開文件****************// void CImageProcessingView::OnFileOpen() {//兩種格式的文件:bmp gifCString filter; filter="所有文件(*.bmp,*.jpg,*.gif)|*.bmp;*.jpg| BMP(*.bmp)|*.bmp| JPG(*.jpg)|*.jpg||"; CFileDialog dlg(TRUE,NULL,NULL,OFN_HIDEREADONLY,filter,NULL); //按下確定按鈕 dlg.DoModal() 函數顯示對話框 if( dlg.DoModal() == IDOK ) { BmpName = dlg.GetPathName(); //獲取文件路徑名 如D:\pic\abc.bmp EntName = dlg.GetFileExt(); //獲取文件擴展名 EntName.MakeLower(); //將文件擴展名轉換為一個小寫字符 Invalidate(); //調用該函數就會調用OnDraw重繪畫圖 } } ? ? ? ? 第五步:在ImageProcessingView.cpp中找到OnDraw()函數,通過OnDraw()函數調用ShowBitmap()函數顯示圖片。代碼如下: void CImageProcessingView::OnDraw(CDC* pDC) {CImageProcessingDoc* pDoc = GetDocument();ASSERT_VALID(pDoc);// TODO: add draw code for native data hereif (!pDoc) return; if( EntName.Compare(_T("bmp")) == 0 ) //bmp格式 { ShowBitmap(pDC,BmpName); //顯示圖片 } } ? ? ? ? 第六步:此時點擊運行,同時點擊文件-打開,即可顯示圖片如下圖所示: PS:這是非常著名的一張圖片萊娜圖(Lenna),全圖是一張花花公子封面的裸圖,后成為數字圖像處理的標志圖片。哈哈~至于BMP圖片格式參照第一篇文章?
二. 讀取BMP圖片和保存圖片
BMP圖片格式如下圖所示:(參考自己文庫) 在很多處理中,都需要獲取BMP圖像的一些數據,如圖像寬度、高度、像素大小等,后面的處理與之相關,主要的是ReadBmp函數。? ? ? ?第一步:在XXXView.h中添加BMP格式圖像相關的成員變量和成員函數,其中成員函數通過類視圖右鍵添加,成員變量可以在XXXView.h中直接復制。
? // Implementation public://添加成員函數void ShowBitmap(CDC* pDC,CString BmpName); //顯示位圖函數bool ReadBmp(); //用來讀取bmp個手機圖片bool SaveBmp(LPCSTR lpFileName); //用來保存bmp格式圖片//添加成員變量CString EntName; //圖像文件擴展名CString BmpName; //圖像文件名稱CBitmap m_bitmap; //創建位圖對象int m_nWidth; //圖像實際寬度int m_nHeight; //圖像實際高度int m_nDrawWidth; //圖像顯示寬度int m_nDrawHeight; //圖像顯示高度DWORD m_nImage; //圖像數據的字節數 只含位圖DWORD m_nSize; //圖像文件大小int m_nLineByte; //圖像一行所占字節數int m_nBitCount; //圖像每個像素所占位數int m_nPalette; //位圖實際使用的顏色表中的顏色數BYTE *m_pImage; //讀入圖片數據后的指針BITMAPFILEHEADER bfh; //全局變量文件頭BITMAPINFOHEADER bih; //全局變量信息頭RGBQUAD m_pPal; //顏色表指針 ? ? ? ? 第二步:在ImageProcessingView.cpp中實現ReadBmp函數和SaveBmp函數。
? //***************讀取圖片數據*************// bool CImageProcessingView::ReadBmp() {//圖片讀出存儲其中的東西FILE *fp = fopen(BmpName,"rb");if(fp==0){ AfxMessageBox("無法打開文件!",MB_OK,0);return 0; }//讀取文件頭 解決BMP格式倒置的方法fread(&bfh.bfType,sizeof(WORD),1,fp);fread(&bfh.bfSize,sizeof(DWORD),1,fp);fread(&bfh.bfReserved1,sizeof(WORD),1,fp);fread(&bfh.bfReserved2,sizeof(WORD),1,fp);fread(&bfh.bfOffBits,sizeof(DWORD),1,fp);//圖像文件的總字節數m_nSize = bfh.bfSize;//判斷是否是bmp格式圖片if(bfh.bfType!=0x4d42) //'BM'{AfxMessageBox("不是BMP格式圖片!",MB_OK,0);return 0;}//讀取信息頭fread(&bih.biSize,sizeof(DWORD),1,fp);fread(&bih.biWidth,sizeof(LONG),1,fp);fread(&bih.biHeight,sizeof(LONG),1,fp);fread(&bih.biPlanes,sizeof(WORD),1,fp);fread(&bih.biBitCount,sizeof(WORD),1,fp);fread(&bih.biCompression,sizeof(DWORD),1,fp);fread(&bih.biSizeImage,sizeof(DWORD),1,fp);fread(&bih.biXPelsPerMeter,sizeof(LONG),1,fp);fread(&bih.biYPelsPerMeter,sizeof(LONG),1,fp);fread(&bih.biClrUsed,sizeof(DWORD),1,fp);fread(&bih.biClrImportant,sizeof(DWORD),1,fp);if(bih.biSize!=sizeof(bih)){AfxMessageBox("本結構所占用字節數出現錯誤");return 0;}//位圖壓縮類型,必須是 0(不壓縮) 1(BI_RLE8壓縮類型)或2(BI_RLE壓縮類型)之一if(bih.biCompression == BI_RLE8 || bih.biCompression == BI_RLE4){AfxMessageBox("位圖被壓縮!");return 0;}//獲取圖像高寬和每個像素所占位數m_nHeight = bih.biHeight;m_nWidth = bih.biWidth;m_nDrawHeight = bih.biHeight;m_nDrawWidth = bih.biWidth;m_nBitCount = bih.biBitCount; //每個像素所占位數//計算圖像每行像素所占的字節數(必須是32的倍數)m_nLineByte = (m_nWidth*m_nBitCount+31)/32*4;//圖片大小 調用系統自帶的文件頭 BITMAPFILEHEADER bfh; BITMAPINFOHEADER bih; //否則用 BITMAPFILEHEADER_ bfh; BITMAPINFOHEADER_ bih;要 m_nImage = m_nLineByte * m_nHeight - 2;m_nImage = m_nLineByte * m_nHeight;//位圖實際使用的顏色表中的顏色數 biClrUsedm_nPalette = 0; //初始化if(bih.biClrUsed)m_nPalette = bih.biClrUsed;//申請位圖空間 大小為位圖大小 m_nImage//malloc只能申請4字節的空間 (未知)m_pImage=(BYTE*)malloc(m_nImage);fread(m_pImage,m_nImage,1,fp);fclose(fp);return true; } ? ? ? ?其中SaveBmp()函數代碼如下: //****************保存文件****************// bool CImageProcessingView::SaveBmp(LPCSTR lpFileName) //lpFileName為位圖文件名 {//保存bmp格式圖片 寫圖片過程 只處理24像素的圖片 該圖片無調色板FILE *fpo = fopen(BmpName,"rb");FILE *fpw = fopen(lpFileName,"wb");fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);//malloc只能申請4字節的空間 (未知)m_pImage=(BYTE*)malloc(m_nImage);fread(m_pImage,m_nImage,1,fpo);fwrite(m_pImage,m_nImage,1,fpw);fclose(fpo);fclose(fpw);return true; } ? ? ? ? 第三步:添加保存menu控件和函數。點擊”查看-建立類向導“,在ID列表中找到ID_FILE_SAVE,點擊COMMAND(Message列表),雙擊添加默認成員函數OnFileSave,同時在Member Functions(成員函數)中雙擊該函數進入函數并編輯。添加如下代碼:
? //******************文件保存*****************// void CImageProcessingView::OnFileSave() {// TODO: Add your command handler code hereCString filter;filter="所有文件(*.bmp,*.jpg)|*.bmp;*.jpg| BMP(*.bmp)|*.bmp| JPG(*.jpg)|*.jpg||";//重點: 1-文件打開 0-文件保存CFileDialog dlg(0,NULL,NULL,OFN_HIDEREADONLY,filter,NULL); //按下確定按鈕if( dlg.DoModal() == IDOK ) {CString str;CString strName;CString filename;str = dlg.GetPathName(); //獲取文件的路徑filename = dlg.GetFileTitle(); //獲取文件名int nFilterIndex=dlg.m_ofn.nFilterIndex;if( nFilterIndex == 2 ) //當用戶選擇文件過濾器為".BMP"時{str = str + ".bmp"; //自動加擴展名.bmpSaveBmp(str); //保存bmp圖片 就是一個寫出圖片的過程 AfxMessageBox("圖片保存成功",MB_OK,0);}} } ? ? ? ? 第四步:在XXXView.cpp中OnDraw()函數中調用讀取圖片函數。
? ? ? ?if( EntName.Compare(_T("bmp")) == 0 ) ? ? ?//bmp格式 ?
? ? ? ? { ?
? ? ? ? ? ? ? ? ReadBmp();
? ? ? ? ? ? ? ? ShowBitmap(pDC,BmpName); ? ? ? ? ? ? ? //顯示圖片 ?
? ? ? ? } ?
? ? ? ? 運行程序,打開圖片點擊保存即可實現。重點是ReadBmp獲取一些重要參數。
?
三. 圖像灰度處理
(參考我的百度文庫:http://wenku.baidu.com/view/3b869230f111f18583d05a43)?
1.灰度圖像概念
? ? ? ? 什么叫灰度圖?任何顏色都有紅、綠、藍三原色組成,假如原來某點的顏色為RGB(R,G,B),那么我們可以通過下面幾種方法,將其轉換為灰度:
? ? ? ? 浮點算法:Gray=R*0.3+G*0.59+B*0.11
? ? ? ? 整數方法:Gray=(R*30+G*59+B*11)/100
? ? ? ? 移位方法:Gray=(R*28+G*151+B*77)>>8;
? ? ? ? 平均值法:Gray=(R+G+B)/3;(此程序采用算法)
? ? ? ? 僅取綠色:Gray=G;
? ? ? ? 通過上述任一種方法求得Gray后,將原來的RGB(R,G,B)中的R,G,B統一用Gray替換,形成新的顏色RGB(Gray,Gray,Gray),用它替換原來的RGB(R,G,B)就是灰度圖了。
? ? ? ? 改變象素矩陣的RGB值,來達到彩色圖轉變為灰度圖
? ? ? ? 加權平均值算法:根據光的亮度特性,其實正確的灰度公式應當是:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?? ?R=G=B=R*0.299+G*0.587+B0.144
? ? ? ??為了提高速度我們做一個完全可以接受的近似,公式變形如下:R=G=B=(R*3+G*6+B)/10?
? ? ? ? 真正的24位真彩圖與8位的灰度圖的區別就在于,真彩圖文件中沒有調色板,灰度圖有調色板,真彩圖中的象素矩陣是RGB值,灰度圖中的象素矩陣是調色板索引值。源代碼只簡單的改變象素矩陣的RGB值,來達到彩色圖轉為灰度圖,并沒有添加調色板;該程序未實現添加了調色板。
?
2.灰度處理源碼
第一步:在前面的代碼基礎上繼續,先在ImageProcessingView.h中添加成員變量m_bitmaplin和BmpNameLin,因為后面處理操作是處理備份文件與原圖進行比較。? // Implementation public://添加成員函數void ShowBitmap(CDC* pDC,CString BmpName); //顯示位圖函數bool ReadBmp(); ? ? ? ? ? ? ? ? ? //用來讀取bmp個手機圖片bool SaveBmp(LPCSTR lpFileName); ? ? ? //用來保存bmp格式圖片//添加成員變量CString EntName; //圖像文件擴展名CString BmpName; //圖像文件名稱CBitmap m_bitmap; //創建位圖對象CBitmap m_bitmaplin; //創建臨時位圖對象進行處理CString BmpNameLin; //保存圖像副本文件 ? ? ? ? 第二步:在ImageProcessingView.cpp中ShowBitmap()函數前添加變量numPicture和level。
? /*************************************************************/ /* numPicture變量顯示圖片數量 /* 0-提示錯誤或未打開圖片 1-顯示一張圖片 2-顯示兩張圖片和處理 /*************************************************************/ int numPicture = 0;/*************************************************************/ /* level變量顯示具體的處理操作,每個處理函數中賦值該變量 /* 0-顯示2張圖片 1-顯示灰度圖片 3-顯示圖片采樣 /* 2 4 8 16 32 64-不同量化等級量化圖片 /*************************************************************/ int level = 0; //****************顯示BMP格式圖片****************// void CImageProcessingView::ShowBitmap(CDC *pDC, CString BmpName) {.... } ? ? ? ? 第三步:修改ImageProcessingView.cpp中OnFileOpen()函數,添加臨時變量名和顯示一張圖片標志變量。代碼如下:
? //****************打開文件****************// void CImageProcessingView::OnFileOpen() {CString filter; filter="所有文件(*.bmp,*.jpg,*.gif)|*.bmp;*.jpg| BMP(*.bmp)|*.bmp| JPG(*.jpg)|*.jpg||"; CFileDialog dlg(TRUE,NULL,NULL,OFN_HIDEREADONLY,filter,NULL); if( dlg.DoModal() == IDOK ) { BmpName = dlg.GetPathName(); BmpNameLin = "picture.bmp"; //臨時變量名numPicture=1; //顯示一張圖片EntName = dlg.GetFileExt(); EntName.MakeLower(); Invalidate(); } } ? ? ? ? 第四步:將視圖切換到ResourceView界面,選中Menu->在IDR_MAINFRAME中添加菜單”顯示“,雙擊它在菜單屬性中選擇”彈出“。在”顯示“的子菜單中添加:
? ? ? ? 雙圖顯示--ID_SHOW_TWO(ID)--默認屬性
? ? ? ? 灰度圖片--ID_SHOW_HD(ID)--默認屬性 第五步:點擊"查看"->"建立類向導"(Ctrl+W),選擇CImageProcessing類,然后ID_SHOW_TWO,雙擊COMMAND(Message),生成默認成員函數。
? 在XXXView.cpp中實現OnShowTwo()函數,代碼如下: //****************顯示兩張圖片****************// void CImageProcessingView::OnShowTwo() {//如果沒有導入圖片直接點擊雙顯 提示錯誤if(numPicture==0){AfxMessageBox("載入圖片后才能顯示2張圖片!");return;}AfxMessageBox("顯示兩張圖片!",MB_OK,0);numPicture = 2; //全局變量 顯示兩圖level =0; //level=0雙顯Invalidate(); //調用Invalidate 每秒調用一次OnDraw畫圖 } ? ? ? ? 第六步:同上面相同的方法,"查看"->”建立類向導“->ID_SHOW_HD(ID)->COMMAND(Message),默認成員函數名。在XXXView.cpp添加代碼如下: /********************************************************************************************/ /* 祥見http://blog.csdn.net/xiakq/article/details/2956902有詳細的灰度算法 /* 其中24位的圖片灰度時,采用如下算法: /* 1.平均值算法 R=G=B=(R+G+B)/3 /* 2.快速算法 R=G=B=(R+G+B+128)/4>>2 /* 3.加權平均值算法 根據光的亮度特性,其實正確的灰度公式應當是R=G=B=R*0.299+G*0.587+B0.144 /* 為了提高速度我們做一個完全可以接受的近似,公式變形如下 R=G=B=(R*3+G*6+B)/10 /* 4.精確加權平均值算法 R=G=B=R*0.299+G*0.587+B0.144 /********************************************************************************************///**灰度圖像就是 R=G=B且為三者的1/3 level=1時灰度圖像**// void CImageProcessingView::OnShowHd() {if(numPicture==0){AfxMessageBox("載入圖片后才能灰度圖片!",MB_OK,0);return;}AfxMessageBox("灰度圖像!",MB_OK,0);//打開臨時的圖片FILE *fpo = fopen(BmpName,"rb");FILE *fpw = fopen(BmpNameLin,"wb+");//讀取文件fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);//灰度圖像unsigned char color;unsigned char red,green,blue;/********************************************************************//* 注意:原來下面所有操作都是for( i=0; i<m_nWidth*m_nHeight; i++ ) /* 后發現如果圖片最后一行沒有完整的一行數據,會出現圖像變多或變少 /* 但圖像的總像素為m_nImage,如果是m_nImage/3就可以保證所有像素都有 /********************************************************************/for(int i=0; i < m_nImage/3; i++ ){fread(&red,sizeof(char),1,fpo);fread(&green,sizeof(char),1,fpo);fread(&blue,sizeof(char),1,fpo);color=(red+green+blue)/3;red=color;green=color; blue=color;fwrite(&red,sizeof(char),1,fpw);fwrite(&green,sizeof(char),1,fpw);fwrite(&blue,sizeof(char),1,fpw);}fclose(fpo);fclose(fpw);numPicture = 2;level=1;Invalidate(); } ? ? ? ? 第七步:修改ShowBitmap()函數中雙顯部分,添加如下代碼:
? //****************顯示BMP格式圖片****************// void CImageProcessingView::ShowBitmap(CDC *pDC, CString BmpName) {..../*** 面代碼為后面顯示第二張圖片*/if(numPicture==2) {//顯示圖片函數LoadImageHBITMAP m_hBitmapChange;if(level==0) //顯示2張圖 BmpNameLin原圖{m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpName,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);}elseif(level==1) //灰度圖片 BmpNameLin臨時圖片{m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);}if( m_bitmap.m_hObject ) {m_bitmap.Detach(); //m_bitmap為創建的位圖對象}m_bitmap.Attach(m_hBitmapChange);//定義并創建一個內存設備環境CDC dcBmp;if( !dcBmp.CreateCompatibleDC(pDC) ) //創建兼容性的DCreturn;BITMAP m_bmp; //臨時bmp圖片變量m_bitmap.GetBitmap(&m_bmp); //將圖片載入位圖中CBitmap *pbmpOld = NULL;dcBmp.SelectObject(&m_bitmap); //將位圖選入臨時內存設備環境//如果圖片太大顯示大小為固定640*640 否則顯示原圖大小if(m_nDrawWidth<650 && m_nDrawHeight<650)pDC->StretchBlt(m_nWindowWidth-m_nDrawWidth,0,m_nDrawWidth,m_nDrawHeight,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);elsepDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY); //恢復臨時DC的位圖dcBmp.SelectObject(pbmpOld); }} ? ? ? ? 雙顯和灰度運行效果如下圖所示:
?
四. 圖片量化處理
(參考我的文庫:http://wenku.baidu.com/view/80b18961f5335a8102d220a0)
1.量化基本概念
? ? ? ? 圖像數字化包括量化和取樣兩個過程,其中:
? ? ? ? 量化:幅值f(x,y)的離散化,f(x,y)表示靜止灰度圖像的空間坐標
? ? ? ? 取樣:對空間連續坐標(x,y)的離散化
? ? ? ??一幅行數為M、列數為N的圖像大小為M×N的矩陣形式為:(其中矩陣中每個元素代表一個像素)
? ? ? ? 該工程所有的處理都基于24位的bmp格式圖片的處理,24為表示biBitCount=24,1個像素占3個字節(red、green、blue)。
? ? ? ? 如圖量化級不同產生的灰度也不同,量化是使連續信號的幅度用有限級的數碼表示的過程。
? ? ? ? 量化等級=2:使用2種灰度級(0~255)表示圖片,小于128的取0,大于等于128的取128。把位圖數據塊所有數據在臨時圖片中取值,在顯示即可。
? ? ? ? 量化等級=4:使用4種灰度級顯示圖片,就會發現圖片分層為4種顏色。同時,0-64區間取0,64-128區間取64,128-192區間取128,192-255區間取192。
? ? ? ? 量化的取值各不相同,我采用的是最簡單的取值。其它方法可自己去查閱資料。
?
2.量化處理源碼
? ? ? ? 第一步:設置菜單欄。將試圖切換到ResourceView界面--選中Menu--在IDR_MAINFRAME中添加菜單“量化”--雙擊它在菜單屬性中選擇“彈出”。在“顯示”的子菜單中添加:屬性為默認屬性。
? ? ? ? 量化 Level 2--ID_LH_2 ? ? ? 量化 Level 4--ID_LH_4
? ? ? ? 量化 Level 8--ID_LH_8 ? ? ? 量化 Level 16--ID_LH_16
? ? ? ? 量化 Level 32--ID_LH_32?? 量化 Level 64--ID_LH_64
? ? ? ? 核心流程是打開兩張圖片原圖(BmpName)和臨時圖片(BmpNameLin),然后讀取原圖信息頭賦值給臨時處理圖片,在讀取原圖m_nImage整個像素矩陣,量化處理每個像素(即分等級量化),最后文件寫量化后的像素矩陣給BmpNameLin,在賦值全局變量level\numPicture和調用Invalidate()重繪圖像即可。
? //****************量化 量化等級為2****************// void CImageProcessingView::OnLh2() {if(numPicture==0) {AfxMessageBox("載入圖片后才能量化!",MB_OK,0);return;}AfxMessageBox("量化等級Level=2!",MB_OK,0);//打開臨時的圖片FILE *fpo = fopen(BmpName,"rb");FILE *fpw = fopen(BmpNameLin,"wb+");//讀取文件fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);//malloc只能申請4字節的空間m_pImage=(BYTE*)malloc(m_nImage);fread(m_pImage,m_nImage,1,fpo);//等級2量化for(int i=0; i<m_nImage; i++ ) {//24位的為調色板為真彩圖 Red Green Blue 為3字節 //量化等級為2取中間值為 64 和 192if(m_pImage[i]<128) { m_pImage[i]=0;}else if(m_pImage[i]>=128) {m_pImage[i]=128;}}fwrite(m_pImage,m_nImage,1,fpw);fclose(fpo);fclose(fpw);numPicture = 2;level=2;Invalidate(); }//****************量化 量化等級為4****************// void CImageProcessingView::OnLh4() {if(numPicture==0) {AfxMessageBox("載入圖片后才能量化!",MB_OK,0);return;}AfxMessageBox("量化等級Level=4!",MB_OK,0);//打開臨時的圖片FILE *fpo = fopen(BmpName,"rb");FILE *fpw = fopen(BmpNameLin,"wb+");fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);m_pImage=(BYTE*)malloc(m_nImage);fread(m_pImage,m_nImage,1,fpo);//等級4量化for(int i=0; i<m_nImage; i++ ) {if(m_pImage[i]<64) {m_pImage[i]=0;}else if( (m_pImage[i]>=64) && (m_pImage[i]<128) ) {m_pImage[i]=64;}else if( (m_pImage[i]>=128) && (m_pImage[i]<192) ) {m_pImage[i]=128;}else if(m_pImage[i]>=192) {m_pImage[i]=192;}}fwrite(m_pImage,m_nImage,1,fpw);fclose(fpo);fclose(fpw);numPicture = 2;level=4;Invalidate(); }//****************量化 量化等級為8****************// void CImageProcessingView::OnLh8() {if(numPicture==0) {AfxMessageBox("載入圖片后才能量化!",MB_OK,0);return;}AfxMessageBox("量化等級Level=8!",MB_OK,0);//打開臨時的圖片 讀取文件FILE *fpo = fopen(BmpName,"rb");FILE *fpw = fopen(BmpNameLin,"wb+");fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);//malloc只能申請4字節的空間 (未知)m_pImage=(BYTE*)malloc(m_nImage);fread(m_pImage,m_nImage,1,fpo);//等級8量化for(int i=0; i<m_nImage; i++ ) {if(m_pImage[i]<32) {m_pImage[i]=0;}else if( (m_pImage[i]>=32) && (m_pImage[i]<64) ) {m_pImage[i]=32;}else if( (m_pImage[i]>=64) && (m_pImage[i]<96) ) {m_pImage[i]=64;}else if( (m_pImage[i]>=96) && (m_pImage[i]<128) ) {m_pImage[i]=96;}else if( (m_pImage[i]>=128) && (m_pImage[i]<160) ) {m_pImage[i]=128;}else if( (m_pImage[i]>=160) && (m_pImage[i]<192) ) {m_pImage[i]=160;}else if( (m_pImage[i]>=192) && (m_pImage[i]<224) ) {m_pImage[i]=192;}else if(m_pImage[i]>=224) {m_pImage[i]=224;}}fwrite(m_pImage,m_nImage,1,fpw);fclose(fpo);fclose(fpw);numPicture = 2;level=8;Invalidate(); }//****************量化 量化等級為16****************// void CImageProcessingView::OnLh16() {if(numPicture==0) {AfxMessageBox("載入圖片后才能量化!",MB_OK,0);return;}AfxMessageBox("量化等級Level=16!",MB_OK,0);int i,j;//打開臨時的圖片FILE *fpo = fopen(BmpName,"rb");FILE *fpw = fopen(BmpNameLin,"wb+");fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);m_pImage=(BYTE*)malloc(m_nImage);fread(m_pImage,m_nImage,1,fpo);for( i=0; i<m_nImage; i++ ) {j=16;while(j<=256){if(m_pImage[i]<j) {if(m_pImage[i]<16) m_pImage[i]=0;else m_pImage[i]=j-16;break;}else j+=16;}}fwrite(m_pImage,m_nImage,1,fpw);fclose(fpo);fclose(fpw);numPicture = 2;level=16;Invalidate(); }//****************量化 量化等級為32****************// void CImageProcessingView::OnLh32() {if(numPicture==0) {AfxMessageBox("載入圖片后才能量化!",MB_OK,0);return;}AfxMessageBox("量化等級Level=32!",MB_OK,0);int i,j;//打開臨時的圖片FILE *fpo = fopen(BmpName,"rb");FILE *fpw = fopen(BmpNameLin,"wb+");//讀取文件fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);m_pImage=(BYTE*)malloc(m_nImage);fread(m_pImage,m_nImage,1,fpo);//等級32化for( i=0; i<m_nImage; i++ ){j=8;while(j<=256){if(m_pImage[i]<j) {if(m_pImage[i]<8) m_pImage[i]=0;else m_pImage[i]=j-8;break;}else j+=8;}}fwrite(m_pImage,m_nImage,1,fpw);fclose(fpo);fclose(fpw);numPicture = 2;level=32;Invalidate(); }//****************量化 量化等級為64****************// void CImageProcessingView::OnLh64() {if(numPicture==0) {AfxMessageBox("載入圖片后才能量化!",MB_OK,0);return;}AfxMessageBox("量化等級Level=64!",MB_OK,0);int i,j;//打開臨時的圖片FILE *fpo = fopen(BmpName,"rb");FILE *fpw = fopen(BmpNameLin,"wb+");//讀取文件fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);m_pImage=(BYTE*)malloc(m_nImage);fread(m_pImage,m_nImage,1,fpo);//等級64量化for( i=0; i<m_nImage; i++ ){j=4;while(j<=256){if(m_pImage[i]<j) {if(m_pImage[i]<16) m_pImage[i]=0;else m_pImage[i]=j-4;break;}else j+=4;}}fwrite(m_pImage,m_nImage,1,fpw);fclose(fpo);fclose(fpw);numPicture = 2;level=64;Invalidate(); } ? ? ? ? 第四步:修改ShowBitmap()函數,顯示量化處理。添加如下代碼:
? if(level==0) //顯示2張圖 BmpNameLin原圖 {m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpName,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION); } else if(level==1) //灰度圖片 BmpNameLin臨時圖片 {m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION); } else //量化2 if(level==2) {m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION); } else //量化4 if(level==4) {m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION); } else //量化8 if(level==8) {m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION); } else //量化16 if(level==16) {m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION); } else //量化32 if(level==32) {m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION); } else //量化64 if(level==64) {m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION); } ? ? ? ? 運行效果如下圖,當量化Level=2時很明顯的兩種灰度顏色,Level=4有4種顏色。
?
五. 圖像采樣功能
(參考我的文庫:http://wenku.baidu.com/view/b3ef4e1f964bcf84b9d57baf)
1.圖像采樣概念
? ? ? ? 該工程所有的處理都基于24位的bmp格式圖片的處理,24為表示biBitCount=24,1個像素占3個字節(red、green、blue)。如圖一張512*512的原圖,保持灰度級256不變后的各種采樣。輸入采樣坐標:如16*16,它的含義是原圖512*512像素,現在組成一個新的圖片為16*16像素,(512/16=32,512/16=32)則每32*32組成一個新的區域。共有這種區域16*16個,采樣的方法有2種:
? ? ? ? a.把這個32*32區域全部賦值成左上角那個像素,這樣圖片的大小不變,困難在于賦值要4層循環。(項目中采用的就是這種方法)
? ? ? ? b.把這個32*32區域的左上角取出來,組成一個新的圖片,共有16*16個像素,這張圖片的大小要變小,只有16*16個像素。但難點在于同時要把bmp文件頭中的圖片大小、信息頭中的長寬像素改變、偏移量等信息更新。
? ? ? ? 原圖8*8的矩陣要處理成3*3的矩陣,則循環先處理第一二行,①②④⑤為3*3處理,去左上角的RGB,③⑥為2*3的處理;重點是原圖讀取一維數組需要轉成二維數組賦值處理;最后再處理最后一行數據。采樣中公式為:
? ? ? ? //獲取填充顏色 相當于一次讀取一個像素的RGB值再乘3跳3個字節
? ? ? ? red=m_pImage[(X+Y*m_nWidth)*3];
? ? ? ? green=m_pImage[(X+Y*m_nWidth)*3+1];
? ? ? ? blue=m_pImage[(X+Y*m_nWidth)*3+2];
? ? ? ? //填出圖像循環 小區域中的長寬循環
? ? ? ? //(X+Y*m_nWidth)*3跳到該小區域 再賦值3*3小區域的RGB 同一區域RGB相同
? ? ? ? m_pImage[(X+Y*m_nWidth)*3+m+n*m_nWidth*3]=red;?m++;
? ? ? ? m_pImage[(X+Y*m_nWidth)*3+m+n*m_nWidth*3]=green;?m++;
? ? ? ? m_pImage[(X+Y*m_nWidth)*3+m+n*m_nWidth*3]=blue;?m++; PS:難點是還未處理剩余部分的采樣。
?
2.圖像采樣代碼
第一步:設置菜單欄? ? ? ? a.將視圖切換到ResourceView界面--選中Menu--在IDR_MAINFRAME中添加菜單“采樣”--雙擊它在菜單屬性中選擇“彈出”;
? ? ? ? b.在“采樣”的子菜單中添加:屬性為默認屬性。ID_CY--圖片采樣。
? ? ? ? c.建立類導向:查看--建立類導向(Ctrl+W)--CImageProcessingView(類名)--ID_CY--COMMAND(Messages)--默認成員函數名。生成void CImageProcessingView::OnCy()采樣函數。
? ? ? ? 第二步:設置采樣對話框
? ? ? ? a.將試圖切換到ResourceView界面--選中Dialog,右鍵鼠標新建一個Dialog,并新建一個名為IDD_DIALOG_CY。編輯框(X)IDC_EDIT_CYX 和 (Y)IDC_EDIT_CYY,確定為默認按鈕。設置成下圖對話框: b.在對話框資源模板空白區域雙擊鼠標—Create a new class創建一個新類--命名為CImageCYDlg。會自動生成它的.h和.cpp文件。類向導Ctrl W--類名:CImageCYDlg--CImageCYDlg(IDs)—WM_INITDLAOG建立這個函數可以用于初始化。
?
c.打開類向導Ctrl+W--選擇MemberVariables頁面,類名:CImageCYDlg--Add Variables--設置成: IDC_EDIT_CYX--int--m_xPlace? ? ? ? ? ? ? ? IDC_EDIT_CYY--int--m_yPlace
? ? ? ? d.在View.cpp中添加采樣的頭文件#include "ImageCYDlg.h"
? ? ? ? 第三步:在ImageProcessingView.cpp中添加代碼
//****************圖片采樣****************// void CImageProcessingView::OnCy() {if(numPicture==0) {AfxMessageBox("載入圖片后才能采樣!",MB_OK,0);return;}CImageCYDlg dlg; //定義采樣對話框//顯示對話框if( dlg.DoModal()==IDOK ) {//采樣坐標最初為圖片的自身像素if( dlg.m_xPlace==0 || dlg.m_yPlace==0 ) {AfxMessageBox("輸入圖片像素不能為0!",MB_OK,0);return;}if( dlg.m_xPlace>m_nWidth || dlg.m_yPlace>m_nHeight ) {AfxMessageBox("圖片像素不能為超過原圖長寬!",MB_OK,0);return;}AfxMessageBox("圖片采樣!",MB_OK,0);//打開臨時的圖片 讀取文件FILE *fpo = fopen(BmpName,"rb");FILE *fpw = fopen(BmpNameLin,"wb+");fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);fread(m_pImage,m_nImage,1,fpo);/*圖片采樣*/int numWidth,numHeight; //圖片此區間取相同的像素點int numSYWidth,numSYHeight; //剩余期間區域 /*********************************************************//* 表示numWidth*numHeight為一個區域 該區域顏色相同 /* 如 512/512=1 512/512=1 1*1為一個區域 /* dlg.m_xPlace*dlg.m_yPlace 表示新的(x,y)坐標 /* numSYWidth表示剩余空間 該區域統一為一個顏色 /*********************************************************/numWidth=m_nWidth/dlg.m_xPlace; numHeight=m_nHeight/dlg.m_yPlace; numSYWidth=m_nWidth%dlg.m_xPlace; numSYHeight=m_nHeight%dlg.m_yPlace; int Y,X;int i,j,m,n;unsigned char red,green,blue; //存儲三種顏色/* 有((m_xPlace * m_yPlace)+ 剩余區域 )個小區域 */for( i=0; i<dlg.m_yPlace; i++ ) //高度{Y=numHeight*i; //獲取Y坐標for( j=0; j<dlg.m_yPlace; j++ ) //寬度{X=numWidth*j; //獲取X坐標/*獲取填充顏色*/red=m_pImage[(X+Y*m_nWidth)*3];green=m_pImage[(X+Y*m_nWidth)*3+1];blue=m_pImage[(X+Y*m_nWidth)*3+2];/*填出圖像循環 小區域中的長寬循環*/for( n=0; n<numHeight; n++ ){for( m=0; m<numWidth*3; ){m_pImage[(X+Y*m_nWidth)*3+m+n*m_nWidth*3]=red;m++;m_pImage[(X+Y*m_nWidth)*3+m+n*m_nWidth*3]=green;m++;m_pImage[(X+Y*m_nWidth)*3+m+n*m_nWidth*3]=blue;m++;}}}}fwrite(m_pImage,m_nImage,1,fpw);fclose(fpo);fclose(fpw);numPicture = 2;level=3;Invalidate();} } 第四步:修改ShowBitmap(CDC* pDC,CString BmpName)中的代碼: else if(level==3) //圖片采樣 { m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0, LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION); ? ? ? ? }? ? ? ? 運行效果如下圖所示,其中彩色圖片應該先灰度處理再進行其他操作。 ? ?
? ? ? ? 總結:后悔當初還沒有寫博客,通過回憶幾年前的代碼,很多當時的體會和思想都已不復存在了!可能你在百度文庫中看到類似的文章,因為那些都是我在2012年上傳的,最初是通過它進行分享編程知識的,后來發現了更好的CSDN而取代之。這篇文章感覺太詳細,有時候一直懷疑是不是失去了算法的本質,不應該寫這么詳細的文章,而更加精簡一點,但可能和從小記筆記有關,很難改過來了,慢慢改吧!
? ? ? ? 最后還是希望文章對你有所幫助,如果文章有不足或錯誤之處,請海涵~
? ? ? (By:Eastmount 2015-5-28 下午點 ??http://blog.csdn.net/eastmount/)
? ? ? ??
?
總結
以上是生活随笔為你收集整理的【数字图像处理】三.MFC实现图像灰度、采样和量化功能详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java+MyEclipse+Tomca
- 下一篇: 【数字图像处理】四.MFC对话框绘制灰度