【C】用C语言提取bmp图片像素,并进行K-means聚类分析——容易遇到的问题

时间:2022-05-08
本文章向大家介绍【C】用C语言提取bmp图片像素,并进行K-means聚类分析——容易遇到的问题,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

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