过程基本分四步或者三步走。第一步,加载图片。第二步,灰度化图片。第三步,Canny边缘化,第四步,检测直线或者圆。
首先,说明下OpenCv表示图片的结构是IplImage。这个东西代表了很多有用的东西。接下来的几步,都有该结构的指针对应图片。
第一步,加载图片。可以直接加载成灰度图,或者加载成彩色图,或者加载为原有图片的格式,第二步再灰度化。
直接加载为灰度图,
IplImage* imgZero = cvLoadImage(szZero, CV_LOAD_IMAGE_GRAYSCALE);
加载为彩色图,则把cvLoadImage函数的第二个参数改为CV_LOAD_IMAGE_COLOR,改为CV_LOAD_IMAGE_ANYCOLOR则保持图片原有格式。
第二步,如果第一步不是按灰度图加载,那么在这一步需要灰度化。代码如下,
IplImage *imgGray = NULL;imgGray = cvCreateImage(cvGetSize(imgZero),IPL_DEPTH_8U,1);cvCvtColor(imgZero, imgGray ,CV_BGR2GRAY);

这几句代码的意思是根据原图片大小创建单通道位深为8的图片,然后把原有图片转换为创建的单通道8位深灰度图片。
第三步,canny处理。代码如下,
IplImage *imgCanny = cvCreateImage(cvGetSize(imgZero ),IPL_DEPTH_8U,1);cvCanny(imgGray , imgCanny, 50, 100);
这几句代码的意思是根据原图片大小创建单通道位深为8的图片,然后对灰度图片进行canny处理,处理结果存储在新建立的imgCanny图片中。
第四步,使用识别函数进行识别直线或者圆。
识别直线的函数是cvHoughLines2。识别圆的函数是cvHoughCircles。进行识别之前,先创建存储区。
CvMemStorage *storage = cvCreateMemStorage(0);

识别直线的代码如下,

1
2
3
4
5
6
7
8
9
10
cvClearMemStorage(storage);
CvPoint* line;
CvSeq* lines;
lines = cvHoughLines2(imgCanny, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 50, 50, 10 );
line = (CvPoint*)cvGetSeqElem(lines, 0);
cvLine(imgZero, line[0], line[1], CV_RGB(255,0,0), 3, CV_AA, 0 );
cout<<"端点1:"<< line[0].x << "," << line[0].y<<endl;
cout<<"端点2:"<< line[1].y << "," << line[1].y<<endl;
pts[1].fX = line[0].x;
pts[1].fY = line[0].y;

识别圆的代码如下,
1
2
3
4
5
6
7
8
9
10
11
12
cvClearMemStorage(storage);
CvSeq * cir=NULL;
cir = cvHoughCircles(imgCanny, storage, CV_HOUGH_GRADIENT, 1, imgRecog->width/10 ,80,40, 50);
float * p=(float *)cvGetSeqElem(cir, 0);
CvPoint pt = cvPoint(cvRound(p[0]),cvRound(p[1]));
cvCircle(imgRecog,pt,cvRound(p[2]),CV_RGB(0,255,0));
cvCircle(imgRecog, pt, 5, CV_RGB(0,255,0), -1, 8, 0 );//绘制圆心
cout<<"圆心:"<<pt.x<<","<<pt.y<<endl;
cout<<"半径:"<<p[2]<<endl;
pts[0].fX = pt.x;
pts[0].fY = pt.y;
fR = p[2];

下面再提供一个识别圆和直线的程序的完整代码。识别直线和圆的参数设置,需要自己调节。
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
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <math.h>
#include <iostream>
#include <fstream>
using namespace std;

#pragma comment(lib, "opencv_core220.lib")
#pragma comment(lib, "opencv_highgui220.lib")
#pragma comment(lib, "opencv_imgproc220.lib")

struct Point
{
double fX;
double fY;
};

Point pts[4];
double fR;

