经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库/运维 » Windows » 查看文章
BMP图像的旋转-C++实现
来源:cnblogs  作者:量子诗人  时间:2020/11/9 15:52:11  对本文有异议

最近数字图像处理课要求用C++处理BMP图像,我很无语,有大好的matlab不用。。。。

但是,利用C++去写的话确实会对原理和codeing水平有些帮助,所以认真写了。。

实验环境:windows10+Clion+MinGW64

参考资料:https://blog.csdn.net/qq_36752072/article/details/78151770

本工程所使用的头文件:

  1. #include<iostream>
  2. #include <cmath>
  3. #include <windows.h>
  4. #include <stdio.h>

 

1、原理部分:

  要进行BMP图像的处理,那我们首先就要了解BMP图片的格式,其实主要分为四个部分:

  1、位图文件头数据(BITMAPFILEHEADER):这个数据结构包含了BMP图像文件的类型、大小等信息;

  1. typedef struct targetBITMAPFILEHEADER{
  2. WORD bfType; //文件类型,对于位图来说,这一部分为0x4d42
  3. DWORD bfSize; //文件大小(包含这14字节)
  4. WORD bfReserved1; //保留字,不考虑
  5. WORD bfReserved2; //保留字,同上
  6. DWORD bfOffBits; //实际位图数据的偏移字节数,即前三个部分长度之和
  7. }BITMAPFILEHEADER;

  2、位图信息头数据(BITMAPINFOHEADER):这个数据结构则是包含了BMP图像数据的宽、高、压缩方法、定义颜色、占用空间等等信息;

  1. typedef struct targetBITMAPINFOHEADER{
  2. DWORD biSize; //指定此结构体的长度,为40
  3. LONG biWidth; //位图宽
  4. LONG biHeight; //位图高
  5. WORD biPlanes; //平面数,为1
  6. WORD biBitCount; //采用颜色位数,可以是1,2,4,8,16,24,新的可以是32
  7. DWORD biCompression; //压缩方式,可以是0,1,2,其中0表示不压缩
  8. DWORD biSizeImage; //实际位图数据占用的字节数
  9. LONG biXPelsPerMeter; //X方向分辨率
  10. LONG biYPelsPerMeter; //Y方向分辨率
  11. DWORD biClrUsed; //使用的颜色数,如果为0,则表示默认值(2^颜色位数)
  12. DWORD biClrImportant; //重要颜色数,如果为0,则表示所有颜色都是重要的
  13. }BITMAPINFOHEADER;

  3、调色板(RGBQUAD):其中,这一部分的数据结构是可选择的,有些为徒需要调色板,有些位图则不需要(比如24位的真彩图就不需要);

  1. //为什么需要调色板呢?
  2. //理由是:可以用调色板对颜色进行映射,从而压缩储存空间。
  3. //正常情况下,24bit的位图每一个像素都有RGB三个通道,一共需要24bit
  4. //但是,一幅图里可能用不到那么多颜色,比如256级灰度图像。
  5. //此时,只需要用8bit,就可以表示2^8种通过调色板定义的颜色。
  6. typedef struct tagRGBQUAD{
  7. BYTE rgbBlue;
  8. BYTE rgbGreen;
  9. BYTE rgbRed;
  10. BYTE rgbReserved; //不用管设为0即可
  11. }RGBQUAD;

  4、位图数据:这部分的内容根据BMP位图使用的位数不同而不同,在24位真彩图中,直接使用RGB,而其他的小于24位的则使用调色板中颜色的索引值。

  1. typedef struct tagIMAGEDATA
  2. {
  3. BYTE blue;
  4. BYTE green;
  5. BYTE red;
  6. }IMAGEDATA;

  接下来我们需要了解的是,如何进行图像旋转。首先,最重要的是,我们要知道旋转之后,整个图片的大小其实是改变了的(以图片中心旋转的话)。图片来自《数字图像处理编程入门》。

 

 

  可以明显地看出,从原来的点到现在的点是一种线性变换,那么我们就可以用矩阵来表示这种运算,然后求逆运算就可以由(x1,y1)求出原来的坐标(x0,y0),这相当于把旋转后图上的像素,映射到原图的像素上,然后把原图该映射点的数据复制给新图该点,这样就完成了图片的旋转。

  对于无法映射到原图的点,我们可以把它们的RGB值设为0,这样就会显示成黑色。

  但是像素毕竟不是点,旋转之后会有误差,,如果旋转之后的像素点并不是很如人意的落在像素点上,而是落在临近的四个像素点构成的正方形区域内(而且这种情况应该是很常见的一种),我们使用双线性插值法来估计该点像素值。

 

 

 

