MFC空間幾何變換之圖像平移、鏡像、旋轉、縮放

本文主要講述基於VC++6.0 MFC圖像處理的應用知識,主要結合自己大三所學課程《數字圖像處理》及課件進行講解,主要通過MFC單文檔視圖實現顯示BMP圖片空間幾何變換,包括圖像平移、圖形旋轉、圖像反轉倒置鏡像和圖像縮放的知識。同時文章比較詳細基礎,沒有采用GDI+獲取矩陣,而是通過讀取BMP圖片信息頭和矩陣像素實現變換,希望該篇文章對你有所幫助,尤其是初學者和學習圖像處理的學生。
       【數字圖像處理】一.MFC詳解顯示BMP格式圖片
       【數字圖像處理】二.MFC單文檔分割窗口顯示圖片
       【數字圖像處理】三.MFC實現圖像灰度、採樣和量化功能詳解
       【數字圖像處理】四.MFC對話框繪製灰度直方圖
       【數字圖像處理】五.MFC圖像點運算之灰度線性變化、灰度非線性變化、閾值化和均衡化處理詳解
        免費資源下載地址:
        http://download.csdn.net/detail/eastmount/8772951

 

一. 圖像平移

 

       前一篇文章講述了圖像點運算(基於像素的圖像變換),這篇文章講述的是圖像幾何變換:在不改變圖像內容的情況下對圖像像素進行空間幾何變換的處理方式。
        點運算對單幅圖像做處理,不改變像素的空間位置;代數運算對多幅圖像做處理,也不改變像素的空間位置;幾何運算對單幅圖像做處理,改變像素的空間位置,幾何運算包括兩個獨立的算法:空間變換算法和灰度級插值算法。

        空間變換操作包括簡單空間變換、多項式卷繞和幾何校正、控制柵格插值和圖像卷繞,這裏主要講述簡單的空間變換,如圖像平移、鏡像、縮放和旋轉。主要是通過線性代數中的齊次座標變換。
        圖像平移座標變換如下:

        運行效果如下圖所示,其中BMP圖片(0,0)像素點爲左下角。


        其代碼核心算法:
        1.在對話框中輸入平移座標(x,y) m_xPY=x,m_yPY=y
        2.定義Place=dlg.m_yPY*m_nWidth*3 表示當前m_yPY行需要填充爲黑色
        3.新建一個像素矩陣 ImageSize=new unsigned char[m_nImage]
        4.循環整個像素矩陣處理 
             for(int i=0 ; i<m_nImage ; i++ ){
                   if(i<Place) {ImageSize[i]=black; continue;} //黑色填充底部 從小往上繪圖
                   else if(i>=Place && countWidth<dlg.m_xPY*3) {//黑色填充左部分
                         ImageSize[i]=black; countWidth++;  continue;
                   }
                   else if(i>=Place && countWidth>=dlg.m_xPY*3) {//圖像像素平移區域
                        ImageSize[i]=m_pImage[m_pImagePlace];//原(0,0)像素賦值過去
                        m_pImagePlace++; countWidth++;
                        if(countWidth==m_nWidth*3) { //一行填滿 m_pImagePlace走到(0,1)
                              number++; m_pImagePlace=number*m_nWidth*3;
                        }
                   }
             }
         5.寫文件繪圖fwrite(ImageSize,m_nImage,1,fpw)

        第一步:在ResourceView資源視圖中,添加Menu子菜單如下:(注意ID號)

        第二步:設置平移對話框。將試圖切換到ResourceView界面--選中Dialog,右鍵鼠標新建一個Dialog,並新建一個名爲IDD_DIALOG_PY。編輯框(X)IDC_EDIT_PYX 和 (Y)IDC_EDIT_PYY,確定爲默認按鈕。設置成下圖對話框:

        第三步:在對話框資源模板空白區域雙擊鼠標—Create a new class創建一個新類--命名爲CImagePYDlg。會自動生成它的.h和.cpp文件。打開類嚮導(Ctrl W),選擇類名:CImagePYDlg添加成員變量如下圖所示,同時在Message Maps中生成ID_JHBH_PY實現函數。

 
        第四步:在CImageProcessingView.cpp中添加頭文件#include "ImagePYDlg.h",並實現平移。


 
  1. /********************************************************/

  2. /* 圖像空間幾何變換:圖像平移 ID_JHBH_PY(幾何變換-平移)

  3. /* 使用平移對話框:CImagePYDlg dlg

  4. /* 算法:f(x,y)=f(x+x0,y+y0)圖像所有點平移,空的補黑'0'

  5. /* 注意該圖像平移方法只是從左上角(0,0)處開始平移

  6. /* 其他方向原理相同 自己去實現

  7. /********************************************************/

  8.  
  9. void CImageProcessingView::OnJhbhPy()

  10. {

  11. if(numPicture==0) {

  12. AfxMessageBox("載入圖片後才能空間平移!",MB_OK,0);

  13. return;

  14. }

  15. //定義採樣對話框也是用來空間變換平移的座標

  16. CImagePYDlg dlg;

  17. if( dlg.DoModal()==IDOK ) //顯示對話框

  18. {

  19. //採樣座標最初爲圖片的自身像素

  20. if( dlg.m_xPY>m_nWidth || dlg.m_yPY>m_nHeight ) {

  21. AfxMessageBox("圖片平移不能爲超過原圖長寬!",MB_OK,0);

  22. return;

  23. }

  24. AfxMessageBox("圖片空間變換-平移!",MB_OK,0);

  25.  
  26. //打開臨時的圖片 讀寫文件

  27. FILE *fpo = fopen(BmpName,"rb");

  28. FILE *fpw = fopen(BmpNameLin,"wb+");

  29. fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);

  30. fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);

  31. fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);

  32. fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);

  33. fread(m_pImage,m_nImage,1,fpo);

  34.  
  35. /************************************************************/

  36. /* 圖片空間變換-平移

  37. /* 座標(dlg.m_xPY,dlg.m_yPY)表示圖像平移的座標

  38. /* 先用Plave計算出平移後的起始座標,其他的座標賦值爲'0'黑色

  39. /* 然後依次平移座標,空的賦爲黑色,否則填充

  40. /************************************************************/

  41.  
  42. /******************************************************************/

  43. /* 嚴重錯誤1:數組變量賦值相等

  44. /* 在View.h中定義變量 BYTE *m_pImage 讀入圖片數據後的指針

  45. /* 建立臨時變量數組,讓它平移變換 unsigned char *ImageSize

  46. /* ImageSize=m_pImage(錯誤)

  47. /* 會導致ImageSize賦值變換時m_pImage也產生了變換,所以輸出全爲黑色

  48. /* 因爲它倆指向了相同的數組地址

  49. /* 解決方法:使用下面C++的new方法動態分配或for循環i=m_nImage賦值

  50. /******************************************************************/

  51.  
  52. /*臨時變量存儲的像素與m_pImage相同,便於處理圖像*/

  53. unsigned char *ImageSize;

  54. ImageSize=new unsigned char[m_nImage]; //new和delete有效的進行動態內存的分配和釋放

  55.  
  56. int Place; //建立臨時座標 記錄起始座標(0,0)平移過來的位置

  57. int m_pImagePlace; //原始圖像平移爲(0,0) 圖像把它平移到Place位置

  58. unsigned char black; //填充黑色='0'

  59.  
  60. /************************************************************/

  61. /* for(int i=0 ; i<m_nHeight ; i++ )

  62. /* for(int j=0 ; j<m_nWidth ; j++ )

  63. /* 不能使用的上面的因爲可能圖像的最後一行沒有完整的一行像素

  64. /* 這樣會出現exe報錯,使用m_nImage讀寫所有像素比較正確

  65. /************************************************************/

  66.  
  67. Place=dlg.m_yPY*m_nWidth*3; //前m_yPY行都要填充爲黑色

  68. black=0; //顏色爲黑色

  69. m_pImagePlace=0; //圖像處事位置爲(0,0),把該點像素平移過去

  70. int countWidth=0; //記錄每行的像素個數,滿行時變回0

  71. int number=0; //數字記錄使用的像素行數,平移時使用

  72.  
  73. for(int i=0 ; i<m_nImage ; i++ )

  74. {

  75. /*如果每行的像素填滿時清爲0*/

  76. if(countWidth==m_nWidth*3) {

  77. countWidth=0;

  78. }

  79.  
  80. /*第一部分:到平移後像素位置前面的所有像素點賦值爲黑色*/

  81. if(i<Place) {

  82. ImageSize[i]=black; //賦值爲黑色

  83. continue;

  84. }

  85.  
  86. /*第二部分:平移區域的左邊部分賦值爲黑色*/

  87. else if(i>=Place && countWidth<dlg.m_xPY*3) { //RGB乘3

  88. ImageSize[i]=black; //賦值爲黑色

  89. countWidth++;

  90. continue;

  91. }

  92.  
  93. /****************************/

  94. /* 各部分如圖所示:

  95. /* 000000000000000000000000

  96. /* 000000000000000000000000

  97. /* 0000000.................

  98. /* 0000000.................

  99. /* 0000000.................

  100. /* 0000000.................

  101. /* 點表示像素部分,0爲黑色

  102. /****************************/

  103.  
  104. /* 重點錯誤提示:由於bmp圖像顯示是從左下角開始存儲(0,0)點所以輸出圖像爲 */

  105. /* bmp圖像是從左下角到右上角排列的 */

  106.  
  107. /****************************/

  108. /* 各部分如圖所示:

  109. /* 0000000.................

  110. /* 0000000.................

  111. /* 0000000.................

  112. /* 0000000.................

  113. /* 000000000000000000000000

  114. /* 000000000000000000000000

  115. /* 點表示像素部分,0爲黑色

  116. /****************************/

  117.  
  118. /*第三部分:圖像像素平移區域*/

  119. else if(i>=Place && countWidth>=dlg.m_xPY*3)

  120. {

  121. ImageSize[i]=m_pImage[m_pImagePlace];

  122. m_pImagePlace++;

  123. countWidth++;

  124. if(countWidth==m_nWidth*3)

  125. {

  126. number++;

  127. m_pImagePlace=number*m_nWidth*3;

  128. }

  129. }

  130. }

  131.  
  132. fwrite(ImageSize,m_nImage,1,fpw);

  133. fclose(fpo);

  134. fclose(fpw);

  135. numPicture = 2;

  136. level=200; //200表示幾何變換

  137. Invalidate();

  138. }

  139. }

        同時在ShowBitmap中添加level標記重新繪製圖片,代碼如下:


 
  1. else //圖像幾何變換

  2. if(level=200)

  3. {

  4. m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,

  5. LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);

  6. }

       運行時需要注意一點:BMP圖像在處理過程中可能會出現一些斜線,而平移(40,60)位移量時可能出現如下。他是因爲BMP格式有個非常重要的規定,要求每一掃描的字節數據必須能被4整除,也就是Dword對齊(長度4字節),如果圖像的一行字節數不能被4整除,就需要在每行末尾不起0達到標準。
        例如一行像素爲97字節,我們就需要補3個字節嗎,數值可以是0,但是我們在BMP格式的信息頭裏說明了其寬度,所以補齊後對我們沒有影響,所以後面補若干個字節的0即可直到被4整除。
 
        通過後面的圖像縮放後,我從學做了一遍這個補齊的縮放。代碼如下,能夠實現完美平移。nice啊~


 
  1. void CImageProcessingView::OnJhbhPy()

  2. {

  3. if(numPicture==0) {

  4. AfxMessageBox("載入圖片後才能空間平移!",MB_OK,0);

  5. return;

  6. }

  7. //定義採樣對話框也是用來空間變換平移的座標

  8. CImagePYDlg dlg;

  9. if( dlg.DoModal()==IDOK ) //顯示對話框

  10. {

  11. //採樣座標最初爲圖片的自身像素

  12. if( dlg.m_xPY>m_nWidth || dlg.m_yPY>m_nHeight ) {

  13. AfxMessageBox("圖片平移不能爲超過原圖長寬!",MB_OK,0);

  14. return;

  15. }

  16. AfxMessageBox("圖片空間變換-平移!",MB_OK,0);

  17.  
  18. //打開臨時的圖片 讀寫文件

  19. FILE *fpo = fopen(BmpName,"rb");

  20. FILE *fpw = fopen(BmpNameLin,"wb+");

  21. fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);

  22. fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);

  23.  
  24. int num; //記錄每行多餘的圖像素數個數

  25. int sfSize; //補齊後的圖像大小

  26. //重點:圖像的每行像素都必須是4的倍數:1*1的圖像爲 r g b 00H

  27. if(m_nWidth*3%4!=0)

  28. {

  29. num=(4-m_nWidth*3%4);

  30. sfSize=(m_nWidth*3+num)*m_nHeight; //每行多number個

  31. }

  32. else

  33. {

  34. num=0;

  35. sfSize=m_nWidth*m_nHeight*3;

  36. }

  37. //注意:假如最後一行像素不足,我默認處理爲完整的一行,不足補00H

  38. //總之處理後的圖像總是m*n且爲4倍數,每行都完整存在

  39.  
  40. /*更改文件頭信息 定義臨時文件頭結構變量*/

  41. BITMAPFILEHEADER bfhsf;

  42. BITMAPINFOHEADER bihsf;

  43. bfhsf=bfh;

  44. bihsf=bih;

  45. bfhsf.bfSize=sfSize+54;

  46. fwrite(&bfhsf,sizeof(BITMAPFILEHEADER),1,fpw);

  47. fwrite(&bihsf,sizeof(BITMAPINFOHEADER),1,fpw);

  48. fread(m_pImage,m_nImage,1,fpo);

  49.  
  50. CString str;

  51. str.Format("補齊=%d",num);

  52. AfxMessageBox(str);

  53.  
  54. /*臨時變量存儲的像素與sfSize相同 new和delete有效的進行動態內存的分配和釋放*/

  55. unsigned char *ImageSize;

  56. ImageSize=new unsigned char[sfSize];

  57.  
  58. int Place; //建立臨時座標 記錄起始座標(0,0)平移過來的位置

  59. int m_pImagePlace; //原始圖像平移爲(0,0) 圖像把它平移到Place位置

  60. unsigned char black=0; //填充黑色='0'

  61. unsigned char other=0; //補碼00H='\0'

  62.  
  63. Place=dlg.m_yPY*(m_nWidth*3+num); //前m_yPY行都要填充爲黑色

  64. m_pImagePlace=0; //圖像處事位置爲(0,0),把該點像素平移過去

  65. int countWidth=0; //記錄每行的像素個數,滿行時變回0

  66. int number=0; //數字記錄使用的像素行數,平移時使用

  67.  
  68. for(int i=0 ; i<sfSize ; i++ )

  69. {

  70. /*第一部分:到平移後像素位置前面的所有像素點賦值爲黑色*/

  71. if(i<Place)

  72. {

  73. ImageSize[i]=black; //賦值爲黑色

  74. continue;

  75. }

  76.  
  77. /*第二部分:平移區域的左邊部分賦值爲黑色*/

  78. else if(i>=Place && countWidth<dlg.m_xPY*3) //RGB乘3

  79. {

  80. ImageSize[i]=black; //賦值爲黑色

  81. countWidth++;

  82. continue;

  83. }

  84.  
  85. /*第三部分:圖像像素平移區域*/

  86. else if(i>=Place && countWidth>=dlg.m_xPY*3)

  87. {

  88. ImageSize[i]=m_pImage[m_pImagePlace];

  89. m_pImagePlace++;

  90. countWidth++;

  91. if(countWidth==m_nWidth*3)

  92. {

  93. if(num==0)

  94. {

  95. countWidth=0;

  96. number++;

  97. m_pImagePlace=number*m_nWidth*3;

  98. }

  99. else //num爲補0

  100. {

  101. for(int j=0;j<num;j++)

  102. {

  103. i++;

  104. ImageSize[i]=other;

  105. }

  106. countWidth=0;

  107. number++;

  108. m_pImagePlace=number*(m_nWidth*3+num); //重點:添加Num

  109. }

  110. }

  111. }

  112. }

  113.  
  114. fwrite(ImageSize,sfSize,1,fpw);

  115. fclose(fpo);

  116. fclose(fpw);

  117. numPicture = 2;

  118. level=200; //200表示幾何變換

  119. Invalidate();

  120. }

  121. }

        運行效果如下圖所示,完美平移,其他算法遇到斜線問題類似補齊即可。




 

 

