这是我寒假的一个作业吧,到最后基本是在学校完成的了。这个程序主要完成的功能是读取.obj文件,顺便读取了其附带.mtl文件,以及.mtl文件附带的.tga格式的纹理(我没有处理其它格式了,因为自己下载的几个模型的纹理都是这种图片格式)。然后,用透视投影,调整视点对准模型正中央,渲染在视口里面。程序也提供了键盘上下左右旋转,上下左右移动,放大放小的功能,已经鼠标旋转模型的功能,不过鼠标旋转的效果不好。

首先要介绍下obj模型文件的格式,这个百度或者google肯定能找到不少文章。.obj文件里面主要是定义了点,线,面,当然还描述了其他元素。.mtl文件里面定义了模型的材质以及纹理的路径。我主要描述下我的代码用到的和假定的obj文件格式。
v 1.0 2.3 4.5 //代表一个三维点
v 1.0 2.3 4.5 //代表一个三维点
v 1.0 2.3 4.5 //代表一个三维点
v 1.0 2.3 4.5 //代表一个三维点
vn 1.0 1.0 1.0 //法线
vt 0.3 0.4 //纹理坐标
p 1 //第一个v代表的点
l 1 2 //第一个v和第二个v连接的直线
f 1// 2// 3// 4// //1,2,3,4构成的面,没有法线和纹理
f 1/1/1 2/1/1 3/1/1 4/1/1 //1,2,3,4构成的面,结构是顶点索引,纹理索引,法线索引
我的代码基本上假定没有法线和纹理索引的时候,后面也必须带//
另外一个最重要的是定义材质库的路径,mtllib xxx.mtl。
至于obj文件更加详细的描述,请查阅更标准的文档吧。

然后,介绍下.mtl的格式,我也只介绍我用到的东西。
比如下面这个材质文件的内容,
newmtl Arakkoa_Gray //定义材质设置的名字,一个材质库可能会出现多个材质设置
d 1.0 //透明度
Kd 1.000000 1.000000 1.000000 //模型散射颜色
Ka 0.250000 0.250000 0.250000 //模型环境颜色
Ks 0.000000 0.000000 0.000000//模型镜面颜色
Ke 0.000000 0.000000 0.000000//模型发射颜色
Ns 0.000000//模型镜面指数
map_Kd Arakkoa_Gray.tga//纹理路径
其它设置我就没有用到了。
至于tga图片的格式,请自行google吧。我只处理了未压缩版本的.tga纹理,格式类似于.bmp文件,处理起来也比较简单。

以下是我渲染一个魔兽世界模型的效果,

基本上启用光照加上纹理之后的渲染效果还行。下面我来讲述我整个代码的结构吧。

说实话,这个程序虽然功能不多,但是也可以写出不少代码来的,虽然没多少啰嗦代码,也有近千行。我也定义了不少结构体和类。如下图,

S开头的是结构体,主要定义了顶点,法线,纹理的数据,以及三者的索引(SVertexData),rgb,Tga文件头。其余的,比如CPoint,CLine,CFace对应obj文件里面的p,l,f元素,这三个类里面有各自的渲染函数
CObj类最复杂,里面有顶点,法线,纹理坐标的数据集合,为了方便,我使用了vector,同时用reserve预留空间。还有,点集合,线集合,面集合,材质设置的集合。CObj的渲染函数则是调用点,线,面集合里面的元素对应的渲染函数。其中,最复杂的是面的渲染函数。面的渲染函数会根据材质设置是否改变来更换材质,然后再渲染每个面,同时每个面里面也有个vector集合包含顶点等的索引,所以我的程序不仅仅支持三角形面。CObj类里面最复杂的是读取.obj模型的函数,函数写了100多行,我也没进行功能拆分了,不细说了,至于读取材质的相对简单点。
CMaterial就描述了我使用的集中材质设置,包括一个CTgaTex类成员等。基本上是在读取的时候初始化下材质,更换材质的时候改变下。CTgaTex主要是读取未压缩的tga文件,并且用读取的数据初始化opengl的纹理设置,更改纹理设置的时候,CMaterial会调用其更改纹理。
CBox类也比较复杂,因为该类关系到透视投影,视点等。我是这样考虑的,我读取模型的时候,确定包括整个模型顶点的最小长方体,存储在CBox里面。然后,CBox会求出该长方体的外接球,我再把操作的对象改变成该外接球,也就是我的透视投影啊,观察点设置,平移,放缩,旋转什么的都针对该外接球了。所以啊,我对模型操作的函数全部放置在CBox类里面了。我的透视函数是调用的gluPerspective,近平面是球的前切面,远平面是球的后切面,我的观察点是球心,视点是正对球心离开其4-10倍半径距离的点
至于其余全局函数的实现就不介绍了。

给出一些关键的类定义,看了这些类定义,就知道我的代码结构了。
//obj的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#ifndef _XPC_YX_OBJ_H
#define _XPC_YX_OBJ_H
#include "vertex.h"
#include "mtl.h"
#include
using std::vector;

//点元素
class CPoint
{
friend class CObj;
public:
void Render(CObj obj);//绘制点

private:
int m_nVI;
};

//线元素
class CLine
{
friend class CObj;
public:
void Render(CObj obj);//绘制点

private:
vector m_vis;
};

//面元素
class CFace
{
friend class CObj;
public:
CFace() { m_nNewMtlI = -1; }
void Render(CObj obj);//绘制面

private:
vector m_vds;
int m_nNewMtlI;//所使用新的材质索引(如果不修改材质库此索引为-1)
};

class CBox;
//obj模型
class CObj
{
friend class CPoint;
friend class CLine;
friend class CFace;

enum {VERTEX = 0, TEXTURE = 1, NORMAL = 2};
public:
CObj(CBox* pBox) { m_pBox = pBox; }
CObj(const char* pcszFileName) { LoadFromFile(pcszFileName); }
int LoadFromFile(const char* pcszFileName);//从文件加载模型
int LoadMaterials(const char* pcszFileName);//加载材质库
void Render();//绘制该模型

private:
static const int VERTEX_INIT_NUM;
static const int POINT_INIT_NUM;
static const int LINE_INIT_NUM;
static const int FACE_INIT_NUM;
vector m_vs;//顶点坐标集合
vector m_vts;//顶点纹理坐标集合
vector m_vns;//顶点法线集合
vector m_ps;//点集合
vector m_ls;//线集合
vector m_fs;//面集合
vector m_ms;//材质库
CBox* m_pBox;
};
#endif

//CBox的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class CBox
{
public:
void Init()
{
m_fXMin = m_fYMin = m_fZMin = INT_MAX;
m_fXMax = m_fYMax = m_fZMax = INT_MIN;
}
void InitBoxInfo();
void Adjust(SVertex v) { Adjust(v.fX, v.fY, v.fZ); }
void Adjust(double fX, double fY, double fZ);
void LookAtBox(int nW, int nH);
void Big(int nPercent);
void Small(int nPercent);
void Left(int nPercent);
void Right(int nPercent);
void Up(int nPercent);
void Down(int nPercent);
bool IsInBox(SVertex vertex);
void Move(SVertex one, SVertex two);//移动模型
void Rotate(SVertex one, SVertex two);//旋转模型
void RotateLeft(double fTheta);
void RotateRight(double fTheta);
void RotateUp(double fTheta);
void RotateDown(double fTheta);
void SetLight();

private:
double m_fXMin;
double m_fXMax;
double m_fYMin;
double m_fYMax;
double m_fZMin;
double m_fZMax;
SVertex m_center;//模型原本的外接球中心
double m_fRadius;//外接球的半径
double m_fDistance;//观察点与外接球的距离
double m_fTheta;//视角
};

//材质类的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#ifndef _XPC_YX_MTL_H
#define _XPC_YX_MTL_H

#include "tga.h"
#include <windows.h>

class CObj;

struct SRgb
{
float fR;
float fG;
float fB;
float fA;
};

class CMaterial
{
friend class CObj;
public:
CMaterial()
{
m_emission.fR = m_emission.fG = m_emission.fB = 0.0;
m_fTrans = 1.0;
}
void InitTex();
void Set();

private:
SRgb m_ambient;//材质的环境颜色
SRgb m_diffuse;//
SRgb m_specular;
SRgb m_emission;
float m_fShiness;//镜面指数
float m_fTrans;//透明度
char m_szName[MAX_PATH];
char m_szTextureName[MAX_PATH];
CTgaTex m_tgaTex;
};
#endif

//tga纹理的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#ifndef _XPC_YX_TGA_H
#define _XPC_YX_TGA_H
#include <windows.h>

#define RGB16(r,g,b) ( ((r>>3) << 10) + ((g>>3) << 5) + (b >> 3) )
#define RGB24(r,g,b) ( ((r) << 16) + ((g) << 8) + (b) )