int main()
{
//打开输出文件
ofstream outf("out.txt");
//获取cout默认输出
streambuf *default_buf=cout.rdbuf();
//重定向cout输出到文件
cout.rdbuf( outf.rdbuf() );

char* szZero = "仪表盘0.bmp";
char* szOne = "仪表盘1.bmp";
char* szRecog = "仪表盘.bmp";
IplImage* imgZero = cvLoadImage(szZero, 1);
if (!imgZero) return -1;
IplImage* imgOne = cvLoadImage(szOne, 1);
if (!imgOne) return -1;
IplImage* imgRecog = cvLoadImage(szRecog, 1);
if (!imgRecog) return -1;

IplImage *imgEdge = NULL;
imgEdge = cvCreateImage(cvGetSize(imgRecog),IPL_DEPTH_8U,1);
IplImage *imgCanny = cvCreateImage(cvGetSize(imgRecog),IPL_DEPTH_8U,1);

CvMemStorage *storage = cvCreateMemStorage(0);//内存采用默认大小

cvCvtColor(imgZero, imgEdge,CV_BGR2GRAY);
cvCanny(imgEdge, imgCanny, 50, 100);
//hough变化检测刻度0直线
cvClearMemStorage(storage);
CvPoint* line;
CvSeq* lines;
lines = cvHoughLines2(imgCanny, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 50, 50, 10 );
line = (CvPoint*)cvGetSeqElem(lines, 0);
cvLine(imgZero, line[0], line[1], CV_RGB(255,0,0), 3, CV_AA, 0 );
cout<<"端点1:"<< line[0].x << "," << line[0].y<<endl;
cout<<"端点2:"<< line[1].y << "," << line[1].y<<endl;
pts[1].fX = line[0].x;
pts[1].fY = line[0].y;

cvCvtColor(imgOne, imgEdge,CV_BGR2GRAY);
cvCanny(imgEdge, imgCanny, 50, 100);
//hough变化检测刻度1直线
cvClearMemStorage(storage);
lines = cvHoughLines2(imgCanny, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 50, 50, 10 );
line = (CvPoint*)cvGetSeqElem(lines, 0);
cvLine(imgOne, line[0], line[1], CV_RGB(255,0,0), 3, CV_AA, 0 );
cout<<"端点1:"<< line[0].x << "," << line[0].y<<endl;
cout<<"端点2:"<< line[1].y << "," << line[1].y<<endl;
pts[2].fX = line[0].x;
pts[2].fY = line[0].y;

cvCvtColor(imgRecog, imgEdge,CV_BGR2GRAY);
cvCanny(imgEdge, imgCanny, 50, 100);
//hough变化圆检测
cvClearMemStorage(storage);
CvSeq * cir=NULL;
cir = cvHoughCircles(imgCanny, storage, CV_HOUGH_GRADIENT, 1, imgRecog->width/10 ,80,40, 50);
float * p=(float *)cvGetSeqElem(cir, 0);
CvPoint pt = cvPoint(cvRound(p[0]),cvRound(p[1]));
cvCircle(imgRecog,pt,cvRound(p[2]),CV_RGB(0,255,0));
cvCircle(imgRecog, pt, 5, CV_RGB(0,255,0), -1, 8, 0 );//绘制圆心
cout<<"圆心:"<<pt.x<<","<<pt.y<<endl;
cout<<"半径:"<<p[2]<<endl;
pts[0].fX = pt.x;
pts[0].fY = pt.y;
fR = p[2];

//hough变化检测指针直线
lines = cvHoughLines2(imgCanny, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 50, 50, 10 );
line = (CvPoint*)cvGetSeqElem(lines, 0);
cvLine(imgRecog, line[0], line[1], CV_RGB(255,0,0), 3, CV_AA, 0 );
cout<<"端点1:"<< line[0].x << "," << line[0].y<<endl;
cout<<"端点2:"<< line[1].y << "," << line[1].y<<endl;
pts[3].fX = line[0].x;
pts[3].fY = line[0].y;

static const double PI = atan(1.0) * 4;
double fKZero = (pts[1].fY - pts[0].fY) / (pts[1].fX - pts[0].fX);
double fThetaZero = 180.0 / PI * atan(fKZero);
if (pts[1].fX + 10 < pts[0].fX)//左半部分
{
fThetaZero += 180.0;
}
fThetaZero += 360.0;
if (fThetaZero > 360.0)
{
fThetaZero -= 360.0;
}

double fKOne = (pts[2].fY - pts[0].fY) / (pts[2].fX - pts[0].fX);
double fThetaOne = 180.0 / PI * atan(fKOne);
if (pts[2].fX + 10 < pts[0].fX)//左半部分
{
fThetaOne += 180.0;
}
fThetaOne += 360.0;
if (fThetaOne > 360.0)
{
fThetaOne -= 360.0;
}

double fKRecog = (pts[3].fY - pts[0].fY) / (pts[3].fX - pts[0].fX);
double fThetaRecog = 180.0 / PI * atan(fKRecog);
if (pts[3].fX + 10 < pts[0].fX)//左半部分
{
fThetaRecog += 180.0;
}
fThetaRecog += 360.0;
if (fThetaRecog > 360.0)
{
fThetaRecog -= 360.0;
}

char szAns[100];
sprintf(szAns, "0刻度的角度为:%f,1刻度的角度为:%f,识别刻度的角度为:%f,识别结果为:%.2f",
fThetaZero, fThetaOne, fThetaRecog, (fThetaRecog - fThetaZero) / (fThetaOne - fThetaZero));
cout << szAns << endl;

cvNamedWindow("检测", CV_WINDOW_AUTOSIZE);
cvShowImage("检测", imgRecog);
cvNamedWindow("刻度0", CV_WINDOW_AUTOSIZE);
cvShowImage("刻度0", imgZero);
cvNamedWindow("刻度1", CV_WINDOW_AUTOSIZE);
cvShowImage("刻度1", imgOne);
cvWaitKey(0);

cvDestroyWindow("检测");
cvDestroyWindow("刻度0");
cvDestroyWindow("刻度1");
cvReleaseImage(&imgRecog);
cvReleaseImage(&imgZero);
cvReleaseImage(&imgOne);
cvReleaseImage(&imgEdge);
cvReleaseImage(&imgCanny);
cvReleaseMemStorage(&storage);
}