二. 圖像鏡像

1.水平鏡像翻轉
        其變換矩陣如下:
                                 X=width-X0-1   (width爲圖像寬度)
                                 Y=Y0
        打開類嚮導,在CImageProcessingView中添加IDs爲ID_JHBH_FZ,生成函數,代碼如下:


 
  1. /* 幾何變換 圖像翻轉:自己對這個功能比較感興趣,做個圖像反轉 */

  2. void CImageProcessingView::OnJhbhFz()

  3. {

  4. if(numPicture==0) {

  5. AfxMessageBox("載入圖片後才能空間反轉!",MB_OK,0);

  6. return;

  7. }

  8. AfxMessageBox("圖片空間變換-反轉圖像!",MB_OK,0);

  9.  
  10. //打開臨時的圖片

  11. FILE *fpo = fopen(BmpName,"rb");

  12. FILE *fpw = fopen(BmpNameLin,"wb+");

  13. fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);

  14. fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);

  15. fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);

  16. fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);

  17. fread(m_pImage,m_nImage,1,fpo);

  18.  
  19. /*new和delete有效的進行動態內存的分配和釋放*/

  20. unsigned char *ImageSize;

  21. ImageSize=new unsigned char[m_nImage];

  22. int countWidth=0; //記錄每行的像素個數,滿行時變回0

  23. int Place; //記錄圖像每行的位置,便於圖像反轉

  24. int number=0; //數字記錄使用的像素行數

  25. Place=m_nWidth*3-1;

  26.  
  27. //翻轉矩陣: y=y0 x=width-x0-1

  28. for(int i=0 ; i<m_nImage ; i++ )

  29. {

  30. if(countWidth==m_nWidth*3)

  31. {

  32. countWidth=0;

  33. }

  34. ImageSize[i]=m_pImage[Place]; //(0,0)賦值(0,width*3-1)像素

  35. Place--;

  36. countWidth++;

  37. if(countWidth==m_nWidth*3)

  38. {

  39. number++;

  40. Place=number*m_nWidth*3-1;

  41. }

  42. }

  43.  
  44. fwrite(ImageSize,m_nImage,1,fpw);

  45. fclose(fpo);

  46. fclose(fpw);

  47. numPicture = 2;

  48. level=200;

  49. Invalidate();

  50. }

        運行效果如下圖所示,其中還是存在一些小BUG,如前面的BMP圖補0湊齊4整數倍寬度或顏色失幀。

 




