关于bmp图片的格式,网上有很多文章,具体可以参考百度百科,也有例子程序。这里只提要注意的问题。
(1)结构体定义问题:首先按照百度百科介绍的定义了结构体,但是编译发现重定义BITMAPFILEHEADER等。其实只要包含了Windows.h,里面的wingdi.h就已经定义了处理bmp的结构体,故不需要自己再重复定义。
(2)读取文件的字节对其问题:要使用#pragma pack (1)来方便读取文件头的结构体,否则结构体的大小会由于字节对齐问题改变。不知是否头文件中已经使用了该宏,在我的代码中注释掉#pragma pack (1)也可以正确运行。另外百度到“pack提供数据声明级别的控制,对定义不起作用”,自己也不太清楚这个宏用在哪里比较合适,一般见是在定义结构体的时候,还请各位批评指正。
(3)补齐行数问题:在看百科介绍结构体时,BITMAPINFOHEADER的biSizeImage表示“位图的大小(其中包含了为了补齐行数是4的倍数而添加的空字节),以字节为单位”,并且有相关的计算方法 。我要强调的是提取像素时要排除这些补齐用字节的影响。按照百度百科上提取像素的方法是会将这些补齐用的00字节算入在内的,从而影响后面的算法。
博客园无法上传bmp图片,所以不贴效果图了。有何问题欢迎批评指正
下面是C语言代码供参考:
1 #pragma once 2 3 #include "targetver.h" 4 5 #include <stdio.h> 6 #include <tchar.h> 7 #include <windows.h> 8 #include "bitmap.h" 9 #include <math.h>
bitmap.h:
1 #pragma pack (1)//字节对齐的控制!非常注意! 2 /* 3 typedef struct tagBITMAPFILEHEADER 4 { 5 WORD bfType;//位图文件的类型,必须为BM(1-2字节) 6 DWORD bfSize;//位图文件的大小,以字节为单位(3-6字节,低位在前) 7 WORD bfReserved1;//位图文件保留字,必须为0(7-8字节) 8 WORD bfReserved2;//位图文件保留字,必须为0(9-10字节) 9 DWORD bfOffBits;//位图数据的起始位置,以相对于位图(11-14字节,低位在前) 10 //文件头的偏移量表示,以字节为单位 11 }BITMAPFILEHEADER; 12 13 typedef struct tagBITMAPINFOHEADER{ 14 DWORD biSize;//本结构所占用字节数(15-18字节) 15 LONG biWidth;//位图的宽度,以像素为单位(19-22字节) 16 LONG biHeight;//位图的高度,以像素为单位(23-26字节) 17 WORD biPlanes;//目标设备的级别,必须为1(27-28字节) 18 WORD biBitCount;//每个像素所需的位数,必须是1(双色),(29-30字节) 19 //4(16色),8(256色)16(高彩色)或24(真彩色)之一 20 DWORD biCompression;//位图压缩类型,必须是0(不压缩),(31-34字节) 21 //1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一 22 DWORD biSizeImage;//位图的大小(其中包含了为了补齐行数是4的倍数而添加的空字节),以字节为单位(35-38字节) 23 LONG biXPelsPerMeter;//位图水平分辨率,每米像素数(39-42字节) 24 LONG biYPelsPerMeter;//位图垂直分辨率,每米像素数(43-46字节) 25 DWORD biClrUsed;//位图实际使用的颜色表中的颜色数(47-50字节) 26 DWORD biClrImportant;//位图显示过程中重要的颜色数(51-54字节) 27 }BITMAPINFOHEADER; 28 29 typedef struct tagRGBQUAD{ 30 BYTE rgbBlue;//蓝色的亮度(值范围为0-255) 31 BYTE rgbGreen;//绿色的亮度(值范围为0-255) 32 BYTE rgbRed;//红色的亮度(值范围为0-255) 33 BYTE rgbReserved;//保留,必须为0 34 }RGBQUAD; 35 */ 36 typedef struct 37 { 38 BYTE b; 39 BYTE g; 40 BYTE r; 41 }RGB; 42 43 //带有坐标的颜色RGB表示 44 typedef struct 45 { 46 RGB rgb; 47 int height; 48 int width; 49 } RGB_EX; 50 #pragma pack ()//字节对齐的控制
main.c:
1 // 针对图片实现K-means聚类算法.cpp : 定义控制台应用程序的入口点。 2 #include "stdafx.h" 3 4 float distance(RGB x, RGB mean); 5 int kmeans_img(RGB **Img, LONG ImgWidth, LONG ImgHeight, ULONG lCount, USHORT K); 6 7 int _tmain(int argc, _TCHAR* argv[]) 8 { 9 //#pragma pack (1)//字节对齐的控制!非常注意! 10 BITMAPFILEHEADER fileHeader; 11 BITMAPINFOHEADER infoHeader; 12 FILE* pfin; fopen_s(&pfin, "test2.bmp","rb"); 13 FILE* pfout; fopen_s(&pfout, "ouput.bmp","wb"); 14 //ReadtheBitmapfileheader; 15 fread(&fileHeader, sizeof(BITMAPFILEHEADER), 1, pfin); 16 //ReadtheBitmapinfoheader; 17 fread(&infoHeader, sizeof(BITMAPINFOHEADER), 1, pfin); 18 //为简化代码,只处理24位彩色 19 if(infoHeader.biBitCount==24) { 20 int size = infoHeader.biWidth * infoHeader.biHeight; 21 RGB **ppImg = NULL; 22 int r; 23 //开辟空间并读入图片 24 //RGB img[infoHeader.biHeight][infoHeader.biWidth]; //这里有错误,尺度改为常亮 25 //fread(ppImg, sizeof(RGB), size, pfin); 26 ppImg = (RGB**)malloc(infoHeader.biHeight * sizeof(RGB*)); 27 if (!ppImg) 28 return -1; 29 //注意!需要处理补齐字节问题:每行字节数目必须是4的整数倍 30 r = infoHeader.biWidth % 4; 31 for (int i = 0; i < infoHeader.biHeight; i++) { 32 ppImg[i] = (RGB*)malloc(sizeof(RGB) * infoHeader.biWidth); 33 if (ppImg[i]) { 34 fread(ppImg[i], sizeof(RGB), infoHeader.biWidth, pfin); 35 fseek(pfin, r, SEEK_CUR); 36 } 37 else 38 return -1; 39 } 40 41 /* 42 //把第50行染成黑色 43 int i=0; 44 for(;i<infoHeader.biWidth;i++) { 45 ppImg[50][i].b = ppImg[50][i].g = ppImg[50][i].r = 0; 46 } 47 */ 48 49 kmeans_img(ppImg, infoHeader.biWidth, infoHeader.biHeight, 2000, 5); 50 51 //将修改后的图片保存到文件 52 fileHeader.bfSize = infoHeader.biHeight * infoHeader.biWidth * 3 + fileHeader.bfOffBits; 53 fwrite(&fileHeader,sizeof(fileHeader),1,pfout); 54 fwrite(&infoHeader,sizeof(infoHeader),1,pfout); 55 for (int i = 0; i < infoHeader.biHeight; i++) { 56 fwrite(ppImg[i],sizeof(RGB),infoHeader.biWidth,pfout); 57 int temp = r; 58 while (temp--) 59 { 60 fputc(0, pfout); 61 } 62 } 63 64 //释放图片占用内存 65 for (int i = 0; i < infoHeader.biHeight; i++) 66 free(ppImg[i]); 67 free(ppImg); 68 } 69 fclose(pfin); 70 fclose(pfout); 71 //#pragma pack () 72 return 0; 73 } 74 75 /* 76 对图片像素使用K-means算法聚类,聚成K类 77 Img:RGB矩阵形式的图片。第一维是高度Height。Img[ImgHeight][ImgWidth]。 78 为保证算法正确性,图片中应已经剔除了补齐字节用的00 79 ImgWidth:图片宽 80 ImgHeight:图片高 81 lCount:迭代次数 82 K:聚类数目 83 84 */ 85 int kmeans_img(RGB **Img, LONG ImgWidth, LONG ImgHeight, ULONG lCount, USHORT K) 86 { 87 int iFlag;//收敛后置为0 88 RGB *means = (RGB*)malloc(K * sizeof(RGB));//K个中心 89 RGB_EX **Cluster = NULL;//存放簇 90 int *ClusterLength = NULL; 91 Cluster = (RGB_EX**)malloc(K * sizeof(RGB_EX*)); 92 ClusterLength = (int *)malloc(K * sizeof(int)); 93 for (int i = 0; i < K; i++) { 94 //随意指定K个中心,应该还有更好的算法. 95 means[i] = Img[(ImgHeight/(i+1))-1][(ImgWidth/(i+1))-1]; 96 //开辟簇的存储空间 97 Cluster[i] = (RGB_EX*)malloc(ImgHeight * ImgWidth * sizeof(RGB_EX)); 98 } 99 100 iFlag = K; 101 //开始迭代 102 while (lCount-- && iFlag) 103 { 104 iFlag = K; 105 //每次聚类前要初始化 106 for (int i = 0; i < K; i++) 107 ClusterLength[i] = 0; 108 109 //对每个像素循环,归置到相应的簇里 110 for (int i = 0; i < ImgHeight; i++) { 111 for (int j = 0; j < ImgWidth; j++) { 112 int iClusterIndex = 0; 113 float fMinDistance = 255 * 255 + 255 *255 + 255 * 255; 114 float d = 0; 115 for (int k = 0; k < K; k++) { 116 d = distance(Img[i][j], means[k]); 117 fMinDistance = fMinDistance > d ? iClusterIndex = k, d : fMinDistance; 118 } 119 Cluster[iClusterIndex][ClusterLength[iClusterIndex]].rgb = Img[i][j]; 120 Cluster[iClusterIndex][ClusterLength[iClusterIndex]].height = i; 121 Cluster[iClusterIndex][ClusterLength[iClusterIndex]++].width = j; 122 } 123 } 124 125 //重新计算每个簇的均值 126 for (int i = 0; i < K; i++) { 127 unsigned long sumR = 0, sumG = 0, sumB = 0; 128 BYTE R = 0, G = 0, B = 0; 129 for (int j = 0; j < ClusterLength[i]; j++) { 130 sumR += Cluster[i][j].rgb.r; 131 sumG += Cluster[i][j].rgb.g; 132 sumB += Cluster[i][j].rgb.b; 133 } 134 if (ClusterLength[i]) { 135 R = sumR / ClusterLength[i]; 136 G = sumG / ClusterLength[i]; 137 B = sumB / ClusterLength[i]; 138 } 139 if ( means[i].r == R && means[i].g == G && means[i].b == B) 140 iFlag --;//若均值不变则终止循环 141 else { 142 means[i].r = R; 143 means[i].g = G; 144 means[i].b = B; 145 } 146 } 147 } 148 149 //迭代结束后为每簇上色表达聚类结果 150 for (int i = 0; i < K; i++) { 151 for (int j = 0; j < ClusterLength[i]; j++) { 152 Img[Cluster[i][j].height][Cluster[i][j].width].r = means[i].r; 153 Img[Cluster[i][j].height][Cluster[i][j].width].b = means[i].b; 154 Img[Cluster[i][j].height][Cluster[i][j].width].g = means[i].g; 155 156 } 157 } 158 159 //释放内存 160 for (int i = 0; i < K; i++) { 161 free(Cluster[i]); 162 } 163 free(Cluster); 164 free (means); 165 free(ClusterLength); 166 167 return 0; 168 } 169 170 float distance(RGB x, RGB mean) 171 { 172 return sqrt( pow((float)(x.b - mean.b),2) + 173 pow((float)(x.g - mean.g),2) + 174 pow((float)(x.r - mean.r),2) 175 ); 176 }
By ascii0x03, 2015.10.19