再给出一个识别图片内所有直线的代码,当然不可能有这么好的效果,
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
#include <opencv/cv.h>
#include <opencv/highgui.h>
#include <math.h>

#pragma comment(lib, "opencv_core220.lib")
#pragma comment(lib, "opencv_highgui220.lib")
#pragma comment(lib, "opencv_imgproc220.lib")

int main()
{
char* szPic = "pic_6.jpg";
IplImage* imgOrigin = cvLoadImage(szPic, 1);
if (!imgOrigin) return -1;

IplImage *imgEdge = NULL;
imgEdge = cvCreateImage(cvGetSize(imgOrigin),IPL_DEPTH_8U,1);
IplImage *imgCanny = cvCreateImage(cvGetSize(imgOrigin),IPL_DEPTH_8U,1);

CvMemStorage *storage = cvCreateMemStorage(0);//内存采用默认大小

cvCvtColor(imgOrigin, imgEdge,CV_BGR2GRAY);
cvCanny(imgEdge, imgCanny, 50, 100);

//hough变化检测刻度0直线
cvClearMemStorage(storage);
CvPoint* line;
CvSeq* lines;
lines = cvHoughLines2(imgCanny, storage, CV_HOUGH_PROBABILISTIC, 1, CV_PI/180, 50, 10, 10 );
for (int i = 0; i < lines->total; ++i)
{
line = (CvPoint*)cvGetSeqElem(lines, i);
cvLine(imgOrigin, line[0], line[1], CV_RGB(255,0,0), 1, CV_AA, 0 );
}

cvNamedWindow("检测", CV_WINDOW_AUTOSIZE);
cvShowImage("检测", imgOrigin);
cvWaitKey(0);
cvDestroyWindow("检测");
cvReleaseImage(&imgOrigin);
cvReleaseImage(&imgEdge);
cvReleaseImage(&imgCanny);
cvReleaseMemStorage(&storage);
}

