关于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