typedef unsigned char BYTE;
//tga文件头
struct STgaHeader
{
BYTE byImageInfoByteCnt; //The image information length, in byte
BYTE byColorTableExist; //0-have not color table,1-have
BYTE byImageType; //Image type,2-uncompare RGB image,10-compare RGB image
BYTE byColorTableInfo[5]; //Color table information
unsigned short uXOrigin;
unsigned short uYOrigin;
unsigned short uWidth;
unsigned short uHeight;
unsigned char chBitsPerPixel; //每像素的字节数
unsigned char chImageDescriptor;
};

class CTgaTex
{
public:
CTgaTex() { byData = 0; m_szTexName[0] = 0; }
CTgaTex(const char* pcszFileName) { byData = 0; LoadTga(pcszFileName); }
~CTgaTex() { if (byData) free(byData); }
int LoadTga(const char* pcszFileName);
void InitTex();
void SetTexture();

private:
STgaHeader m_header;
BYTE* byData;
char m_szTexName[MAX_PATH];
unsigned m_uTexName;
};

#endif

如果你需要完整的代码,可以留言或者给我发邮件,代码写得不怎么样,不过还是尽力写好一点的了。里面肯定会有让老手觉得不规范的地方的。

由于很多人留言询问代码,为了方便,已上传百度网盘,分享地址:http://pan.baidu.com/s/1eQJ9mF4 密码:ovnv

刚开始学习图形学的时候,就看的是计算机图形学opengl版。那么,我当然也想下载最新版本的sdk。但是发现,官网上只有3.1及其以上版本的文档,无论我怎么找,都找不到相应的sdk。后面,我好像是在pudn上面找到了3.0版本的sdk。将就着用了吧,一直到今天我在看OpenGL编程指南第七版的时候,发现无法使用函数glGenBuffers。好吧,没办法,我只能去找更新版本的sdk。

还是找来找去,发现什么都找不到,但是发现opengl的扩展库倒是不少。比如,glew和glextension之类的。后面发现csdn上有篇博文说,opengl3.1及其之后的版本官方都不提供实现了,所以就找不到那些sdk。有个方法是glew,那么就用它吧。

使用glew也不是那么简单的事情,首先得下载glew,我下载了的是1.9的版本。即使配置成功了,也不一定说就能使用成功了。因为还必须初始化glew,最坑爹的是这个初始化必须在你创建窗口之后,否则一定会失败

下面给出使用glew的示例代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include "windows.h"
#include <glew.h>
#include <glut.h>
#include <assert.h>

#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#pragma comment(lib , "glew32.lib")

const GLint WINDOW_WIDTH = 600;
const GLint WINDOW_HEIGHT = 450;
const GLint WINDOW_POS_X = 100;
const GLint WINDOW_POS_Y = 100;
const char* cszWindowTitile = "primrestart";

void Display()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
glRectf(0.0, 0.0, 100.0, 100.0);
glutSwapBuffers();
}

void Reshape(int nW, int nH)
{
glViewport(0, 0, nW, nH);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, nW, 0, nH);
}