写这个程序的目的最初是给别人做个作业,最后那个人也不知道要不要,很郁闷。本来不怎么想写的,去网上下了别人的代码,后面发现完全不对头,就拿来做了大幅度的修改,不过使用了下载的图片作为纹理。
这个例子最大的用处是使用OpenGL怎么自动产生球体纹理,自己计算球体的纹理坐标毕竟麻烦了点,虽然也不难。当然还有加载纹理,以及光照,视角设定,特殊键处理等的方法。可以使用上下键放缩和左右键移动。由于使用的是别人的代码修改的,自己感觉一坨糟,也不想去改好了。总之,要模拟出个漂亮的太阳系没那么容易,我的这个比网上有些下载的貌似稍微强点。
先上张图片,再把代码和纹理都贡献了,不过得传到百度网盘了,提供链接。

对于靠近太阳的行星,可以按上键靠近,对于贴了地球纹理那个球体,还可以看到地球的样子了。有兴趣的完全可以把这个太阳系改得再漂亮点。
百度网盘下载

这应该是用vc在程序中添加视频音频播放器最方便的方法了。但是,你如果只是用vs2010或者其它版本工具插入activex控件,然后生成代码,那就不一定能够得到你想要的全部功能。
wmplayer貌似还有个对应的wm sdk。这个可以用com接口的形式更加底层的控制播放器。但是,如果我们能够得到相关的实现播放器功能的代码,我们也能够得到相同程度的控制。前段时间,在网上找了很久,终于找到一大堆有个wmplayer的类代码。用其中的CWMPPlayer4类绑定播放器就能够得到很强大的控制了,用这个类的成员函数可以得到其他控制和设置类的接口等。
用这个东西最大的缺陷是无法播放内存数据,如果是内存数据,必须生成临时文件。最大的优点是简单方便,功能强大,还可以在不同版本的windows平台兼容,只要上面有默认的wmplayer播放器。这个东西还可以播放网络数据,因为文件其实是通过url索引的。因此,在要求简单的时候,还可以用这个东西快速实现一个网络播放器
下面提供wmplayer相关类的代码,WMP

屏蔽右键和全屏都可以在PreTranslateMessage这个虚函数里面处理,比如if (pMsg->message == WM_LBUTTONDBLCLK)
return TRUE;返回TRUE就不会对这个消息进行处理了。屏蔽右键也可以通过调用m_mediaPlayer.put_enableContextMenu(FALSE);处理。
最奇葩的是屏蔽当前播放的是哪个文件的信息,居然有人奇葩到提出这样的需求。找来找去都没有这样的接口,我都直接看wmsdk的文档了,最后想出了个奇葩的办法,就是用一个static控件遮住。
到现在觉得,最好的操作wmplayer的方式是下载微软的wmsdk,从而直接用接口操作,可以提供最大限度的控制。