2.垂直鏡像倒轉
        其中變換矩陣如下:
                                      X=X0
                                      Y=height-Y0-1   (height爲圖像高度)
        它相當於把原圖的像素矩陣的最後一行像素值賦值給第一行,首先找到(0,0)對應的(height-1,0)像素值,然後依次賦值該行的像素數據;最後當前行賦值結束,依次下一行。重點是找到每行的第一個像素點即可。
        代碼中引用兩個變量:Place=(m_nWidth*3)*(m_nHeight-1-1)即是(height-1,0)最後一行的第一個像素點;然後是循環中Place=(m_nWidth*3)*(m_nHeight-number-1)找到每行的第一個像素點。

        同樣通過類嚮導生成函數void CImageProcessingView::OnJhbhDz(),代碼如下:


 
  1. /* 幾何變換 圖像倒轉 */

  2. void CImageProcessingView::OnJhbhDz()

  3. {

  4. if(numPicture==0) {

  5. AfxMessageBox("載入圖片後才能空間反轉!",MB_OK,0);

  6. return;

  7. }

  8. AfxMessageBox("圖片空間變換-反轉圖像!",MB_OK,0);

  9.  
  10. //打開臨時的圖片

  11. FILE *fpo = fopen(BmpName,"rb");

  12. FILE *fpw = fopen(BmpNameLin,"wb+");

  13. fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);

  14. fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);

  15. fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);

  16. fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);

  17. fread(m_pImage,m_nImage,1,fpo);

  18.  
  19. /*new和delete有效的進行動態內存的分配和釋放*/

  20. unsigned char *ImageSize;

  21. ImageSize=new unsigned char[m_nImage];

  22. int countWidth=0; //記錄每行像素個數,滿行時變回0

  23. int Place; //每列位置

  24. int number=0; //像素行數

  25. Place=(m_nWidth*3)*(m_nHeight-1-1); //0行存儲

  26.  
  27. //翻轉矩陣: x=x0 y=height-y0-1

  28. for(int i=0 ; i<m_nImage ; i++ )

  29. {

  30. ImageSize[i]=m_pImage[Place]; //(0,0)賦值(0,0)像素

  31. Place++;

  32. countWidth++;

  33. if(countWidth==m_nWidth*3)

  34. {

  35. countWidth=0;

  36. number++;

  37. Place=(m_nWidth*3)*(m_nHeight-number-1);

  38. }

  39. }

  40.  
  41. fwrite(ImageSize,m_nImage,1,fpw);

  42. fclose(fpo);

  43. fclose(fpw);

  44. numPicture = 2;

  45. level=200;

  46. Invalidate();

  47. }

        運行結果如下圖所示,第二張圖顏色沒有失幀或變灰,這完全可以懷疑在翻轉過程中RGB像素編程BGR後導致的結果,最終實現了翻轉圖像,但灰度存在一定;所以如果改爲RBG順序不變化即可原圖顏色顯示。




 


 

 