int main(int argc, char** argv)
{
glutInit(argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
glutInitWindowPosition(WINDOW_POS_X, WINDOW_POS_Y);
glutCreateWindow(cszWindowTitile);
int nRet = glewInit();
if (nRet != GLEW_OK)
{
MessageBox(NULL, "glew初始化失败", "Error", MB_ICONERROR);
return -1;
}
Init();
glutDisplayFunc(Display);
glutReshapeFunc(Reshape);
glutMainLoop();

return 0;
}

从该代码中可以看到,glew.h之前不能包含gl.h,否则编译不过,还有链接上glew.lib,另外最恶心的就是glewInit的正确位置,一定要在创建了窗口(函数调用glutCreateWindow)之后,否则glewInit一定会失败的,glewInit失败了的话,再使用3.1及其以上版本的函数就会出现内存错误了。

最后做下贡献,附录下我的opengl sdk,opengl3.0+glew1.9+一点文档,基本上可以满足要求了。

这是我在做数字图像处理实验中实现的一个类。该类包含了很多丰富的功能。虽然写得不是太规范,不过我还是尽我的能力让它规范起来。尽可能的便于使用,也尽可能的效率高些,尽可能得增加应用场合。自认为用这个类处理位图基本上足够了。
该类虽说不能处理所有格式的位图,因为时间有限,没有去实现了。但是,基本上8位和24位位图能够完美处理,其它格式的会出错,因为压根没有考虑。那么,现在我来介绍下该类的功能吧。
首先,从文件加载位图,获取位图所有信息和数据。彩色位图灰度化(24位和8位的都可以,24位位图灰度化后会变成8位),使用内部函数计算全局阈值再二值化或者用给定阈值二值化,均值滤波,中值滤波,拉普拉斯滤波,四个方向的梯度滤波,log变换图像,exp变换图像,hough变换检测最明显的一条直线,得到像素分布统计数组,将图片直方图绘制到指定dc和矩形框内部,绘制位图到DC指定位置,拉伸绘制位图到DC指定位置,保存当前状态的图片(也就是你可以将滤波或者任何变换后的图片再保存到文件),输出图像数据到指定文件,创建位图头部,设置位图数据等。功能还是非常丰富的,基本上可以满足很多要求,代码都经常我的认真调试,代码实现上也注意了性能和使用场合。
任意你对代码不满意的部分或者使用出现问题都可以email我,或者自己直接修改。我就不打算再增强它的功能或者优化其实现或者把它改得更规范点了。

类定义如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#ifndef GRAY_BITMAP_H
#define GRAY_BITMAP_H

#define BITMAP_ID 0x4D42 // universal id for a bitmap
#define MAX_COLORS_PALETTE 256

#include <windows.h>

#ifndef SAFE_DELETE
#define SAFE_DELETE(p) { if (p) { delete p; p = NULL; } }
#endif
#ifndef SAFE_DELETE_ARRAY
#define SAFE_DELETE_ARRAY(p) { if (p) { delete [] p; p = NULL; } }
#endif

#define NEIGHBOUR_SIZE (3)

// this builds a 24 bit color value in 8.8.8 format
#define RGB24BIT(r,g,b) ((b) + ((g) << 8) + ((r) << 16) )

class CxBitmap
{
//以下是操作位图的一些函数
public:
CxBitmap();
~CxBitmap();
int CreateBitmap(int nWidth, int nHeight, UINT nBitcount, const void* pvBits);
int LoadBitmap(const char* pszFileName = NULL);
void Draw(HDC hdc, int nStartX, int nStartY);
void StretchDraw(HDC hDc, int nStartX, int nStartY, int nWidth, int nHeight);
void DrawHistogram(HDC hDc, const RECT rect);
BOOL IsAvailable() { return m_bLoad; }
void SaveFile(char* pszBitmapName);
void WriteBuffer(char* pszFileName);
int WriteMeshObj(const char* pcszObjFileName);

//以下是获取和设置位图的属性的一些函数
public:
int GetWidth() const { return bitmapinfoheader.biWidth; }
int GetHeight() const { return bitmapinfoheader.biHeight; }
int GetBytesPerPixel() const { return bitmapinfoheader.biBitCount / 8; }
int GetBytesPerLine() const { return m_nBytesPerLine; }
BYTE* GetBuffer() const { return pbyBuffer; }
BYTE* GetUnAlignBuffer() const { return pbyUnAlignBuffer; }
int GetImageSize() const { return bitmapinfoheader.biSizeImage; }
void SetBitmapName(char* pszBitmapName) { strcpy(m_szBitmapName, pszBitmapName); }
void SetBuffer(const void* pvBuffer)
{
m_bReadOnly = TRUE;
m_bLoad = TRUE;
pbyBuffer = (BYTE*)pvBuffer;
}
BOOL IsReadOnly() const { return m_bReadOnly; }
BOOL SetReadOnly(BOOL bReadOnly) { return m_bReadOnly = bReadOnly; }

//以下是图像处理的一些函数
public:
void ColorToGray();//把彩色图像转换为灰度图像
int* GetHistogram();//得到直方图统计数组(不同灰度值的像素数目)
int GrayToBinary(int nThreshold);
int GrayToBinary();
void MedianFilter();//中值滤波图像
void AverageFilter();//均值滤波
void LaplaceFilter();//拉普拉斯滤波
void GradientFilterLeft();//梯度滤波
void GradientFilterRight();
void GradientFilterUp();
void GradientFilterDown();
void LogTransfer();//对图像进行对数变换
void ExpTransfer();
void GaussFilter(int nRadius = 1);
//用Hough变换检测二值化后图像中的一条直线(R = x*cos(theta) + y*sin(theta),theta是弧度)
void HoughLine(int* pnR, double* pfTheta);

//内部用到的一些私有函数
private:
void FlipBuffer();
void FreeBitmap();
int GetMedian(int* pnArr, int nSize);
void Filter(double* pfFactors, int nSize, BOOL bAve);
int GetThreshold(int* pnHistogram);
void InitPalette();

private:
char m_szBitmapName[MAX_PATH];
BITMAPFILEHEADER bitmapfileheader; // this contains the bitmapfile header
BITMAPINFOHEADER bitmapinfoheader; // this is all the info including the palette
RGBQUAD palette[256]; // we will store the palette here
BOOL m_bReadOnly;
BYTE* pbyBuffer;
BYTE* pbyUnAlignBuffer;
BYTE* pbyTmpBuffer; //临时内存缓冲,用于滤波
BOOL m_bHistogram; //是否已经获得直方图数组了
int* m_pnHistogram;
BOOL m_bLoad; //是否已经加载图片了
BOOL m_bScale; //是否已经灰度化了
BOOL m_bBinary;
int m_nBytesPerLine;
int m_nBytesPerPixel;
};

#endif

类实现如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
#include "CxBitmap.h"
#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <algorithm>
using namespace std;

CxBitmap::CxBitmap()
{
m_bLoad = FALSE;
pbyBuffer = NULL;
pbyTmpBuffer = NULL;
m_bHistogram = FALSE;
m_pnHistogram = NULL;
m_bReadOnly = FALSE;
m_bBinary = FALSE;
}

CxBitmap::~CxBitmap()
{
FreeBitmap();
SAFE_DELETE(m_pnHistogram);
}

void CxBitmap::FreeBitmap()
{
//如果是调用SetBuffer得到的图片,则不需要释放内存,否则会造成内存错误
if (m_bReadOnly == FALSE) SAFE_DELETE_ARRAY(pbyBuffer);
SAFE_DELETE_ARRAY(pbyTmpBuffer);
m_bLoad = FALSE;
m_bHistogram = FALSE;
m_bScale = FALSE;
m_bBinary = FALSE;
}

void CxBitmap::InitPalette()
{
for (int i = 0; i < MAX_COLORS_PALETTE; ++i)
{
palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = i;
}
}

int CxBitmap::CreateBitmap(int nWidth, int nHeight, UINT nBitcount, const void* pvBits)
{
FreeBitmap();
m_nBytesPerPixel = nBitcount / 8;
m_nBytesPerLine = (nWidth * nBitcount + 31) / 32 * 4;

//设置bitmapfileheader
bitmapfileheader.bfType = BITMAP_ID;
bitmapfileheader.bfReserved1 = bitmapfileheader.bfReserved2 = 0;
bitmapfileheader.bfOffBits = sizeof(bitmapfileheader) + sizeof(bitmapinfoheader);
if (nBitcount == 8)
{
bitmapfileheader.bfOffBits += sizeof(palette);
InitPalette();
}
bitmapfileheader.bfSize = bitmapfileheader.bfOffBits + m_nBytesPerLine * nHeight;
//设置bitmapinfoheader
bitmapinfoheader.biSize = sizeof(bitmapinfoheader);
bitmapinfoheader.biWidth = nWidth;
bitmapinfoheader.biHeight = nHeight;
bitmapinfoheader.biPlanes = 1;
bitmapinfoheader.biBitCount = nBitcount;
bitmapinfoheader.biCompression = BI_RGB;
bitmapinfoheader.biSizeImage = m_nBytesPerLine * nHeight;
bitmapinfoheader.biXPelsPerMeter = bitmapinfoheader.biYPelsPerMeter = 0;
bitmapinfoheader.biClrUsed = 0;
if (nBitcount == 8)
{
bitmapinfoheader.biClrUsed = 256;
}
bitmapinfoheader.biClrImportant = 0;
SetBuffer(pvBits);
pbyTmpBuffer = new BYTE[bitmapinfoheader.biSizeImage];

return TRUE;
}

void CxBitmap::FlipBuffer()
{
int nLenOfLine = (bitmapinfoheader.biWidth * bitmapinfoheader.biBitCount) / 8;
BYTE* pbyTemp = new BYTE[nLenOfLine];
int nEnd = bitmapinfoheader.biHeight / 2 - 1;

for (int i = 0; i <= nEnd; ++i)
{
memcpy(pbyTemp, pbyBuffer + i * nLenOfLine, nLenOfLine);
memcpy(pbyBuffer + i * nLenOfLine, pbyBuffer + (bitmapinfoheader.biWidth - i - 1)
* nLenOfLine, nLenOfLine);
memcpy(pbyBuffer + (bitmapinfoheader.biWidth - i - 1) * nLenOfLine, pbyTemp, nLenOfLine);
}
delete [] pbyTemp;
}

int CxBitmap::LoadBitmap(const char* pszFileName)
{
int i;
int nUnAlignBytesPerLine;

FreeBitmap();
if (pszFileName != NULL)
{
strcpy(m_szBitmapName, pszFileName);
}

FILE* fpBitmap = fopen(m_szBitmapName, "rb");

if (fpBitmap == NULL)
{
return FALSE;
}

int nSizeOfHeader = sizeof(bitmapfileheader);
if (nSizeOfHeader != 14)//字节对齐可能会导致其变成16
{
nSizeOfHeader = 14;
}
fread(bitmapfileheader, nSizeOfHeader, 1, fpBitmap);
if (bitmapfileheader.bfType != BITMAP_ID)//检测是否是位图
{
fclose(fpBitmap);
return FALSE;
}
fread(bitmapinfoheader, sizeof(bitmapinfoheader), 1, fpBitmap);
if (bitmapinfoheader.biBitCount == 8)//8位位图,那么需要读取调色板
{
fread(palette, sizeof(PALETTEENTRY) * MAX_COLORS_PALETTE, 1, fpBitmap);
}
if (bitmapinfoheader.biSizeImage == 0)
{
bitmapinfoheader.biSizeImage = bitmapinfoheader.biWidth * bitmapinfoheader.biHeight
* bitmapinfoheader.biBitCount / 8;
}
pbyBuffer = new BYTE[bitmapinfoheader.biSizeImage];
if (pbyBuffer == NULL)
{
return FALSE;
}
fread(pbyBuffer, bitmapinfoheader.biSizeImage, 1, fpBitmap);
if (bitmapinfoheader.biHeight < 0)
{
bitmapinfoheader.biHeight = -bitmapinfoheader.biHeight;
FlipBuffer();
}
fclose(fpBitmap);

m_nBytesPerLine = (bitmapinfoheader.biWidth * bitmapinfoheader.biBitCount + 31) / 32 * 4;
m_nBytesPerPixel = bitmapinfoheader.biBitCount / 8;
pbyUnAlignBuffer = pbyBuffer;
nUnAlignBytesPerLine = bitmapinfoheader.biWidth * m_nBytesPerPixel;
if (m_nBytesPerLine != nUnAlignBytesPerLine)
{
pbyUnAlignBuffer = new BYTE[nUnAlignBytesPerLine * bitmapinfoheader.biHeight];
for (i = 0; i < bitmapinfoheader.biHeight; ++i)
{
memcpy(pbyUnAlignBuffer + i * nUnAlignBytesPerLine, pbyBuffer
+ i * m_nBytesPerLine, nUnAlignBytesPerLine);
}
}
m_bLoad = TRUE;
pbyTmpBuffer = new BYTE[bitmapinfoheader.biSizeImage];

return TRUE;
}

//Y=0.3R+0.59G+0.11B
//该函数仅简单地处理24位和8位位图
void CxBitmap::ColorToGray()
{
int nGray = 0;
int nRead = 0, nWrite = 0;
int i, j;
int nBytesWriteLine = (bitmapinfoheader.biWidth * 8 + 31) / 32 * 4;//灰度后每个像素只有8bits

if (m_bLoad m_bScale == FALSE)//如果已经加载了图片并且没有进行灰度化处理
{
if (bitmapinfoheader.biBitCount == 24)
{
bitmapfileheader.bfOffBits += sizeof(palette);
bitmapinfoheader.biBitCount = 8;
bitmapinfoheader.biClrUsed = 256;
bitmapinfoheader.biSizeImage = nBytesWriteLine * bitmapinfoheader.biHeight;
bitmapfileheader.bfSize = bitmapfileheader.bfOffBits + bitmapinfoheader.biSizeImage;
InitPalette();

for (i = 0; i < bitmapinfoheader.biHeight; ++i)
{
nRead = i * m_nBytesPerLine;
nWrite = i * nBytesWriteLine;
for (j = 0; j < bitmapinfoheader.biWidth; ++j)
{
nGray = 11 * pbyBuffer[nRead++];
nGray += 59 * pbyBuffer[nRead++];
nGray += 30 * pbyBuffer[nRead++];
nGray /= 100;
pbyTmpBuffer[nWrite++] = nGray;
}
}
m_nBytesPerLine = nBytesWriteLine;
m_nBytesPerPixel = 1;
memcpy(pbyBuffer, pbyTmpBuffer, bitmapinfoheader.biSizeImage);
}
else if (bitmapinfoheader.biBitCount == 8)//灰度化调色板
{
for (i = 0; i < MAX_COLORS_PALETTE; ++i)
{
nGray = 11 * palette[i].rgbBlue;
nGray += 59 * palette[i].rgbGreen;
nGray += 30 * palette[i].rgbRed;
nGray /= 100;
palette[i].rgbBlue = palette[i].rgbGreen = palette[i].rgbRed = nGray;
}
}

m_bScale = TRUE;
m_bHistogram = FALSE;//需要重新计算直方图数组
}
}

int* CxBitmap::GetHistogram()
{
if (m_bScale == FALSE)
{
ColorToGray();
}

if (m_bHistogram)
{
return m_pnHistogram;
}

if (m_pnHistogram == NULL)
{
m_pnHistogram = new int[MAX_COLORS_PALETTE];
memset(m_pnHistogram, 0, sizeof(int) * MAX_COLORS_PALETTE);
if (m_pnHistogram == NULL)
{
return NULL;
}
}

int nRead = 0;
int i, j;
int nAdd = bitmapinfoheader.biBitCount / 8;

for (i = 0; i < bitmapinfoheader.biHeight; ++i)
{
nRead = i * m_nBytesPerLine;
for (j = 0; j < bitmapinfoheader.biWidth; ++j)
{
m_pnHistogram[pbyBuffer[nRead]]++;
nRead += nAdd;
}
}

m_bHistogram = TRUE;

return m_pnHistogram;
}

void CxBitmap::DrawHistogram(HDC hDc, const RECT rect)
{
double fPosX = rect.left;
double fPosY = rect.top;
int nWidth = rect.right - rect.left;
int nHeight = rect.bottom - rect.top;
int nBegX = nWidth * 0.05;
int nEndY = nHeight * 0.95;
double fWidth = 1.0 * (nWidth - nBegX) / MAX_COLORS_PALETTE;
const int NUM = 100;
const double ADD = 10 * (1.0 * nHeight) / NUM;
char szGrade[MAX_PATH];
int nTmp;
int i;
int nMaxCnt = 0;
int nAddScore = 10;
int nAddScale = 16;

if (GetHistogram() == NULL)
{
return;
}

SetTextAlign(hDc, TA_LEFT);
SetBkMode(hDc, TRANSPARENT);

for (i = 0; i < NUM; i += nAddScore)
{
sprintf(szGrade, "%d", NUM - i);
TextOut(hDc, (int)fPosX, (int)fPosY, szGrade, strlen(szGrade));
fPosY += ADD;
}

fPosX = nBegX;
for (i = 0; i < MAX_COLORS_PALETTE; i += nAddScale)
{
sprintf(szGrade, "%d", i);
TextOut(hDc, (int)fPosX, nEndY, szGrade, strlen(szGrade));
fPosX += fWidth * nAddScale;
}
sprintf(szGrade, "%d", MAX_COLORS_PALETTE - 1);
TextOut(hDc, (int)fPosX, nHeight, szGrade, strlen(szGrade));

HBRUSH hOldBrush = (HBRUSH)SelectObject(hDc, GetStockObject(BLACK_BRUSH));
HPEN hOldPen = (HPEN)SelectObject(hDc, GetStockObject(BLACK_PEN));
fPosX = rect.left + nBegX;
fPosY = rect.top;

for (i = 0; i < MAX_COLORS_PALETTE; ++i)
{
if (m_pnHistogram[i] > nMaxCnt)
{
nMaxCnt = m_pnHistogram[i];
}
}

for (i = 0; i < MAX_COLORS_PALETTE; ++i)
{
nTmp = nHeight * m_pnHistogram[i] / nMaxCnt;
Rectangle(hDc, (int)fPosX, (int)(fPosY + nEndY - nTmp),
(int)(fPosX + fWidth), (int)(fPosY + nEndY));
fPosX += fWidth;
}
SelectObject(hDc, hOldBrush);
SelectObject(hDc, hOldPen);
}

int CxBitmap::GetThreshold(int* pnHistogram)
{
double fU0, fU1;
double fW0, fW1;
int nCount;
int nT, nMaxT;
double fDevi, fMaxDevi = 0; //方差及最大方差
int i;
int nSum = 0;

for (i = 0; i < MAX_COLORS_PALETTE; i++)
{
nSum += pnHistogram[i];
}
for (nT = 0; nT < MAX_COLORS_PALETTE; nT++)
{
fU0 = 0;
nCount = 0;
//阈值为t时,c0组的均值及产生的概率
for (i = 0; i <= nT; i++)
{
fU0 += i * pnHistogram[i];
nCount += pnHistogram[i];
}
fU0 /= nCount;
fW0 = 1.0 * nCount / nSum;
//阈值为t时,c1组的均值及产生的概率
fU1 = 0;
for (i = nT + 1; i < MAX_COLORS_PALETTE; i++)
{
fU1 += i * pnHistogram[i];
}
fU1 /= nSum - nCount;
fW1 = 1 - fW0;

//两类间方差
fDevi = fW0 * fW1 * (fU1 - fU0) * (fU1 - fU0);
//记录最大的方差及最佳位置
if (fDevi > fMaxDevi)
{
fMaxDevi = fDevi;
nMaxT = nT;
}
}

return nMaxT;
}

int CxBitmap::GrayToBinary()
{
if(GetHistogram())
{
return GrayToBinary(GetThreshold(m_pnHistogram));
}

return FALSE;
}

//二值化
int CxBitmap::GrayToBinary(int nThreshold)
{
int nRead = 0;

if (!m_bLoad || !m_bScale)
{
return FALSE;
}

if (bitmapinfoheader.biBitCount == 24)
{
ColorToGray();
}

for (int i = 0; i < bitmapinfoheader.biHeight; ++i)
{
nRead = i * m_nBytesPerLine;
for (int j = 0; j < bitmapinfoheader.biWidth; ++j)
{
if (pbyBuffer[nRead] >= nThreshold)
{
pbyBuffer[nRead] = 255;
}
else
{
pbyBuffer[nRead] = 0;
}

if (i == 0 || i == bitmapinfoheader.biHeight - 1
|| j == 0 || j == bitmapinfoheader.biWidth - 1)
{
pbyBuffer[nRead] = 0;
}
nRead++;
}
}
m_bBinary = TRUE;

return TRUE;
}

void CxBitmap::Draw(HDC hDc, int nStartX, int nStartY)
{
if (m_bLoad)
{
StretchDIBits(hDc, nStartX, nStartY, bitmapinfoheader.biWidth,
bitmapinfoheader.biHeight, 0, 0, bitmapinfoheader.biWidth,
bitmapinfoheader.biHeight, pbyBuffer, (BITMAPINFO*)bitmapinfoheader,
DIB_RGB_COLORS, SRCCOPY);
}
}

void CxBitmap::StretchDraw(HDC hDc, int nStartX, int nStartY, int nWidth, int nHeight)
{
if (m_bLoad)
{
StretchDIBits(hDc, nStartX, nStartY, nWidth, nHeight, 0, 0, bitmapinfoheader.biWidth,
bitmapinfoheader.biHeight, pbyBuffer, (BITMAPINFO*)bitmapinfoheader,
DIB_RGB_COLORS, SRCCOPY);
}
}

void CxBitmap::WriteBuffer(char* pszFileName)
{
if (m_bLoad)
{
FILE* fpWrite = fopen(pszFileName, "w");
int nRead = 0;
for (int i = 0; i < bitmapinfoheader.biHeight; ++i)
{
nRead = i * m_nBytesPerLine;
for (int j = 0; j < bitmapinfoheader.biWidth; ++j)
{
for (int k = 0; k < m_nBytesPerPixel; ++k)
{
fprintf(fpWrite, "%d ", pbyBuffer[nRead]);
++nRead;
}
}
fprintf(fpWrite, "\n");
}
}
}

void CxBitmap::SaveFile(char* pszBitmapName)
{
if (m_bLoad)
{
FILE* fpSave = fopen(pszBitmapName, "wb");
fwrite(bitmapfileheader, sizeof(bitmapfileheader), 1, fpSave);
fwrite(bitmapinfoheader, sizeof(bitmapinfoheader), 1, fpSave);
if (bitmapinfoheader.biBitCount == 8)
{
fwrite(palette, sizeof(palette), 1, fpSave);
}
fwrite(pbyBuffer, bitmapinfoheader.biSizeImage, 1, fpSave);
fclose(fpSave);
}
}

int CxBitmap::GetMedian(int* pnArr, int nSize)
{
int nTime = nSize / 2 + 1;
int nTemp = 0;

//下面进行nTime + 1次冒泡排序
while (nTime)
{
nTime--;
nSize--;
for (int i = 0; i < nSize; ++i)
{
if (pnArr[i] > pnArr[i + 1])
{
nTemp = pnArr[i];
pnArr[i] = pnArr[i + 1];
pnArr[i + 1] = nTemp;
}
}
}
return pnArr[nSize + 1];
}

void CxBitmap::MedianFilter()
{
int nRead = 0;
int nMedian = 0;
int nBytePerPixel = bitmapinfoheader.biBitCount / 8;
int i, j, k, m, n, nCnt;;
const int FILTEER_SIZE = 3;
int nTmpArr[FILTEER_SIZE * FILTEER_SIZE];

if (!m_bLoad)
{
return;
}

//拷贝边界到临时缓存区
memcpy(pbyTmpBuffer, pbyBuffer, m_nBytesPerLine);
memcpy(pbyTmpBuffer, pbyBuffer + (bitmapinfoheader.biHeight - 1) * m_nBytesPerLine,
m_nBytesPerLine);
for (i = 0; i < bitmapinfoheader.biHeight; ++i)
{
nRead = i * m_nBytesPerLine;
pbyTmpBuffer[nRead] = pbyBuffer[nRead];
pbyTmpBuffer[nRead + 1] = pbyBuffer[nRead + 1];
pbyTmpBuffer[nRead + 2] = pbyBuffer[nRead + 2];

nRead += nBytePerPixel * (bitmapinfoheader.biWidth - 1);
pbyTmpBuffer[nRead] = pbyBuffer[nRead];
pbyTmpBuffer[nRead + 1] = pbyBuffer[nRead + 1];
pbyTmpBuffer[nRead + 2] = pbyBuffer[nRead + 2];
}

for (i = 1; i < bitmapinfoheader.biHeight - 1; ++i)
{
nRead = i * m_nBytesPerLine + nBytePerPixel;
for (j = 1; j < bitmapinfoheader.biWidth - 1; ++j)
{
for (k = 0; k < m_nBytesPerPixel; ++k)
{
nCnt = 0;
nMedian = 0;
for (m = 0; m < FILTEER_SIZE; ++m)
{
for (n = 0; n < FILTEER_SIZE; ++n)
{
nTmpArr[nCnt++] = pbyBuffer[nRead + (m - 1) * m_nBytesPerLine + (n -1) * nBytePerPixel];
}
}
nMedian = GetMedian(nTmpArr, nCnt);
pbyTmpBuffer[nRead++] = nMedian;
}
}
}

m_bBinary = FALSE;//滤波处理之后肯定不是二值图像了
memcpy(pbyTmpBuffer, pbyTmpBuffer, bitmapinfoheader.biSizeImage);
}

//传入滤波窗口系数和尺寸(nSize*nSize)
void CxBitmap::Filter(double* pfFactors, int nSize, BOOL bAve)
{
double fMedian = 0;
int nRead = 0;
int nRadius = nSize / 2;//滤波窗口的半径
int nBytePerPixel = bitmapinfoheader.biBitCount / 8;
int i, j, k, m, n;

//拷贝边界到临时缓存区
for (i = 0; i < nRadius; ++i)
{
memcpy(pbyTmpBuffer + i * m_nBytesPerLine, pbyBuffer + i * m_nBytesPerLine,
m_nBytesPerLine);
memcpy(pbyTmpBuffer + (bitmapinfoheader.biHeight - 1 - i) * m_nBytesPerLine,
pbyBuffer + (bitmapinfoheader.biHeight - 1 - i) * m_nBytesPerLine,
m_nBytesPerLine);
}

for (i = 0; i < bitmapinfoheader.biHeight; ++i)
{
nRead = i * m_nBytesPerLine;
for (j = 0; j < nRadius; ++j)
{
pbyTmpBuffer[nRead] = pbyBuffer[nRead++];
pbyTmpBuffer[nRead] = pbyBuffer[nRead++];
pbyTmpBuffer[nRead] = pbyBuffer[nRead++];
}

nRead = i * m_nBytesPerLine + nBytePerPixel * (bitmapinfoheader.biWidth - nRadius);
for (j = 0; j < nRadius; ++j)
{
pbyTmpBuffer[nRead] = pbyBuffer[nRead++];
pbyTmpBuffer[nRead] = pbyBuffer[nRead++];
pbyTmpBuffer[nRead] = pbyBuffer[nRead++];
}
}

for (i = nRadius; i < bitmapinfoheader.biHeight - nRadius; ++i)
{
nRead = i * m_nBytesPerLine + nRadius * nBytePerPixel;
for (j = nRadius; j < bitmapinfoheader.biWidth - nRadius; ++j)
{
for (k = 0; k < m_nBytesPerPixel; ++k)
{
fMedian = 0;
for (m = 0; m < nSize; ++m)
{
for (n = 0; n < nSize; ++n)
{
fMedian += *(pfFactors + nSize * m + n) * pbyBuffer[nRead + (m - nRadius) * m_nBytesPerLine + (n - nRadius) * nBytePerPixel];
}
}
if (bAve)
{
fMedian /= nSize * nSize;
}
//assert(nMedian >= 0);
//注意必须处理变换后不在0-255范围内的像素
if (fMedian < 0)
{
fMedian = 0;
}
if (fMedian > 255)
{
fMedian = 255;
}
pbyTmpBuffer[nRead++] = fMedian;
}
}
}

m_bBinary = FALSE;//滤波处理之后肯定不是二值图像了
memcpy(pbyBuffer, pbyTmpBuffer, bitmapinfoheader.biSizeImage);
}

//8邻域滤波均值滤波
void CxBitmap::AverageFilter()
{
const int AVE_NEIGHBOUR_SIZE = 3;
static double s_fAveFactors[AVE_NEIGHBOUR_SIZE][AVE_NEIGHBOUR_SIZE] =
{
{1, 1, 1},
{1, 1, 1},
{1, 1, 1},
};
assert(m_bLoad);
if (m_bLoad)
{
Filter(s_fAveFactors[0], AVE_NEIGHBOUR_SIZE, TRUE);
}
}

//拉普拉斯滤波
void CxBitmap::LaplaceFilter()
{
static double s_flapFactors[NEIGHBOUR_SIZE][NEIGHBOUR_SIZE] =
{
{-1, -1, -1},
{-1, 9, -1},
{-1, -1, -1},
};
assert(m_bLoad);
if (m_bLoad)
{
Filter(s_flapFactors[0], NEIGHBOUR_SIZE, FALSE);
}
}

//梯度滤波
void CxBitmap::GradientFilterLeft()
{
const int GRADIENT_NEIGHBOUR_SIZE = 3;
static double s_fGdFactors[GRADIENT_NEIGHBOUR_SIZE][GRADIENT_NEIGHBOUR_SIZE] =
{
{-1, 0, 1},
{-2, 0, 2},
{-1, 0, 1}
};

assert(m_bLoad);
if (m_bLoad)
{
Filter(s_fGdFactors[0], GRADIENT_NEIGHBOUR_SIZE, FALSE);
}
}

//梯度滤波
void CxBitmap::GradientFilterRight()
{
const int GRADIENT_NEIGHBOUR_SIZE = 3;
static double s_fGdFactors[GRADIENT_NEIGHBOUR_SIZE][GRADIENT_NEIGHBOUR_SIZE] =
{
{1, 0, -1},
{2, 0, -2},
{1, 0, -1}
};

assert(m_bLoad);
if (m_bLoad)
{
Filter(s_fGdFactors[0], GRADIENT_NEIGHBOUR_SIZE, FALSE);
}
}

//梯度滤波
void CxBitmap::GradientFilterUp()
{
const int GRADIENT_NEIGHBOUR_SIZE = 3;
static double s_fGdFactors[GRADIENT_NEIGHBOUR_SIZE][GRADIENT_NEIGHBOUR_SIZE] =
{
{1, 2, 1},
{0, 0, 0},
{-1, -2, -1},
};

assert(m_bLoad);
if (m_bLoad)
{
Filter(s_fGdFactors[0], GRADIENT_NEIGHBOUR_SIZE, FALSE);
}
}

//梯度滤波
void CxBitmap::GradientFilterDown()
{
const int GRADIENT_NEIGHBOUR_SIZE = 3;
static double s_fGdFactors[GRADIENT_NEIGHBOUR_SIZE][GRADIENT_NEIGHBOUR_SIZE] =
{
{-1, -2, -1},
{0, 0, 0},
{1, 2, 1}
};

assert(m_bLoad);
if (m_bLoad)
{
Filter(s_fGdFactors[0], GRADIENT_NEIGHBOUR_SIZE, FALSE);
}
}

void CxBitmap::GaussFilter(int nRadius)
{
int nSize = 2 * nRadius + 1;
int nDis = 0;
const double SIGMA = 1.5;
const double A = 2 * SIGMA * SIGMA;
const double B = 1.0 / (4 * atan(1.0) * A);
double* pfFactors = new double[nSize * nSize];
double fTotal = 0.0;
int i, j;

for (i = 0; i < nSize; ++i)
{
for (j = 0; j < nSize; ++j)
{
nDis = (i - nRadius) * (i - nRadius) + (j - nRadius) * (j - nRadius);
pfFactors[i * nSize + j] = B * exp(-nDis / A);
fTotal += pfFactors[i * nSize + j];
}
}
for (i = 0; i < nSize; ++i)
{
for (j = 0; j < nSize; ++j)
{
pfFactors[i * nSize + j] /= fTotal;
}
}

assert(m_bLoad);
if (m_bLoad)
{
Filter(pfFactors, nSize, FALSE);
}

delete [] pfFactors;
}

void CxBitmap::LogTransfer()
{
const int C = 10;
int nRead = 0;
int nScale = 0;

for (int i = 0; i < bitmapinfoheader.biHeight; ++i)
{
nRead = i * m_nBytesPerLine;
for (int j = 0; j < bitmapinfoheader.biWidth; ++j)
{
for (int k = 0; k < m_nBytesPerPixel; ++k)
{
nScale = C * log(1 + pbyBuffer[nRead]);
pbyBuffer[nRead++] = nScale;
}
}
}
}

void CxBitmap::ExpTransfer()
{
const int C = 10;
int nRead = 0;
int nScale = 0;

for (int i = 0; i < bitmapinfoheader.biHeight; ++i)
{
nRead = i * m_nBytesPerLine;
for (int j = 0; j < bitmapinfoheader.biWidth; ++j)
{
for (int k = 0; k < m_nBytesPerPixel; ++k)
{
nScale = C * exp(pbyBuffer[nRead]);
if (nScale > 255)
{
nScale = 255;
}
pbyBuffer[nRead++] = nScale;
}
}
}
}

//针对hough变换的定义得到的直线相对的是坐标系原点的坐标系
void CxBitmap::HoughLine(int* pnR, double* pfTheta)
{
const double PI = 4.0 * atan(1.0);
double fRate = PI / 180;
int nWidth = GetWidth(), nHeight = GetHeight();
int iRMax = (int)sqrt(nWidth * nWidth + nHeight * nHeight) + 1;
int iThMax = 360;
int iTh;
int iR;
int iMax = -1;
int iThMaxIndex = -1;
int iRMaxIndex = -1;
int *pnArray = NULL;
int nPos = 0;

if (m_bBinary == FALSE)
{
GrayToBinary();
}
pnArray = new int[iRMax * iThMax];//iRMax行,每一行长度是iThMax
memset(pnArray, 0, sizeof(int) * iRMax * iThMax);

for (int y = 0; y < nHeight; y++)
{
nPos = m_nBytesPerLine * y;
for (int x = 0; x < nWidth; x++)
{
if(pbyBuffer[nPos] == 255)
{
for(iTh = 0; iTh < iThMax; iTh++)
{
iR = (int)(x * cos(iTh * fRate) + y * sin(iTh * fRate));

if(iR > 0)
{
pnArray[iR * iThMax + iTh]++;
}
}
}
nPos++;
} // x
} // y

for(iR = 0; iR < iRMax; iR++)
{
for(iTh = 0; iTh < iThMax; iTh++)
{
int iCount = pnArray[iR * iThMax + iTh];
if(iCount > iMax)
{
iMax = iCount;
iRMaxIndex = iR;
iThMaxIndex = iTh;
}
}
}

*pnR = iRMaxIndex;
*pfTheta = fRate * iThMaxIndex;

delete [] pnArray;
}

extern int Pow2Big(int nA);
int CxBitmap::WriteMeshObj(const char* pcszObjFileName)
{
int i, j;
int nPos;
char szTexFileName[MAX_PATH];
FILE* fpFile;

strcpy(szTexFileName, pcszObjFileName);
strcat(szTexFileName, ".mtl");
fpFile = fopen(szTexFileName, "w");
fprintf(fpFile, "newmtl %s.mtl\n", m_szBitmapName);
fprintf(fpFile, "map_Kd %s\n", m_szBitmapName);
fclose(fpFile);

fpFile = fopen(pcszObjFileName, "w");
if (fpFile == NULL)
{
return 0;
}

fprintf(fpFile, "mtllib %s\n", szTexFileName);
//v
for (i = 0; i < bitmapinfoheader.biHeight; ++i)
{
for (j = 0; j < bitmapinfoheader.biWidth; ++j)
{
fprintf(fpFile, "v %d %d %d\n", j, i, 0);//v x y z
}
}
//vt(纹理)
for (i = 0; i < bitmapinfoheader.biHeight; ++i)
{
for (j = 0; j < bitmapinfoheader.biWidth; ++j)
{
fprintf(fpFile, "vt %f %f 0.0\n", (double)(j + 1) / bitmapinfoheader.biWidth,
(double)(i + 1) / bitmapinfoheader.biHeight);//vt s t
}
}
fprintf(fpFile, "usemtl %s.mtl\n", m_szBitmapName);
//f 1//1 2//2 101//101
for (i = 0; i < bitmapinfoheader.biHeight - 1; ++i)
{
for (j = 0; j < bitmapinfoheader.biWidth - 1; ++j)
{
nPos = i * bitmapinfoheader.biWidth + j;
//上三角,面标记为nPos
fprintf(fpFile, "f %d/%d/ %d/%d/ %d/%d/ %d\n", nPos, nPos,
nPos + bitmapinfoheader.biWidth, nPos + bitmapinfoheader.biWidth,
nPos + bitmapinfoheader.biWidth + 1, nPos + bitmapinfoheader.biWidth + 1,
nPos);
nPos++;
//下三角
fprintf(fpFile, "f %d/%d/ %d/%d/ %d/%d/ %d\n", nPos, nPos, nPos - 1, nPos - 1,
nPos + bitmapinfoheader.biWidth, nPos + bitmapinfoheader.biWidth, nPos);
}
}
fclose(fpFile);

return 1;
}

下面我再演示一下使用该类得到的一些效果吧。
加载原图的效果,我都是用使用该类加载文件并绘制到指定位置。

下面这张是对一张24位彩色图像灰度化后得到灰度直方图。

对同样的图片进行某个方向梯度变换后的效果如下图,可以看到边缘都增强了。

再下面这张是另外一个实现进行下边缘梯度滤波再hough变换的结果,该实验的目的是检测三角形。

锐化空间滤波用来增强细节或者边缘,常用的有拉普拉斯滤波和梯度滤波。
拉普拉斯滤波其实就是二维离散函数的二阶差分。因为任意一张图片可以看作是二维离散函数z=f(x,y),对其求二阶离散差分就能得到3 3的滤波窗口系数表,具体推理可以参考数字图像处理那本很经典的书籍。再用原图减去就得到了一张增强细节的图片了。
如果使用下列的系数那么得到的是用*原图减去拉普拉斯变换的增强细节的图片

static double s_flapFactors[NEIGHBOUR_SIZE][NEIGHBOUR_SIZE] =
{
{-1, -1, -1},
{-1, 9, -1},
{-1, -1, -1},
};

梯度滤波则用到了方向变化率之类的知识,简而言之就是在哪个方向变化最快。梯度滤波没有精确的系数表示,一般用一些系数表示来近似。比如,

{-1, 0, 1},
{-2, 0, 2},
{-1, 0, 1}

就是用来增强左边缘的,还有,

{1, 0, -1},
{2, 0, -2},
{1, 0, -1},

{1, 2, 1},
{0, 0, 0},
{-1, -2, -1},

{-1, -2, -1},
{0, 0, 0},
{1, 2, 1}

都是用来增强其它方向的边缘,总共四个滤波系数窗口用来增强不同方向的边缘
至于这些滤波的实现方法,只需要用这些滤波系数去调用我在上一篇文章给出的滤波公共函数即可。

图像处理中的空间滤波主要有2种类型,平滑空间滤波和锐化空间滤波。平滑空间滤波一般用于模糊图像,比如消除散乱的噪声点等。平滑空间滤波主要有均值滤波和中值滤波,以及其余的复杂统计方法。滤波窗口的大小可以选为33或者44或者5*5等。
其实不同的空间滤波处理基本上就滤波窗口的系数不相同,除了均值滤波要除以滤波窗口大小以及中值滤波要特殊处理之外,那么我们可以实现一个针对特定滤波窗口实现的滤波公共函数。比如,均值滤波窗口

{1,1,1}

{1,1,1},

{1,1,1}

就是选取当前像素点周围9个点的像素值总和再除以9得到的。而中值滤波则必须得到这9个值中排行第5的值作为当前像素的值。平滑滤波的原理也比较简单。下面给出相关的代码。

滤波基础函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//传入滤波窗口系数和尺寸(nSize*nSize)
void CxBitmap::Filter(double* pfFactors, int nSize, BOOL bAve)
{
double fMedian = 0;
int nRead = 0;
int nRadius = nSize / 2;//滤波窗口的半径
int nBytePerPixel = bitmapinfoheader.biBitCount / 8;
int i, j, k, m, n;

//拷贝边界到临时缓存区
for (i = 0; i < nRadius; ++i)
{
memcpy(pbyTmpBuffer + i * m_nBytesPerLine, pbyBuffer + i * m_nBytesPerLine,
m_nBytesPerLine);
memcpy(pbyTmpBuffer + (bitmapinfoheader.biHeight - 1 - i) * m_nBytesPerLine,
pbyBuffer + (bitmapinfoheader.biHeight - 1 - i) * m_nBytesPerLine,
m_nBytesPerLine);
}

for (i = 0; i < bitmapinfoheader.biHeight; ++i)
{
nRead = i * m_nBytesPerLine;
for (j = 0; j < nRadius; ++j)
{
pbyTmpBuffer[nRead] = pbyBuffer[nRead++];
pbyTmpBuffer[nRead] = pbyBuffer[nRead++];
pbyTmpBuffer[nRead] = pbyBuffer[nRead++];
}

nRead = i * m_nBytesPerLine + nBytePerPixel * (bitmapinfoheader.biWidth - nRadius);
for (j = 0; j < nRadius; ++j)
{
pbyTmpBuffer[nRead] = pbyBuffer[nRead++];
pbyTmpBuffer[nRead] = pbyBuffer[nRead++];
pbyTmpBuffer[nRead] = pbyBuffer[nRead++];
}
}

for (i = nRadius; i < bitmapinfoheader.biHeight - nRadius; ++i)
{
nRead = i * m_nBytesPerLine + nRadius * nBytePerPixel;
for (j = nRadius; j < bitmapinfoheader.biWidth - nRadius; ++j)
{
for (k = 0; k < m_nBytesPerPixel; ++k)
{
fMedian = 0;
for (m = 0; m < nSize; ++m)
{
for (n = 0; n < nSize; ++n)
{
fMedian += *(pfFactors + nSize * m + n) * pbyBuffer[nRead + (m - nRadius) * m_nBytesPerLine + (n - nRadius) * nBytePerPixel];
}
}
if (bAve)
{
fMedian /= nSize * nSize;
}
//assert(nMedian >= 0);
//注意必须处理变换后不在0-255范围内的像素
if (fMedian < 0)
{
fMedian = 0;
}
if (fMedian > 255)
{
fMedian = 255;
}
pbyTmpBuffer[nRead++] = fMedian;
}
}
}

m_bBinary = FALSE;//滤波处理之后肯定不是二值图像了
memcpy(pbyBuffer, pbyTmpBuffer, bitmapinfoheader.biSizeImage);
}

均值滤波函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//8邻域滤波均值滤波
void CxBitmap::AverageFilter()
{
const int AVE_NEIGHBOUR_SIZE = 3;
static double s_fAveFactors[AVE_NEIGHBOUR_SIZE][AVE_NEIGHBOUR_SIZE] =
{
{1, 1, 1},
{1, 1, 1},
{1, 1, 1},
};
assert(m_bLoad);
if (m_bLoad)
{
Filter(s_fAveFactors[0], AVE_NEIGHBOUR_SIZE, TRUE);
}
}

hough变换是图像处理里面一种检测直线等规则曲线的方法。这里介绍下检测直线的原理和实现方法,最后给出相关的代码。

先给出数学上面的里面,斜截式直线y=kx+b有两个参数k,b,注意直线方程是在笛卡儿坐标系xoy中的。任意一条直线对应于kob平面中一个点(k,b)。那么,对于xoy平面中的任意一个点肯定有无数条通过它的直线,那么直线都有对应的kob平面中的点,这些点能组成一条直线。综合起来就是,xoy平面中一条直线对应于kob平面中的一个点,xoy平面中的一个点对应于kob平面中的一条直线。
现在有个问题是斜截式无法表示xoy平面中所有直线,那么我们希望能找到能够表示xoy平面中所有直线的参数式子。这个式子是r=xcos(θ)+ysin(θ)。推导过程如下:

r为原点到直线的距离,(x0,y0)为垂足,θ为垂线和x轴正向的夹角,那么对于直线上面的任意的一个点都有
(x0, y0) (x-x0,y-y0)=0,得到x x0 - x0 x0 + y y0 - y0 y0 = 0,即x x0 + y y0 = x0 x0 + y0 y0 = r r。等式两边再除以r得到r = x cos(θ) + y sin(θ)。那么这个关于(θ,r)的参数式子可以表示xoy平面上所有的直线了。

现在再来讨论下如何根据这个参数式子检测直线的。
我们手上的只有xoy平面上的图像。而且根据我们的推理,我们的图像的原点必须在左下角,因为我们是用笛卡尔坐标系推出该参数式子的。那么,我们可以枚举图像上面每一个可能是直线上面的点(比如,我们可以对图像阈值化后,像素值为255的点就可能是直线上面的点),由于图像上的每个点对应于θor平面上面的一条曲线(这里管它是直线还是曲线了,对于检测原理没影响),那么我们就能得到很多θor平面上的曲线。我们对每个(θ,r)点计数通过其上的曲线个数,最后选取曲线个数最大的那个点(θmax,Rmax)其对应的肯定是我们要检测的直线。至于其它的预处理,一般是梯度滤波增强边缘或者拉普拉斯增强边缘,然后弄个全局二值化,就可以用hough变换检测直线了(我这次的实验就没有平滑滤波噪声,那样会使直线变粗,使检测效果变坏)
最后我给出我的检测直线的代码,并指出代码需要主要的实现点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//针对hough变换的定义得到的直线相对的是坐标系原点的坐标系
//针对hough变换的定义得到的直线相对的是坐标系原点的坐标系
void CxBitmap::HoughLine(int* pnR, double* pfTheta)
{
const double PI = 4.0 * atan(1.0);
double fRate = PI / 180;
int nWidth = GetWidth(), nHeight = GetHeight();
int iRMax = (int)sqrt(nWidth * nWidth + nHeight * nHeight) + 1;
int iThMax = 360;
int iTh;
int iR;
int iMax = -1;
int iThMaxIndex = -1;
int iRMaxIndex = -1;
int *pnArray = NULL;
int nPos = 0;

if (m_bBinary == FALSE)
{
GrayToBinary();
}
pnArray = new int[iRMax * iThMax];//iRMax行,每一行长度是iThMax
memset(pnArray, 0, sizeof(int) * iRMax * iThMax);

for (int y = 0; y < nHeight; y++)
{
nPos = m_nBytesPerLine * y;
for (int x = 0; x < nWidth; x++)
{
if(pbyBuffer[nPos] == 255)
{
for(iTh = 0; iTh < iThMax; iTh++) { iR = (int)(x * cos(iTh * fRate) + y * sin(iTh * fRate)); if(iR > 0)
{
pnArray[iR * iThMax + iTh]++;
}
}
}
nPos++;
} // x
} // y

for(iR = 0; iR < iRMax; iR++)
{
for(iTh = 0; iTh < iThMax; iTh++) { int iCount = pnArray[iR * iThMax + iTh]; if(iCount > iMax)
{
iMax = iCount;
iRMaxIndex = iR;
iThMaxIndex = iTh;
}
}
}

*pnR = iRMaxIndex;
*pfTheta = fRate * iThMaxIndex;

delete [] pnArray;
}

注意nPos 必须按m_nBytesPerLine对齐,因为即使是灰度图像每行的长度也不一定是图像的宽度,如果这个错了,检测结果就完全错了。另外一个是计算出来的iR值必须大于0才有效。
相关博文可以参考这篇关于hough变换的文章,我也说说hough变换

假如你定义了一个位图类,里面包含位图头,位图信息头,调色板,位图数据。然后你按照位图的格式将位图文件读入你的类中,现在你知道了位图的全部信息了。主要信息包含在位图信息头里面,数据则在位图数据缓冲里面。现在的问题是,在Windows下面如何将一张位图画出来,而且现在是如何从数据缓存里面绘画出位图。
一般情况,我们都是直接绘制在dc里面,而不是绑定到子控件,让子控件自己绘画,比如picture控件之类的,我觉得提供绘制在dc里面的接口更具有广泛性。

现在我知道两种从内存数据绘制彩色位图的2种方法。第一种麻烦一点,第二种则相当直接。
方法一:
第一步,用CreateCompatibleDC创建跟目标dc的兼容性内存dc。
第二步,用CreateCompatibleBitmap创建跟目标dc的兼容性位图。
第三步,用SelectObject将第二步创建的兼容位图选入第一步创建的兼容dc中。
第四步,用SetDIBits设置兼容位图的数据缓冲
第五步,用BitBlt将数据从兼容内存dc绘制到目标dc。
第六步,删除兼容位图和兼容dc。
代码如下,其中buffer代表位图数据缓冲。

1
2
3
4
5
6
7
8
9
10
11
HDC hCompatibleDC = CreateCompatibleDC(hDc);
HBITMAP hCompatibleBitmap = CreateCompatibleBitmap(hDc, bitmapinfoheader.biWidth,
bitmapinfoheader.biHeight);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hCompatibleDC, hCompatibleBitmap);
SetDIBits(hDc, hCompatibleBitmap, 0, bitmapinfoheader.biHeight,
buffer, (BITMAPINFO*)&bitmapinfoheader, DIB_RGB_COLORS);
BitBlt(hDc, nStartX, nStartY, bitmapinfoheader.biWidth, bitmapinfoheader.biHeight,
hCompatibleDC, 0, 0, SRCCOPY);
SelectObject(hCompatibleDC, hOldBitmap);
DeleteObject(hCompatibleDC);
DeleteObject(hCompatibleDC);