代码部分:

  

  1. /**
  2. *注意,由于包含了头文件<windows.h>,所以文件头和信息头还有调色板的定义都用不到
  3. */
  1. void rotateBMP(string path, string resPath, int angle) {
  2. //获取路径名
  3. const char *picture_name=path.c_str();
  4. const char *res_name=resPath.c_str();
  5. //定义文件指针
  6. FILE *file, *targetFile;
  7. //定义文件头和信息头
  8. BITMAPFILEHEADER bmpFile, writeBmpFile;
  9. BITMAPINFOHEADER bmpInfo, writeBmpInfo;
  10. //角度转弧度
  11. auto thelta=(double)(angle*PI/180);
  12. auto cosa=(float)cos((double)thelta);
  13. auto sina=(float)sin((double)thelta);
  14. file = fopen(picture_name, "rb");
  15. targetFile=fopen(res_name,"wb");
  16. fread(&bmpFile, sizeof(BITMAPFILEHEADER), 1, file);
  17. fseek(file, sizeof(BITMAPFILEHEADER), 0);//跳过位图文件头结构
  18. fread(&bmpInfo, sizeof(BITMAPINFOHEADER), 1, file);
  19. // //fseek(file,sizeof(BITMAPINFOHEADER),0);//跳过信息头
  20. this->ShowBMPHead(bmpFile);
  21. this->ShowBMPInfoHead(bmpInfo);
  22. /**
  23. step 1 : 图片处理第一步,首先是完成将文件头,信息头等的数据迁移
  24. **/
  25. writeBmpFile = bmpFile;
  26. writeBmpInfo = bmpInfo;
  27. int width = bmpInfo.biWidth;
  28. int height = bmpInfo.biHeight;
  29. cout<<width<<","<<height<<endl;
  30. //原图的四个角坐标
  31. auto SrcX1=(float)(-0.5*width);
  32. auto SrcY1=(float)(0.5*height);
  33. auto SrcX2=(float)(0.5*width);
  34. auto SrcY2=(float)(0.5*height);
  35. auto SrcX3=(float)(-0.5*width);
  36. auto SrcY3=(float)(-0.5*height);
  37. auto SrcX4=(float)(0.5*width);
  38. auto SrcY4=(float)(-0.5*height);
  39. //新图的四个角坐标
  40. float DstX1=cosa*SrcX1+sina*SrcY1;
  41. float DstY1=-sina*SrcX1+cosa*SrcY1;
  42. float DstX2=cosa*SrcX2+sina*SrcY2;
  43. float DstY2=-sina*SrcX2+cosa*SrcY2;
  44. float DstX3=cosa*SrcX3+sina*SrcY3;
  45. float DstY3=-sina*SrcX3+cosa*SrcY3;
  46. float DstX4=cosa*SrcX4+sina*SrcY4;
  47. float DstY4=-sina*SrcX4+cosa*SrcY4;
  48. //计算新图的宽度和高度
  49. auto newWidth=(int)(max(fabs(DstX4-DstX1),fabs(DstX3-DstX2))+0.5);
  50. auto newHeight=(int)(max(fabs(DstY4-DstY1),fabs(DstY3-DstY2))+0.5);
  51. writeBmpInfo.biWidth = newWidth;
  52. writeBmpInfo.biHeight = newHeight;
  53. // 在计算实际占用的空间的时候我们需要将宽度为4byte的倍数
  54. int writewidth = WIDTHBYTES(newWidth * writeBmpInfo.biBitCount);
  55. writeBmpInfo.biSizeImage = writewidth * writeBmpInfo.biHeight;
  56. writeBmpFile.bfSize = 54 + writeBmpInfo.biSizeImage;
  57. //把修改过的文件头和信息头写入目标文件
  58. fwrite(&writeBmpFile, 1, sizeof(BITMAPFILEHEADER), targetFile);
  59. fwrite(&writeBmpInfo, 1, sizeof(BITMAPINFOHEADER), targetFile);
  60. //申请空间
  61. if(bmpInfo.biBitCount==24)//如果是24位的BMP图
  62. {
  63. int l_width=WIDTHBYTES(width * bmpInfo.biBitCount);
  64. BYTE *preData = (BYTE *)malloc(height * l_width);
  65. memset(preData, 0, height * l_width);
  66. BYTE *aftData = (BYTE *)malloc(newHeight * writewidth);
  67. memset(aftData, 0, newHeight * writewidth);
  68. //原来的旋转中心
  69. int rotateX = width / 2;
  70. int rotateY = height / 2;
  71. //新图的中心
  72. int write_rotateX = newWidth / 2;
  73. int write_rotateY = newHeight / 2;
  74. int OriginalImg = l_width * height;
  75. int LaterImg = writewidth * newHeight;
  76. fread(preData, 1, OriginalImg, file);
  77. for (int i = 0; i < newHeight; ++i) {
  78. for (int j = 0; j < newWidth; ++j) {
  79. int index = i * writewidth + j * 3;
  80. // 利用公式计算这个原来的点的地方
  81. double y0 =
  82. (j - write_rotateX) * sina + (i - write_rotateY) * cosa + rotateY;
  83. double x0 =
  84. (j - write_rotateX) * cosa - (i - write_rotateY) * sina + rotateX;
  85. if((x0>=0)&&(x0<width)&&(y0>=0)&&(y0<=height))
  86. {
  87. /**
  88. * 我们在这里使用双线性插值法来完成对应
  89. */
  90. int y0_True = y0;
  91. int x0_True = x0;
  92. double distance_to_a_X = x0 - x0_True;
  93. double distance_to_a_Y = y0 - y0_True;
  94. int original_point_A = y0_True * l_width + x0_True * 3;
  95. int original_point_B = y0_True * l_width + (x0_True + 1) * 3;
  96. int original_point_C = (y0_True + 1) * l_width + x0_True * 3;
  97. int original_point_D = (y0_True + 1) * l_width + (x0_True + 1) * 3;
  98. if (x0_True == width - 1) {
  99. original_point_A = original_point_B;
  100. original_point_C = original_point_D;
  101. }
  102. if (y0_True == height - 1) {
  103. original_point_C = original_point_A;
  104. original_point_D = original_point_B;
  105. }
  106. //相当于blue
  107. aftData[index] = (1 - distance_to_a_X) * (1 - distance_to_a_Y) * preData[original_point_A]
  108. + (1 - distance_to_a_X) * distance_to_a_Y * preData[original_point_B]
  109. + distance_to_a_X * (1 - distance_to_a_Y) * preData[original_point_C]
  110. + distance_to_a_X * distance_to_a_Y * preData[original_point_D];
  111. //相当于green
  112. aftData[index + 1] = (1 - distance_to_a_X) * (1 - distance_to_a_Y) * preData[original_point_A + 1]
  113. + (1 - distance_to_a_X) * distance_to_a_Y * preData[original_point_B + 1]
  114. + distance_to_a_X * (1 - distance_to_a_Y) * preData[original_point_C + 1]
  115. + distance_to_a_X * distance_to_a_Y * preData[original_point_D + 1];
  116. //相当于red
  117. aftData[index + 2] = (1 - distance_to_a_X) * (1 - distance_to_a_Y) * preData[original_point_A + 2]
  118. + (1 - distance_to_a_X) * distance_to_a_Y * preData[original_point_B + 2]
  119. + distance_to_a_X * (1 - distance_to_a_Y) * preData[original_point_C + 2]
  120. + distance_to_a_X * distance_to_a_Y * preData[original_point_D + 2];
  121. }
  122. }
  123. }
  124. fwrite(aftData,1,LaterImg,targetFile);
  125. fclose(file);
  126. fclose(targetFile);
  127. delete [] preData;
  128. delete [] aftData;
  129. }
  130. else if(bmpInfo.biBitCount==8)//如果是8位的BMP图
  131. {
  132. RGBQUAD strPla[256];//复制调色板
  133. for (unsigned int nCounti = 0; nCounti < 256; nCounti++) {
  134. fread((char *)&(strPla[nCounti].rgbBlue), 1, sizeof(BYTE), file);
  135. fread((char *)&(strPla[nCounti].rgbGreen), 1, sizeof(BYTE), file);
  136. fread((char *)&(strPla[nCounti].rgbRed), 1, sizeof(BYTE), file);
  137. fread((char *)&(strPla[nCounti].rgbReserved), 1, sizeof(BYTE), file);
  138. }
  139. //写入调色板
  140. for (int nCounti = 0; nCounti < 256; nCounti++) {
  141. fwrite((char *)&(strPla[nCounti].rgbBlue), 1, sizeof(BYTE), targetFile);
  142. fwrite((char *)&(strPla[nCounti].rgbGreen), 1, sizeof(BYTE), targetFile);
  143. fwrite((char *)&(strPla[nCounti].rgbRed), 1, sizeof(BYTE), targetFile);
  144. fwrite((char *)&(strPla[nCounti].rgbReserved), 1, sizeof(BYTE), targetFile);
  145. }
  146. int l_width=WIDTHBYTES(width * bmpInfo.biBitCount);
  147. BYTE *preData = (BYTE *)malloc(height * l_width);
  148. memset(preData, 0, height * l_width);
  149. BYTE *aftData = (BYTE *)malloc(newHeight * writewidth);
  150. memset(aftData, 0, newHeight * writewidth);
  151. //读取原图像素数据
  152. fread(preData, sizeof(GRAYDATA)*width, height, file);
  153. //初始化新图的像素点
  154. for (int i = 0; i < newHeight; ++i) {
  155. for (int j = 0; j < newWidth; ++j) {
  156. *(aftData+i*newWidth+j)=0;
  157. }
  158. }
  159. int rotateX = width / 2;
  160. int rotateY = height / 2;
  161. //新图的中心
  162. int write_rotateX = newWidth / 2;
  163. int write_rotateY = newHeight / 2;
  164. int OriginalImg = l_width * height;
  165. int LaterImg = writewidth * newHeight;
  166. fread(preData, 1, OriginalImg, file);
  167. for (int i = 0; i < newHeight; ++i) {
  168. for (int j = 0; j < newWidth; ++j) {
  169. int index = i * writewidth + j ;
  170. // 利用公式计算这个原来的点的地方
  171. double y0 =
  172. (j - write_rotateX) * sina + (i - write_rotateY) * cosa + rotateY;
  173. double x0 =
  174. (j - write_rotateX) * cosa - (i - write_rotateY) * sina + rotateX;
  175. if((x0>=0)&&(x0<width)&&(y0>=0)&&(y0<=height))
  176. {
  177. /**
  178. * 我们在这里使用双线性插值法来完成对应
  179. */
  180. int y0_True = y0;
  181. int x0_True = x0;
  182. double distance_to_a_X = x0 - x0_True;
  183. double distance_to_a_Y = y0 - y0_True;
  184. int original_point_A = y0_True * l_width + x0_True ;
  185. int original_point_B = y0_True * l_width + (x0_True + 1) ;
  186. int original_point_C = (y0_True + 1) * l_width + x0_True ;
  187. int original_point_D = (y0_True + 1) * l_width + (x0_True + 1) ;
  188. if (x0_True == width - 1) {
  189. original_point_A = original_point_B;
  190. original_point_C = original_point_D;
  191. }
  192. if (y0_True == height - 1) {
  193. original_point_C = original_point_A;
  194. original_point_D = original_point_B;
  195. }
  196. //相当于blue
  197. aftData[index] = (1 - distance_to_a_X) * (1 - distance_to_a_Y) * preData[original_point_A]
  198. + (1 - distance_to_a_X) * distance_to_a_Y * preData[original_point_B]
  199. + distance_to_a_X * (1 - distance_to_a_Y) * preData[original_point_C]
  200. + distance_to_a_X * distance_to_a_Y * preData[original_point_D];
  201. }
  202. }
  203. }
  204. fwrite(aftData,1,LaterImg,targetFile);
  205. fclose(file);
  206. fclose(targetFile);
  207. delete [] preData;
  208. delete [] aftData;
  209. }
  210. else
  211. {
  212. cout<<"错误的输入!!!!!!!!!!!!!"<<endl;
  213. }
  214. }

 

 

 

原文链接:http://www.cnblogs.com/xjknb/p/13854500.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号