至于vc内嵌浏览器的方式有很多种,vs2008就可以使用htmldialog了,使用htmlview也行,但是个人觉得还是插入activex控件,然后再绑定变量来的舒服。我就是用插入activex控件再绑定变量的方式的,记得使用vc6就会生产类CWebBrowser2,但是这个类使用起来并不是那么的舒服,所以我对它再进行了一次继承。
注意到,CWebBrowser2继承自CWnd,所以我们继承自CWebBrowser2得类CxExplorer就必须支持mfc里面的动态创建机制,总之这个也很简单,加几句宏就行了,原理其实也不需要弄得太清楚,把mfc的原理弄得太清楚实在太费时间了。。。
我创建的这个浏览器类,除了提供基类的接口外,还提供了几个方便自动填表之类操作的接口。比如,获取text的值,写text的值,点击按钮或者复选框,执行js函数等的功能,由于我基本也就只用过这些功能,所以也就弄了几个这样的接口,也没过多区关注其原理等。
其实,我也没学过太多的com知识,只看过一本com技术内幕的书,更别说com的模板ATL了。所以啊,很多知识也不是很了解。用内嵌浏览器也玩过几次,以前没怎么发掘它的强大应用了。。。
其实,内嵌浏览器很多很好的应用,比如说可以作为客户端,由于内嵌了浏览器,所以不需要实现太多的客户端逻辑,而且服务端直接写web页面就行了,不过速度要求不能太高。还有自动填写表之类的应用,还有写网页游戏的自动玩的功能应该都能发掘出来吧。。。
关于如何判断网页是否加载完成的方法,网上一直说用OnDocumentComplete事件,其实还可能出现网页离线的状态,但是我试了几个方法都检测不出来。所以,最好还是用OnTimer检测和设置你想要的东西。

下面提供这几个类的下载,我用的使用方式是DDX,不使用vs生成的类还可以保持代码的一致性。explorer

使用位图纹理主要需要注意2个地方。1个是非二次方纹理的处理,这个可以参考我上一篇文章的。另外一个是需要注意位图数据的格式,24位的是BGR而不是RGB,32位的是BGRA,而不是RGBA,所以用glTexImage2D之类的函数需要注意把format改成GL_BGR_EXT或者GL_BGRA_EXT,但是internalformat必须是GL_RGB或者GL_RGBA,如果没有进行这个处理的话,颜色效果会变掉的。
下面我给出我的一个任务里面所实现的bitmap位图类,我加载数据的时候使用了以前实现的类CxBitmap,这个也可以从我的另一篇文章中得到。整个类还是比较简单的,你也可以修改加载文件的代码,从而不需要使用CxBitmap类。

CBitmapTex类的定义如下:

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
#ifndef XPC_YX_BITMAPTEX_H
#define XPC_YX_BITMAPTEX_H
#include

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

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

private:
int m_nWidth;
int m_nHeight;
int m_nBytesPerPixel;
BYTE* m_byData;
char m_szTexName[MAX_PATH];
unsigned m_uTexName;
double m_fTexScaleX;
double m_fTexScaleY;
};

#endif

CBitmapTex的实现如下,

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
#include "bitmapTex.h"
#include "CxBitmap.h"
#include "tool.h"
#include
#include
#include

//该函数只处理了没有压缩的格式
int CBitmapTex::LoadBitmapTex(const char* pcszFileName)
{
CxBitmap* pBitmap = new CxBitmap;
int i;
int nBytesPerLine;

strcpy(m_szTexName, pcszFileName);
if (pBitmap->LoadBitmap(pcszFileName) == NULL)
{
return 0;
}
m_nWidth = pBitmap->GetWidth();
m_nHeight = pBitmap->GetHeight();
m_nBytesPerPixel = pBitmap->GetBytesPerPixel();
m_byData = (BYTE*)malloc(m_nWidth * m_nHeight * m_nBytesPerPixel);
if (m_byData == NULL)
{
return 0;
}
nBytesPerLine = m_nWidth * m_nBytesPerPixel;
if (pBitmap->GetBytesPerLine() == nBytesPerLine)
{
//拷贝数据
memcpy(m_byData, pBitmap->GetBuffer(), m_nHeight * m_nWidth * m_nBytesPerPixel);
}
else//注意文件内部的bitmap数据已经对齐,但是opengl纹理的数据不能对其,所以必须一行行拷贝
{ //否则会出现纹理变形
for (i = 0; i < m_nHeight; ++i) { memcpy(m_byData + i * nBytesPerLine, pBitmap->GetBuffer()
+ i * pBitmap->GetBytesPerLine(), nBytesPerLine);//拷贝数据
}
}

delete pBitmap;

return 1;
}