方法二:直接调用StretchDIBits绘制位图
该函数功能相当强悍,似乎专为从内存数据绘制位图到dc而生。
函数原型如下:
int StretchDIBits(
HDC hdc, // handle to DC
int XDest, // x-coord of destination upper-left corner
int YDest, // y-coord of destination upper-left corner
int nDestWidth, // width of destination rectangle
int nDestHeight, // height of destination rectangle
int XSrc, // x-coord of source upper-left corner
int YSrc, // y-coord of source upper-left corner
int nSrcWidth, // width of source rectangle
int nSrcHeight, // height of source rectangle
CONST VOID lpBits, // bitmap bits
CONST BITMAPINFO
lpBitsInfo, // bitmap data
UINT iUsage, // usage options
DWORD dwRop // raster operation code
);
使用也相当简单,调用

1
2
3
4
StretchDIBits(hDc, nStartX, nStartY, bitmapinfoheader.biWidth,
bitmapinfoheader.biHeight, 0, 0, bitmapinfoheader.biWidth,
bitmapinfoheader.biHeight, buffer, (BITMAPINFO*)&bitmapinfoheader,
DIB_RGB_COLORS, SRCCOPY);

即可了。

区别一:引用必须要指代一个对象,所以引用必须初始化,但是指针不需要。