三. 圖像旋轉

        圖像饒原點旋轉順時針theta角矩陣變換如下:注意BMP圖像(0,0)左下角


        寫到這裏真心覺得寫底層的代碼非常困難啊!尤其是以爲像素轉換二維像素,同時也覺得當時的自己算法部分還是很強大的,也感覺到如果採用GDI+操作像素矩陣Matrix或ColorMatrix是多麼的方便,因爲它定義好了X和Y向量,這就是爲什麼Android前面寫的圖像處理要容易得多。但是效率高~
        好像利用GDI+旋轉通過幾句代碼即可:
        matrix.Rotate(15); //矩陣旋轉15度
        graph.SetTransform(&matrix);
        graph.DrawImage(&image,points,3);
        下面這部分代碼是實現Android旋轉的:參考我的博客


 
  1. //旋轉圖片

  2. private void TurnPicture() {

  3. Matrix matrix = new Matrix();

  4. turnRotate=turnRotate+15;

  5. //選擇角度 饒(0,0)點選擇 正數順時針 負數逆時針 中心旋轉

  6. matrix.setRotate(turnRotate,bmp.getWidth()/2,bmp.getHeight()/2);

  7. Bitmap createBmp = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());

  8. Canvas canvas = new Canvas(createBmp);

  9. Paint paint = new Paint();

  10. canvas.drawBitmap(bmp, matrix, paint);

  11. imageCreate.setBackgroundColor(Color.RED);

  12. imageCreate.setImageBitmap(createBmp);

  13. textview2.setVisibility(View.VISIBLE);

  14. }

        實現效果如下圖所示:


        言歸正傳,新建Dialog如下圖所示,設置ID_DIALOG_XZ和變量:

        再點擊空白處創建CImageXZDlg類(旋轉),它會自動生成.h和.cpp文件。打開類嚮導生成CImageXZDlg類的成員變量m_xzds(旋轉度數),並設置其爲int型(最大值360 最小值0)。
        在類嚮導(Ctrl+W)選擇類CImageProcessingView,爲ID_JHBH_TXXZ(圖像旋轉)添加函數,同時添加頭文件#include "ImageXZDlg.h"


 
  1. /**********************************************************/

  2. /* 幾何變換:圖片旋轉

  3. /* 先添加對話框:IDD_JHBH_TXXZ(圖像旋轉),創建新類CImageXZDlg

  4. /* 創建輸入度數的:m_xzds Member variables 爲int 0-360間

  5. /**********************************************************/

  6.  
  7. void CImageProcessingView::OnJhbhTxxz()

  8. {

  9. if(numPicture==0) {

  10. AfxMessageBox("載入圖片後才能空間旋轉!",MB_OK,0);

  11. return;

  12. }

  13.  
  14. //定義對話框並調用對話框

  15. CImageXZDlg dlg;

  16. if( dlg.DoModal()==IDOK ) //顯示對話框

  17. {

  18. AfxMessageBox("圖片空間變換-旋轉圖像!",MB_OK,0);

  19. //讀寫文件

  20. FILE *fpo = fopen(BmpName,"rb");

  21. FILE *fpw = fopen(BmpNameLin,"wb+");

  22. fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);

  23. fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);

  24. fwrite(&bfh,sizeof(BITMAPFILEHEADER),1,fpw);

  25. fwrite(&bih,sizeof(BITMAPINFOHEADER),1,fpw);

  26. fread(m_pImage,m_nImage,1,fpo);

  27.  
  28. /*new和delete有效的進行動態內存的分配和釋放*/

  29. unsigned char *ImageSize;

  30. ImageSize=new unsigned char[m_nImage];

  31. int Place; //記錄圖像每行的位置,便於圖像旋轉

  32.  
  33. /*定義PA=3.14時使用的方法是arcsin(1.0/2)*6即爲π*/

  34. double PA;

  35. PA=asin(0.5)*6;

  36.  
  37. /*把輸入的0-360的正整數度數轉換爲角度,30度=π/6*/

  38. double degree;

  39. degree=PA*dlg.m_xzds/180; //調用dlg.m_xzds(旋轉度數)

  40.  
  41. //對應的二維矩陣 注意圖像矩陣從左下角開始處理 它最終要轉換成一維存儲

  42. int X,Y; //圖像變換前通過一維矩陣轉換爲二維

  43. int XPlace,YPlace;

  44.  
  45. //輸出轉換爲的角度

  46. CString str;

  47. str.Format("轉換後的角度=%f",degree);

  48. AfxMessageBox(str);

  49.  
  50. //圖像旋轉處理

  51. for(int i=0 ; i<m_nImage ; i++ )

  52. {

  53. //原圖:一維矩陣轉換爲二維矩陣

  54. X=(i/3)%m_nWidth;

  55. Y=(i/3)/m_nWidth;

  56. //注意錯誤:X=i/m_nHeight Y=i%m_nWidth; 只輸出最後1/3

  57.  
  58. //圖像旋轉爲:a(x,y)=x*cos-y*sin b(x,y)=x*sin+y*cos

  59. XPlace=(int)(X*cos(degree)-Y*sin(degree));

  60. YPlace=(int)(X*sin(degree)+Y*cos(degree));

  61.  
  62. //在轉換爲一維圖想輸出

  63. if( (XPlace>=0 && XPlace<=m_nWidth) && (YPlace>=0 && YPlace<=m_nHeight) )

  64. {

  65. Place=YPlace*m_nWidth*3+XPlace*3;

  66. //在圖像範圍內賦值爲該像素

  67. if(Place+2<m_nImage)

  68. {

  69. ImageSize[i]=m_pImage[Place];

  70. i++;

  71. ImageSize[i]=m_pImage[Place+1];

  72. i++;

  73. ImageSize[i]=m_pImage[Place+2];

  74. }

  75. //否則賦值爲黑色

  76. else

  77. {

  78. ImageSize[i]=0;

  79. i++;

  80. ImageSize[i]=0;

  81. i++;

  82. ImageSize[i]=0;

  83. }

  84. }

  85. //否則賦值爲黑色

  86. else

  87. {

  88. ImageSize[i]=0;

  89. i++;

  90. ImageSize[i]=0;

  91. i++;

  92. ImageSize[i]=0;

  93. }

  94. }

  95.  
  96. fwrite(ImageSize,m_nImage,1,fpw);

  97. fclose(fpo);

  98. fclose(fpw);

  99. numPicture = 2;

  100. level=200; //幾何變換

  101. Invalidate();

  102. }

  103. }

        運行效果如下圖所示,中心旋轉太難了!找到中心那個位置就不太容易,我做不下去了,fuck~同時旋轉過程中,由於是饒左下角(0,0)實現,故有的角度會到界面外顯示全黑。下圖分別旋轉15度和355度。




 


 

 