void CBitmapTex::InitTex()
{
int nFormat;
int nWidthPowerOfTwo = ::Pow2Big(m_nWidth);
int nHeightPowerOfTwo = ::Pow2Big(m_nHeight);

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, m_uTexName);
glBindTexture(GL_TEXTURE_2D, m_uTexName);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

if (m_nBytesPerPixel == 3)
{
nFormat = GL_BGR_EXT;
}
else if (m_nBytesPerPixel == 4)
{
nFormat = GL_BGRA_EXT;//bitmap内部数据是bgra格式,所以输入格式必须变化下
}

if (nWidthPowerOfTwo == m_nWidth nHeightPowerOfTwo == m_nHeight)
{
//输出格式必须是GL_RGB,写成GL_BGR_EXT会出现空白
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, nWidthPowerOfTwo, nHeightPowerOfTwo,
0, nFormat, GL_UNSIGNED_BYTE, m_byData);
m_fTexScaleX = m_fTexScaleY = 1.0;
}
else
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nWidthPowerOfTwo, nHeightPowerOfTwo,
0, nFormat, GL_UNSIGNED_BYTE, NULL);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_nWidth, m_nHeight,
nFormat, GL_UNSIGNED_BYTE, m_byData);
m_fTexScaleX = 1.0 * m_nWidth / nWidthPowerOfTwo;
m_fTexScaleY = 1.0 * m_nHeight / nHeightPowerOfTwo;
}
}

void CBitmapTex::SetTexture()
{
glBindTexture(GL_TEXTURE_2D, m_uTexName);
//设置纹理堆栈
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glScaled(m_fTexScaleX, m_fTexScaleY, 1.0);
}

注意到我的实现中使用了CxBitmap类,你可以从这篇文章中得到。还使用tool.h中的一个函数Pow2Big,该然后是返回一个刚好比输入参数大的2的次方大小,实现很简单。
该类只需要先调用LoadBitmapTex读入数据之后,再调用InitTex初始化opengl的一些纹理设置,最后需要使用哪个纹理,就用哪个纹理对象调用SetTexture函数就行了,非常方便。

也许在某些显卡上面,直接用glTexImage2D是可以使用非2次方纹理的。但是在我的机子上,直接用出现运行错误了。有没有办法让我们的程序同时支持2次方纹理和非二次方纹理了,并且我们还不需要改变纹理坐标了。
有,我们可以判断纹理的宽和高是不是都为2次方的。如果都是,那么直接使用就行了。如果不是,我们先用glTexImage2D申请2次方的纹理,然后用glTexSubImage2D把我们的数据放进去,再计算纹理坐标的变化因子,最后用glMatrixMode进入纹理堆栈操作,用glScaled设置坐标变化因子,这一切做完之后就可以按照非二次方纹理使用了。
下面提供部分代码,大致可以看出操作方法。

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
if (m_nBytesPerPixel == 3)
{
nFormat = GL_BGR_EXT;
}
else if (m_nBytesPerPixel == 4)
{
nFormat = GL_BGRA_EXT;//bitmap内部数据是bgra格式,所以输入格式必须变化下
}

if (nWidthPowerOfTwo == m_nWidth nHeightPowerOfTwo == m_nHeight)
{
//输出格式必须是GL_RGB,写成GL_BGR_EXT会出现空白
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, nWidthPowerOfTwo, nHeightPowerOfTwo,
0, nFormat, GL_UNSIGNED_BYTE, m_byData);
}
else
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, nWidthPowerOfTwo, nHeightPowerOfTwo,
0, nFormat, GL_UNSIGNED_BYTE, NULL);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_nWidth, m_nHeight,
nFormat, GL_UNSIGNED_BYTE, m_byData);
fTexScaleX = 1.0 * m_nWidth / nWidthPowerOfTwo;
fTexScaleY = 1.0 * m_nHeight / nHeightPowerOfTwo;
//设置纹理堆栈
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glScaled(fTexScaleX, fTexScaleY, 1.0);
}

这是我寒假的一个作业吧,到最后基本是在学校完成的了。这个程序主要完成的功能是读取.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}

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