区别二:存在空指针,但是不存在空引用,引用必须要指代某个对象。

区别三:由于不存在空引用,使用引用时候不需要测试是否有效,但是使用指针则需要保证其是有效的。

区别四:指针可以改变其指向的对象,但是引用不可以,引用一被初始化后就不能改变

区别五:重载[]操作符的时候,由于语法需要,应该返回引用而不是指针。

区别六:从概念上看,指针是一个变量,而引用则是其它变量的别名

区别七:指针是占据内存的,但是引用则不一定会分配内存,引用只是一个别名。

区别八:对指针使用操作符和对引用使用操作符效果不同,对引用使用操作符得到的都是对所指代的对象使用操作符的结果

该函数原型如下,
BOOL SetWindowPos(
HWND hWnd, // handle to window
HWND hWndInsertAfter, // placement-order handle
int X, // horizontal position
int Y, // vertical position
int cx, // width
int cy, // height
UINT uFlags // window-positioning options
);

第一个参数是你要修改的窗口局部,第二个参数是用于修改Z order的,可以用这个参数把你的窗口弄成TopMost之类的窗口,后面四个参数很好理解,最后那个参数是一些标志,具体的请查看msdn。
基本上我觉得使用这个函数有些时候达不到你想要的效果原因是不知道这个函数使用的坐标系其实不是桌面坐标系,也不是当前窗口的客户区坐标系,而是父窗口的客户区坐标系。这个点可能在你移动非子窗口的时候没有什么影响,但是你想移动子窗口的时候就会出问题了,老是达不到你想要的效果那是非常烦人的,而你debug进去发现你的数据又都是对的。当你意识到该函数使用的坐标系的时候,对应的问题就都能够解决了。

