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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/492329.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

超级干货:一文看懂5G产业链及投资机会

来源&#xff1a;新材料在线摘要&#xff1a;本文将讲述5G行业概况、产业链结构、上游关键原材料、本行业竞争格局及材料重点应用领域。报告合集涵盖5G关键材料、5G天线、氮化镓半导体、导热材料、电磁屏蔽材料、高频覆铜板基材、微波介质陶瓷、先进封装、手机外壳等九大市场研…

MFC图像增强之图像普通平滑、高斯平滑、Laplacian、Sobel、Prewitt锐化

本文主要讲述基于VC6.0 MFC图像处理的应用知识&#xff0c;主要结合自己大三所学课程《数字图像处理》及课件进行讲解&#xff0c;主要通过MFC单文档视图实现显示BMP图像增强处理&#xff0c;包括图像普通平滑、高斯平滑、不同算子的图像锐化知识。希望该篇文章对你有所帮助&am…

南京大学教授施斌及其团队—— 光纤变“神经” 大地能感知

来源&#xff1a;人民日报你能相信吗&#xff1f;一根头发丝粗细的光纤&#xff0c;根据不同地质环境和多场监测要求&#xff0c;穿上各种“定制”的外衣&#xff0c;就能变身敏感强健的“大地感知神经”&#xff0c;使得大地一有灾害异动&#xff0c;远在千里之外的监测系统就…

MFC详解显示BMP格式图片

本文主要是讲述《数字图像处理》系列栏目中的第一篇文章.主要详细介绍了BMP图片格式,同时使用C和MFC显示BMP格式,主要结合自己的《数字图像处理》课程和以前的项目叙述讲解. 一.BMP图片格式定义 BMP文件格式是Windows操作系统推荐和支持的标准图像文件格式,是一种将内存或显示…

0pencv——图像腐蚀

1、代码如下&#xff1a; #include "stdafx.h" #include <opencv2/opencv.hpp>using namespace cv;int main() {Mat srcImage imread("小狗1.jpg");imshow("显示图像", srcImage);Mat element getStructuringElement(MORPH_RECT, Size(…

腾讯研究院发布:《人工智能+制造产业发展研究》报告

来源&#xff1a;腾讯研究院摘要&#xff1a;工业革命以后的“自动化”概念追求的是机器自动生产&#xff0c;本质是“机器替人”&#xff0c;强调在完全不需要人的情况下进行不间断的大规模机器生产&#xff1b;而“智能化”追求的是机器的柔性生产&#xff0c;本质是“人机协…

Opencv——图像模糊

1、代码如下&#xff1a; #include "stdafx.h" #include <opencv2/opencv.hpp>using namespace cv;int main() {Mat srcImage imread("小狗1.jpg");imshow("原图像", srcImage);Mat dstImage;blur(srcImage, dstImage, Size(5, 5));imsh…

Android开发中依赖注入的应用

什么是依赖注入&#xff1f; 依赖是指一个对象持有其他对象的引用。依赖注入则是将这些依赖对象传递给被依赖对象&#xff0c;而不是被依赖对象自己创建这些对象。 public class MyClass{private AnotherClass mAnotherObject;public MyClass(){mAnotherObject new AnotherCla…

工业富联2018年报来了!上市后首张成绩单大起底

未来智能实验室是人工智能学家与科学院相关机构联合成立的人工智能&#xff0c;互联网和脑科学交叉研究机构。未来智能实验室的主要工作包括&#xff1a;建立AI智能系统智商评测体系&#xff0c;开展世界人工智能智商评测&#xff1b;开展互联网&#xff08;城市&#xff09;云…

Opencv——图像膨胀

1、代码如下&#xff1a; #include "stdafx.h" #include <opencv2/opencv.hpp>using namespace cv;int main() {Mat srcImage imread("小狗1.jpg");imshow("原图像", srcImage);Mat dstImage;Mat element getStructuringElement(MORPH_…

深度学习背后的基础-神经网络揭秘