四. 圖像縮放

        圖像縮放主要有兩種方法:
        1.最近鄰插值:向後映射時,輸出圖像的灰度等於離它所映射位置最近的輸入圖像的灰度值。其中向前映射和向後映射如下:

 

        對於向前映射每個輸出圖像的灰度要經過多次運算,對於向後映射,每個輸出圖像的灰度只經過一次運算。在實際應用中,更多的是採用向後映射法,其中根據四個相鄰像素灰度值計算某個位置的像素灰度值即爲灰度級插值。
        2.雙線性插值:四點確定一個平面函數,屬於過約束問題。即單位正方形頂點已知,求正方形內任一點的f(x,y)值。


        換個通熟的說法,如下圖所示。採用最近鄰插值法就是P(x,y)像素值採用四捨五入等於離它最近的輸入圖像像素值。分別計算它到四個頂點之間的距離,但是這樣會造成圖像的馬賽克、鋸齒等現象。而採用雙線性插值法,主要通過該座標周圍的四個像素值,按照比例混合計算器近似值。比例混合的依據是離哪個像素近,哪個像素的比例越大。



        下面是採用最近鄰插值法的過程,注意BMP圖縮放還需修改頭文件信息。
        第一步:在資源視圖中添加“圖像縮放”Dialog

        第二步:點擊空白處創建對話框的類CImageSFDlg,同時打開類嚮導爲其添加成員變量m_sfbs(縮放倍數),其爲int型在0-200之間。



        第三步:打開類嚮導爲其添加成員函數void CImageProcessingView::OnJhbhSf() 並實現縮放。同時添加頭文件#include "ImageSFDlg.h"。


 
  1. /*******************************************************************/

  2. /* ID_JHBH_SF: 幾何運算-縮放-最近鄰插值算法

  3. /* 算法思想:輸出圖像的灰度等於離它所映射位置最近的輸入圖像的灰度值

  4. /* 先計算出放大縮小後的長寬,根據它計算找原圖中的點灰度,四捨五入

  5. /*******************************************************************/

  6.  
  7. void CImageProcessingView::OnJhbhSf()

  8. {

  9. if(numPicture==0) {

  10. AfxMessageBox("載入圖片後才能幾何縮放圖像!",MB_OK,0);

  11. return;

  12. }

  13.  
  14. CImageSFDlg dlg; //定義縮放對話框

  15. if( dlg.DoModal()==IDOK )

  16. {

  17. //採樣座標最初爲圖片的自身像素 m_sfbs(縮放倍數)

  18. if( dlg.m_sfbs==0 ) {

  19. AfxMessageBox("輸入圖片縮放倍數不能爲0!",MB_OK,0);

  20. return;

  21. }

  22.  
  23. FILE *fpo = fopen(BmpName,"rb");

  24. FILE *fpw = fopen(BmpNameLin,"wb+");

  25. fread(&bfh,sizeof(BITMAPFILEHEADER),1,fpo);

  26. fread(&bih,sizeof(BITMAPINFOHEADER),1,fpo);

  27.  
  28. /*先求縮放後的長寬*/

  29. int sfWidth,sfHeight; //縮放後的長寬

  30. int sfSize; //縮放後的圖像大小

  31. sfWidth=(int)(m_nWidth*(dlg.m_sfbs*1.0)/100); //24位圖像RGB必須是3倍數 循環讀取時爲RGB

  32. sfHeight=(int)(m_nHeight*(dlg.m_sfbs*1.0)/100);

  33. int number; //記錄每行多餘的圖像素數個數

  34.  
  35. //重點:圖像的每行像素都必須是4的倍數:1*1的圖像爲 r g b 00H

  36. if(sfWidth*3%4!=0) {

  37. number=(4-sfWidth*3%4);

  38. sfSize=(sfWidth*3+(4-sfWidth*3%4))*sfHeight;

  39. }

  40. else {

  41. number=0;

  42. sfSize=sfWidth*sfHeight*3;

  43. }

  44. //注意:假如最後一行像素不足,我默認處理爲完整的一行,不足補00H

  45. //總之處理後的圖像總是m*n且爲4倍數,每行都完整存在

  46.  
  47. /*更改文件頭信息 定義臨時文件頭結構變量*/

  48. BITMAPFILEHEADER bfhsf;

  49. BITMAPINFOHEADER bihsf; //縮放(sf)

  50. bfhsf=bfh;

  51. bihsf=bih;

  52.  
  53. bfhsf.bfSize=sfSize+54;

  54. bihsf.biWidth=sfWidth;

  55. bihsf.biHeight=sfHeight;

  56.  
  57. //顯示部分m_nDrawWidth<650顯示原圖,否則顯示

  58. flagSF=1; //圖像縮放爲1標識變量

  59. m_nDrawWidthSF=sfWidth;

  60. m_nDrawHeightSF=sfHeight;

  61.  
  62. fwrite(&bfhsf,sizeof(BITMAPFILEHEADER),1,fpw);

  63. fwrite(&bihsf,sizeof(BITMAPINFOHEADER),1,fpw);

  64.  
  65. fread(m_pImage,m_nImage,1,fpo);

  66.  
  67. unsigned char red,green,blue;

  68. unsigned char other=0; //補碼00H='\0'

  69. int placeX; //記錄在原圖中的第幾行的位置

  70. int placeY; //記錄在原圖中的位置(x,y)

  71. int placeBH; //記錄變換後在變換圖中的位置

  72.  
  73. /*new和delete有效的進行動態內存的分配和釋放*/

  74. unsigned char *ImageSize;

  75. ImageSize=new unsigned char[sfSize];

  76.  
  77. /*讀取文件像素信息 縮放注意:1.找最近灰度 2.四捨五入法(算法+0.5)*/

  78. for(int i=0; i<sfHeight ; i++ ) //行

  79. {

  80. placeX=(int)(i/(dlg.m_sfbs*1.0/100)+0.5)*bih.biWidth*3;

  81. for(int j=0; j<sfWidth ; j++ ) //列

  82. {

  83. red=green=blue=0;

  84. //放大倍數爲(dlg.m_sfbs*1.0/100)

  85. placeY=placeX+(int)(j/(dlg.m_sfbs*1.0/100)+0.5)*3;

  86. //重點是:number*i補充00H,如果是numer圖像會被切成2塊

  87. placeBH=(i*sfWidth*3+number*i)+j*3;

  88. if(placeY+2<m_nImage)

  89. {

  90. ImageSize[placeBH]=m_pImage[placeY];

  91. ImageSize[placeBH+1]=m_pImage[placeY+1];

  92. ImageSize[placeBH+2]=m_pImage[placeY+2];

  93. }

  94. else

  95. {

  96. ImageSize[placeBH]=0;

  97. ImageSize[placeBH+1]=0;

  98. ImageSize[placeBH+2]=0;

  99. }

  100. }

  101. }

  102.  
  103. fwrite(ImageSize,sfSize,1,fpw);

  104. fclose(fpo);

  105. fclose(fpw);

  106. numPicture = 2;

  107. level=200;

  108. Invalidate();

  109. }

  110. }

        第四步:因爲圖像縮放修改BMP圖片頭信息,所以需要修改ShowBitmap中的顯示第二張圖片時的部分代碼。如下所示:添加變量flagSF、m_nDrawWidthSF和m_nDrawHeightSF。


 
  1. /*定義顯示圖像縮放時的長寬與標記*/

  2. int flagSF=0; //圖像幾何變換縮放變換

  3. int m_nDrawWidthSF=0; //圖像顯示寬度縮放後

  4. int m_nDrawHeightSF=0; //圖像顯示高度縮放後

  5.  
  6. //****************顯示BMP格式圖片****************//

  7. void CImageProcessingView::ShowBitmap(CDC *pDC, CString BmpName)

  8. {

  9. ......

  10. else //圖像幾何變換

  11. if(level=200)

  12. {

  13. m_hBitmapChange = (HBITMAP) LoadImage(NULL,BmpNameLin,IMAGE_BITMAP,0,0,

  14. LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);

  15. }

  16.  
  17.  
  18. if( m_bitmap.m_hObject ) {

  19. m_bitmap.Detach(); //m_bitmap爲創建的位圖對象

  20. }

  21. m_bitmap.Attach(m_hBitmapChange);

  22. //定義並創建一個內存設備環境

  23. CDC dcBmp;

  24. if( !dcBmp.CreateCompatibleDC(pDC) ) //創建兼容性的DC

  25. return;

  26. BITMAP m_bmp; //臨時bmp圖片變量

  27. m_bitmap.GetBitmap(&m_bmp); //將圖片載入位圖中

  28. CBitmap *pbmpOld = NULL;

  29. dcBmp.SelectObject(&m_bitmap); //將位圖選入臨時內存設備環境

  30.  
  31. //圖片顯示調用函數StretchBlt

  32. if(flagSF==1)

  33. {

  34. CString str;

  35. str.Format("縮放長=%d 寬%d 原圖長=%d 寬=%d",m_nDrawWidthSF,

  36. m_nDrawHeightSF,m_nWidth,m_nHeight);

  37. AfxMessageBox(str);

  38. flagSF=0;

  39. //m_nDrawWidthSF縮放此存見函數最近鄰插值法中賦值

  40. if(m_nDrawWidthSF<650 && m_nDrawHeightSF<650)

  41. pDC->StretchBlt(m_nWindowWidth-m_nDrawWidthSF,0,

  42. m_nDrawWidthSF,m_nDrawHeightSF,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);

  43. else

  44. pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,

  45. m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY); //顯示大小爲640*640

  46. }

  47. else {

  48. //如果圖片太大顯示大小爲固定640*640 否則顯示原圖大小

  49. if(m_nDrawWidth<650 && m_nDrawHeight<650)

  50. pDC->StretchBlt(m_nWindowWidth-m_nDrawWidth,0,

  51. m_nDrawWidth,m_nDrawHeight,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);

  52. else

  53. pDC->StretchBlt(m_nWindowWidth-640,0,640,640,&dcBmp,0,0,

  54. m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);

  55. }

  56. //恢復臨時DC的位圖

  57. dcBmp.SelectObject(pbmpOld);

  58. }

        運行效果如下圖所示,採用最近鄰插值法縮放大了會出現失幀。

 


 


        但是同時當圖片縮小是總是報錯,圖片縮放確實有點難,因爲像素需要補齊4整數倍,同時需要修改消息頭,同時像素矩陣的變換都非常複雜。

 

        

//////////////////////////////////////////////////////////////////

轉載:https://blog.csdn.net/eastmount/article/details/46345299

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章