我列举2个例子,一个是设置窗口为顶级窗口同时修改为覆盖整个桌面工作区,另一个是移动子窗口对话框到主对话框的客户区左上角。2个例子都在我的代码内部,后面我会提供代码下载。

例子一,设置窗口为顶级窗口同时修改为覆盖整个桌面工作区。
将下面的代码放在你的主对话框的OnInitDialog()函数内部就能够达到例子一的效果了。

1
2
3
4
5
RECT rect;
SystemParametersInfo(SPI_GETWORKAREA, 0, (PVOID)rect, 0);
m_nScreenWidth = rect.right - rect.left;
m_nScreenHeight = rect.bottom - rect.top;
SetWindowPos(wndTop, rect.left, rect.top, m_nScreenWidth, m_nScreenHeight, SWP_SHOWWINDOW);

例子二,移动子窗口对话框到主对话框的客户区左上角。
这个例子的实现比较麻烦,首先修改子对话框的资源文件里面的属性,一定改成child类型的对话框。其次,用非模式对话框创建出子对话框,同时注意把主对话框最为其父窗口。最后只需要在child对话框的OnInitDialog()函数里面调用SetWindowPos(wndTop, 0, 0, 0, 0, SWP_NOSIZE);//移动子窗口,则坐标是相对于父窗口客户区就能达到效果了。这三个操作是缺一不可的。