来源&#xff1a;混沌巡洋舰摘要&#xff1a;最近&#xff0c; 深度学习三杰获得了计算机界最重要的图灵奖&#xff0c; 它们的贡献都集中在对深度学习的根据神经网络的理论突破。 今天我们看到的所有和人工智能有关的伟大成就&#xff0c; 从阿法狗到自动驾驶&#xff0c; 从海…

Opencv——灰度变换、直方图均衡化

1、代码如下&#xff1a; #include "stdafx.h" #include <opencv2/opencv.hpp>using namespace cv;int main() {Mat srcImage imread("lena.bmp");Mat grayImage;cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);Mat dstImage;equalizeHist(grayIm…

并发模型之——共享内存模型(线程与锁)理论篇

这里我们使用Java的线程与锁来解析共享内存模型&#xff1b;做过java开发并且了解线程安全问题的知道&#xff0c;要使某段代码是线程安全的那必须要满足两个条件&#xff1a;内存可见性、原子性&#xff1b; 内存可见性 在JVM规定多个线程进行通讯是通过共享变量进行的&a…

谷歌员工怒了 900人联名抗议 刚成立的AI道德委员会处境尴尬

来源&#xff1a;网易智能谷歌员工又怒了。3月26日&#xff0c;谷歌宣布成立人工智能项目外部顾问委员会&#xff0c;该委员会将与谷歌就面部识别和公平性等人工智能的主要问题进行磋商。争议的焦点在于&#xff0c;谷歌将保守派传统基金会(Heritage Foundation)主席凯科尔斯詹…

Opencv——基于索引表的图像细化

图像细化针对的是二值图像 或者用阀值处理的二值图像。基于索引表的细化算法大致是遍历被二值化图像的边缘&#xff0c;根据边缘点的八连通域情况查找索引表以确定该边缘点是否能够被删除。根据一些细化规则我们可以建立索引表&#xff0c;因此我们的主要工作就是不断地遍历边…

DeepMind推出首个商业产品,30秒内准确诊断眼疾!

来源&#xff1a;Financial Times、智东西编译摘要&#xff1a;这个设备能像最好的医学专家一样&#xff0c;准确地诊断各种眼部疾病。4月1日&#xff0c;谷歌母公司Alphabet旗下位于伦敦的AI部门DeepMind已打造出了可诊断复杂眼部疾病的商业医疗设备原型&#xff0c;这将是Dee…

Opencv——Sobel边缘检测

1、代码如下&#xff1a; #include "stdafx.h" #include <opencv2/opencv.hpp>using namespace cv;int main() {Mat srcImage imread("lena.jpg");Mat dstImage_x, dstImage_y;Sobel(srcImage, dstImage_x, CV_8U, 1, 0);Sobel(srcImage, dstImag…

Head first servlet and jsp学习笔记

学习中遇到的问题&#xff1a;java基础不行&#xff0c;都忘光了。 主要是&#xff1a;继承&#xff0c;接口&#xff0c;多线程&#xff0c;IO。尤其是多线程&#xff0c;在分布式系统中应该使用的比较多 第一章&#xff1a;前言和体系结构 HTTP协议&#xff1a; TCP/IP的上层…

一文解析|首个上榜科创板的机器人企业,江苏北人“闯关记”

来源&#xff1a;机器人大讲堂摘要&#xff1a;随着上交所公布了科创板首批受理上市申请的企业名单&#xff0c;这九家企业的每一家都被拿到放大镜下细细观察&#xff0c;评头论足。而其中&#xff0c;江苏北人作为登上科创版的首家机器人企业似乎受到的关注最多。江苏北人是一…

Matlab——绘制基础曲线

1、代码如下&#xff1a; close all;clear all;clc; %关闭所有图形窗口&#xff0c;清除工作空间所有变量&#xff0c;清空命令行 x0:0.02:10; y1sin(x); y22*sin(x); plot(x,y1,b*:,x,y2,r-); %设置颜色、标记和线型 axis([0 pi 0 2]); %设置坐标轴 title(正弦曲…