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; //計算客戶區高度
//定義并創建一個內存設備環境DC
CDC dcBmp;
if( !dcBmp.CreateCompatibleDC(pDC) ) //創建兼容性的DC
return;
BITMAP m_bmp; //臨時bmp圖片變量
m_bitmap.GetBitmap(&m_bmp); //將圖片載入位圖中
CBitmap *pbmpOld = NULL;
dcBmp.SelectObject(&m_bitmap); //將位圖選入臨時內存設備環境
//圖片顯示調用函數stretchBlt
pDC->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 gif
CString 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 here
if (!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;
//位圖實際使用的顏色表中的顏色數 biClrUsed
m_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 here
CString 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"; //自動加擴展名.bmp
SaveBmp(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) {
//顯示圖片函數LoadImage
HBITMAP m_hBitmapChange;
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);
}
if( m_bitmap.m_hObject ) {
m_bitmap.Detach(); //m_bitmap為創建的位圖對象
}
m_bitmap.Attach(m_hBitmapChange);
//定義并創建一個內存設備環境
CDC dcBmp;
if( !dcBmp.CreateCompatibleDC(pDC) ) //創建兼容性的DC
return;
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);
else
pDC->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
? ? ? ? 第二步:建立類向導。查看->建立類導向(Ctrl+W)->CXXXView(類名)->ID_LH_2->COMMAND(Messages)->默認成員函數名。相同方法分別為量化等級2、4、8、16、32、64建立類導向。
? ? ? ? 第三步:在ImageProcessingView.cpp中編輯灰度函數。代碼如下:
? ? ? ??核心流程是打開兩張圖片原圖(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 和 192
if(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);
? ? ? ? }
? ? ? ??運行效果如下圖所示,其中彩色圖片應該先灰度處理再進行其他操作。
?
? ? ?
//
轉載:https://blog.csdn.net/eastmount/article/details/46010637
總結
以上是生活随笔為你收集整理的MFC实现图像灰度、采样和量化功能详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TensorFlow的基本介绍及Hell
- 下一篇: 用S-函数编写Simulink中的正弦模