代码下载地址,实验二

正如大家所知道的那样,多核多cpu越来越普遍了,而且编写多线程程序也是件很简单的事情。在Windows下面,调用CreateThread函数一次就能够以你想要的函数地址新建一个子线程运行。然后,事情确实你发现创建多线程根本没有让程序快多少,也没有提高多少cpu利用率,甚至可能让cpu利用率下降。唯一能够确定的是多线程能够避免界面假死。为什么会是这样的了。本文将举一些例子和讲述一些原因。

首先,我来讲一下多处理的一些知识。如下图所示,

多处理器系统也只有一个待运行的线程队列,内存中也只有一个操作系统拷贝,而且也只有一个内存系统,但是会有多个cpu同时运行不同的线程。一个cpu运行一个线程,那么上图中的系统最多能在同一时间运行2个线程。其实,多处理系统需要掌握的知识不是这些,而是缓存一致性
现在来解释下什么是缓存一致性。由于,还是只有一个内存系统。所有cpu都要和这个内存系统通信,但是只有一条总线,那么这无疑会造成总线紧张,限制整体的速度了。那么,你多个cpu也没多少意义了。解决这个问题的办法还是利用cpu的缓存机制,学过组成原理的同学都知道,cpu的缓存命中率还是很高的,有90%以上吧。那么,我继续利用缓存机制还是可以降低总线的频繁使用的。但是,每个cpu都有自己的缓存。如果有2个cpu的缓存存储的是同一内存数据的内容,其中一个cpu的缓存更新了,另外一个cpu的缓存也必须更新,这就是所谓的缓存一致性。编程多线程程序的一个很重要的一点就是避免因为缓存一致性引起的缓存更新风暴。
现在我举一个缓存更新风暴的例子。
如图所示的类定义,
缓存一致性风暴
锁lockHttp和lockSsl中间只有8个字节,而绝大部分系统上一个缓存行是128个字节,那么这2个锁很可能就处在同一个缓存行上面。那么,最坏的情况会发生什么事情了。假设处理器P1在运行一个处理http请求的线程T1,处理器P2在运行一个处理ssl请求的线程T2,那么当T1获得锁lockHttp的时候,锁的内容就会改变,为了保持缓存一致性,就会更新P2的缓存。那么,T2要获得锁lockssl的时候,发现缓存已经失效了,就必须从内存中重新加载缓存之类。总之,这会将缓存命中率降低到90%以下,引起性能的严重降低。而且发生这种事情的原因是因为我们不了解硬件的体系结构。

多cpu不能成倍提高速度的原因是任务的某些部分是必须串行处理的。比如,矩阵乘法可以分为三个部分,初始化矩阵,相乘,返回结果。这三部分第二部分可以用多线程来处理,第一部分和第三部分则是不可以的。而且第二部分必须在第一部分完成之后,第三部分必须在第一部分完成之后。那么,无论你添加多少个处理器,最快的时间都至少是第一部分和第二部分的时间之和。这个事实好像叫做Amdahl法则。
如果使用多线程,那么就必须考虑线程同步,而线程同步又是导致速度降低的关键。所以下面就讲述一些方法来加快多线程程序的吞吐速度。
方法一,把一个任务分解为多个可以子任务。
因为总有些子任务是可以并发的,多个子任务并发执行了很可能就能够避免cpu需要io操作的完成了,而且能够提高系统的吞吐量。
方法二,缓存多线程的共享数据。
当你已经在使用多线程了,很多时候必须使用共享数据。如果,数据是只读的,那么可以在第一次获取后保存起来,以后就可以重复使用了。但是,第一次的获取还是无法避免的需要线程同步操作的。
方法三,如果线程数目有限,就不要共享数据。
做法是为每一个线程实例化一个单独的数据,其实就是为每一个线程分配一块数据使用。这样没有线程同步操作了,速度可以尽可能的提示。
方法四,如果没办法确定线程数目到底有多少,那么使用部分共享吧。
部分共享其实就是使用多个资源池代替一个资源池,资源池的数目得更加经验来确定。如下图所示,

最后在提一个叫做Thundering Herd的问题,该问题维基百科有定义http://en.wikipedia.org/wiki/Thundering_herd_problem。大意是,当多个线程在等待一个资源的时候,如果事件等待到了,操作系统是唤醒所有等待的线程让它们自己去竞争资源了还是选择一个线程把资源给它。当然唤醒所有的线程肯定开销要大,而且所有没有抢到资源的线程还得重新进入等待状态,这无疑造成很多没必要的操作,浪费了没必要的线程上下文切换。总之,会不会存在Thundering Herd还是跟不同的操作系统有关的。万一存在Thundering Herd了,多线程可能就没那么好办了。

到现在我们知道了为什么多cpu并不能成倍提高程序的速度了。首先因为有些任务无法并行,其次即使是并行cpu之间还是有很多牵制的。本书的内容主要来自提高c++性能的编程技术一书。