这里讲的不是pca的原理,我还没仔细去研究过。我刚刚使用过opencv进行pca降维,效果成功过。所以只是记录下方式和注意的地方。当然也参考了网上很多的代码。网上的代码大多大同小异,我的代码当然也和别人的差不多。

首先,我用的是C版本的OpenCV。我不是嫌弃C++版本的,而是C版本的OpenCV本来已经够复杂了,我不想再跟踪C++版本的这些对象的内存了。我们需要使用到OpenCV的CvMat结构存储数据。

我们需要使用以下的CvMat。
CvMat m_pData;//原始样本集
CvMat
m_pMean;//平均值向量、
CvMat m_pEigVals;//协方差矩阵的特征值
CvMat
m_pEigVecs;//协方差矩阵的特征向量
CvMat m_pResult;//PCA降维的结果
CvMat
m_pQueryIn;//输入的一个数据
CvMat* m_pQueryOut;//用样本集的PCA信息降维后的一个数据

首先,我们创建数据m_pData = cvCreateMat(m_nRows, m_nDim, CV_64FC1);然后初始化数据。再创建m_pMean,m_pEigVals,m_pEigVecs。然后调用cvCalcPCA计算平均值向量和协方差矩阵的特征值和特征向量。最后一步是cvProjectPCA,在这个函数里面可以指定维度。我们就可以得到降维之后的数据。

现在再讲查询。首先创建一个数据。m_pQueryIn = cvCreateMat(1, m_nDim, CV_64FC1);m_pQueryOut = cvCreateMat(1, m_nUseDim, CV_64FC1);然后,我们初始化m_pQueryIn。再调用cvProjectPCA(m_pQueryIn, m_pMean, m_pEigVecs, m_pQueryOut);就可以得到降维之后的数据。

下面看下综合起来的代码。

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
if (m_pData)
{
cvReleaseMat(m_pData);
}
m_pData = cvCreateMat(m_nRows, m_nDim, CV_64FC1);
//在这里初始化样本集合
if (m_pMean)
{
cvReleaseMat(m_pMean);
}
m_pMean = cvCreateMat(1, m_nDim, CV_64FC1);
if (m_pEigVals)
{
cvReleaseMat(m_pEigVals);
}
m_pEigVals = cvCreateMat(1, std::min(m_nRows, m_nDim), CV_64FC1);

if (m_pEigVecs)
{
cvReleaseMat(m_pEigVecs);
}
m_pEigVecs = cvCreateMat(std::min(m_nRows, m_nDim), m_nDim, CV_64FC1);

cvCalcPCA(m_pData, m_pMean, m_pEigVals, m_pEigVecs, CV_PCA_DATA_AS_ROW);

if (m_pResult)
{
cvReleaseMat(m_pResult);
}
m_pResult = cvCreateMat(m_nRows, m_nUseDim, CV_64FC1);
cvProjectPCA(m_pData, m_pMean, m_pEigVecs, m_pResult);//现在m_pResult保存的就是降低维度之后的信息

//初始化查询需要使用的数据
if (m_pQueryIn)
{
cvReleaseMat(m_pQueryIn);
}
if (m_pQueryOut)
{
cvReleaseMat(m_pQueryOut);
}
m_pQueryIn = cvCreateMat(1, m_nDim, CV_64FC1);
m_pQueryOut = cvCreateMat(1, m_nUseDim, CV_64FC1);

//从m_nDim降到m_nUseDim维
double* CNeighborSet::Pca(double* pfIn)
{
m_pQueryIn->data.db = pfIn;
cvProjectPCA(m_pQueryIn, m_pMean, m_pEigVecs, m_pQueryOut);//现在pResult保存的是降低维度之后的信息
return m_pQueryOut->data.db;
}

现在还有一个问题,是确定我们到底能够降低到多少维度了。如果维度降低太多,就保持不了足够的信息,就会失真,达不到应用的效果。当然如果效果不对,可以慢慢增大维度,直到够用为止。
当然还有其它办法了。比如说,在代码中计算我们应该保留多少维度就能够保持90%的信息了。下面要给出的就是这个办法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
double fSum = 0.0;
double fSum0 = 0.0;
for (int i = 0; i < m_pEigVals->cols; ++i)
{
fSum += *(m_pEigVals->data.db + i);
}

double fTmp = fSum * m_fPcaRatio;
for (int i = 0; i < m_pEigVals->cols; ++i)
{
fSum0 += *(m_pEigVals->data.db + i);
if (fSum0 >= fTmp)
{
m_nUseDim = i + 1;
break;
}
}

事情的经过是这样的,我在实现一个基于OpenCv的CImage类。我需要实现它的拷贝构造函数,因为我要把它push_back到vector里面去。我过去的一直做法是在赋值操作符中实现真正的操作,在拷贝构造函数中调用。但是在这里却出现了内存错误。

好歹我还写过几年程序,那我就开始了艰辛的调试。经过调试我发现,我的程序一直只进入拷贝构造函数,然后进去赋值操作符,在赋值操作符中我释放了对象原有的内存。错误就出现在了这里。按照我这种写法的理解,push_back是先用默认构造函数构造对象加入vector,然后再用原有拷贝构造函数初始化内容,或者说用赋值操作符初始化内容。

事实上,完全不是这样的。push_back直接添加新的内存,然后对这块新的内存用拷贝构造函数初始化而已。既然这样的话,拷贝构造函数是不能够直接调用赋值操作符的。应该再重新写一个真正的赋值子函数,拷贝构造函数和赋值操作符都调用这个子函数。赋值操作符中多了一个先检查是否同一个对象,如果不是同一个则释放原有内存的过程而已。

具体的可以看,CImage的部分代码。

class CImage
{
public:
    CImage();
    ~CImage();
    CImage(const CImage yxImage);

    CImage operator =(const CImage yxImage);//深度拷贝

private:
    void CopyData(const CImage yxImage);
};
CImage::CImage(void)
{
    m_pImg = NULL;
    m_ppfData = NULL;
    m_pfData = NULL;
}

CImage::CImage(const char* pcszName) 
{
    m_pImg = NULL;
    m_ppfData = NULL;
    m_pfData = NULL;
    Load(pcszName);
}

CImage::~CImage(void)
{
    UnInit();
}

void CImage::CopyData(const CImage yxImage)
{
    m_pImg = cvCloneImage(yxImage.m_pImg);
    m_nRows = m_pImg->height;
    m_nCols = m_pImg->width * m_pImg->nChannels;
    int nLineSize = m_nCols * sizeof(double);
    m_ppfData = new double*[m_nRows];
    m_pfData = new double[m_nRows * m_nCols];
    for (int i = 0; i < m_pImg->height; ++i)
    {
        m_ppfData[i] = m_pfData[i * m_nCols];
        memcpy(m_ppfData[i], yxImage[i], nLineSize);
    }
}

CImage::CImage(const CImage yxImage)//复制构造函数
{
    //*this = yxImage;//不能用=来实现,否则在push_back时候会出现内存错误,因为vector不调用默认构造函数
     //而是先添加对象的内存,再用复制构造函数初始化数据
    CopyData(yxImage);
}

CImage CImage::operator=(const CImage yxImage)
{
    if (this == yxImage)
    {
        return *this;
    }

    UnInit();

    CopyData(yxImage);

    return *this;
}

昨天一直在尝试使用assimp导入obj模型,再把数据导入到cgal的Polyhedron_3中。结果发现,所有的模型都是平滑着色的。好奇怪的现象。。。我尝试了很多次修改着色模式,一点反应都没有。我去掉assimp显示就是对的。后面,怀疑到我在cgal中计算顶点法线的过程。果然是这里出问题了。计算顶点法线的过程中基本上只使用了一个邻接面。也就是cgal里面已经没有原有obj模型的拓扑性质了。所以,顶点法线基本上等于面法线了,效果就成了flat着色了。

这有什么办法了。难道在assimp中计算法线。即使这样,我能够得到正确的顶点法线,但是cgal里面已经没有原有模型的拓扑性质了。我的数据结构还是加载失败的。所以,我不能使用assimp。而且,assimp还不能导入.m模型。虽然,它能导入格式麻烦的obj模型已经很不错了。

下面是我用assimp加载模型,并且导入到cgal中的代码。

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
#include <CGAL/Polyhedron_incremental_builder_3.h>
#include "Enriched_polyhedron.h"
#include <vector>
#include <assimp/Importer.hpp> // C++ importer interface
#include <assimp/scene.h> // Output data structure
#include <assimp/postprocess.h> // Post processing flags

template <class HDS>
class Builder_obj : public CGAL::Modifier_base<HDS>
{
private:
typedef typename HDS::Vertex::Point Point;
typedef typename CGAL::Polyhedron_incremental_builder_3<HDS> Builder;
const char* pcszfileName;
const aiScene* scene;

public:
Builder_obj(const char* pFilename)
{
pcszfileName = pFilename;
}

~Builder_obj() {}

void operator()(HDS hds)
{
Builder builder(hds,true);
builder.begin_surface(3,1,6);

Assimp::Importer importer;
scene = importer.ReadFile(pcszfileName, aiProcessPreset_TargetRealtime_Quality);
if (!scene) {
fprintf (stderr, "ERROR: reading mesh %s\n", pcszfileName);
return;
}

for (unsigned int m_i = 0; m_i < scene->mNumMeshes; m_i++)
{
const aiMesh* mesh = scene->mMeshes[m_i];

for (unsigned int v_i = 0; v_i < mesh->mNumVertices; v_i++)
{
if (mesh->HasPositions())
{
const aiVector3D* vp = (mesh->mVertices[v_i]);
printf (" vp %i (%f,%f,%f)\n", v_i, vp->x, vp->y, vp->z);
builder.add_vertex(Point(vp->x, vp->y, vp->z));
}
}

for (unsigned int j = 0; j < mesh->mNumFaces; ++j)
{
const aiFace face = mesh->mFaces[j];
assert(face.mNumIndices == 3);

builder.begin_facet();
builder.add_vertex_to_facet(face.mIndices[0]);
builder.add_vertex_to_facet(face.mIndices[1]);
builder.add_vertex_to_facet(face.mIndices[2]);
builder.end_facet();
}

}

builder.end_surface();
}
};

template <class kernel, class items>
class Parser_obj
{
public:
typedef typename Enriched_Model::HalfedgeDS HalfedgeDS;
Parser_obj() {}
~Parser_obj() {}

public:
bool read(const char*pFilename,
Enriched_Model *pMesh)
{
CGAL_assertion(pMesh != NULL);
Builder_obj<HalfedgeDS> builder(pFilename);
pMesh->delegate(builder);
return true;
}
};

代码确实非常简洁,但是不能到达要求,又有什么办法了。如果谁有什么解决办法的,欢迎回复啊。。。所以,我打算自己实现一个适用于cgal的导入obj模型(我只导入顶点),.m模型,.off模型等的类吧,以方便以后扩充和重复使用吧。

最近用了点时间学习了QT,重新阅读了下OpenGL超级宝典第四版,并且基本阅读完了。坑爹的是看完了第四版居然在网上发现了已经可以下载第五版的中文版了。翻阅了下第五版的目录,发现作者已经把GPU编程融合到整本书里面去了。而不像第四版和第三版那样只是分出几张做些介绍而已。我以前在图书馆借了本OpenGL编程宝典第三版,居然只有第三版(PS:我堂堂大中南的图书馆也该更新下了,真不明白图书馆买那么多不需要的办公室软件书籍作甚,整个图书馆只能索引到3本qt相关的书籍),里面居然还有用汇编语言形式的GPU底层着色操作。这个在第四版就去掉了。第五版我没有仔细阅读。以后有时间的话,还是阅读下第五版的内容吧。接下来,也不打算继续看Qt的东西了,毕竟界面编程这种东西,看多了没意义。我毕竟用了几年的MFC,看qt这种封装的太好的东西跟看手册一样的,所以我还是不需要继续去背书了。。。
所以,接下来可能学习下着色语言的所谓橙皮书。也可能阅读英文原版的,毕竟我还没看过一本英文原版的书籍,也提高一下英语。还有要看看微分几何的书籍了。如果还有时间的话,看看数字图像处理,以及matlab的一些东西。
我还是回到标题吧。。。
前段时间,用MFC做了个交互式分割模型的程序,我用的CSplitWnd切分主窗口为三个视图。做这个界面的过程中,遇到最恶心的问题是,我的一个基于对话框的视图无法很好的自放缩子控件,也就是自动调整控件大小。不要跟我说在OnSize里面处理下就行了。不要告诉我再禁止掉滚动条。总之很难达到想要的结果。而且,代码会乱成一团,本来就不爽mfc框架生成的那么多代码。不要以为我在这里喷MFC,我毕竟是学MFC过来的,用了很多次。我以前的界面99%都是MFC做的。可以说是MFC养大了我,但是我慢慢发现我还是对MFC越来越不满了。
所以,我就去看Qt了。同学介绍有个豆子的空间里面关于qt的教程不错,我就把前面的大部分看了一遍,并且都把代码提取出来,全部整合到一个工程里面实验了一下。总之,发现qt还不错吧。至少对于前面的那个问题,qt里面非常好解决,因为它有水平布局和垂直布局的概念。
至于Qt和Opengl的结合,也非常简单。直接继承QGLWidget,实现几个函数就行了。qt5还提供了增强版本的3d类QGLView。这里我要说的是libQglviewer。这个是在qt上的一个增强opengl渲染C++库。封装了很多默认的功能,非常强大。cgal上面的qt框架的3d显示就是推荐的这个东西。
至于模型加载,我打算试试Assimp,用这个加载模型之后,提取出数据,再来初始化cgal的数据结构。这样就不用处理各种烦人的obj,m格式等等了。。。
估计下一个需要渲染的东西就是用这些东西实现了吧。

这里讲得纹理合成指的是合成图片纹理,也就是二维纹理合成,并不是指将纹理直接合成到三维模型上面。虽然从本质上面来说,三维纹理合成和二维纹理是一样的。
纹理合成基本上是用的非参数化的采样方法,也就是Li-Yi-Wei的那篇纹理合成的论文所使用的方法。基本思路就是为当前要合成的像素在样图中寻找一个最佳的邻域,至于这个邻域的形状是可以变化的。合成像素选取的顺序也是可以变化的。这些或多或少都会影响合成的最终结果。LiYiWei的论文里面选取的是L形状的邻域,并且使用扫描线的顺序。
在LiYiWei的三维纹理合成论文居然又提到了合成顺序对合成结果影响不大,这个好像没那么正确。因为,合成纹理像素的使用,需要匹配邻域,而且这些邻域最好是已经合成的像素,如果不是就不会得到较好的结果。这个是最关键的一点。所以说,合成顺序对合成结果还是有一定的影响的。
为什么L邻域能够得到较好的结果了,那是因为扫描线顺序下,L邻域里面的像素都是已经合成的像素。所以,如果打算对合成纹理算法进行局部的修改就需要注意邻域和合成顺序的设定,必须尽可能使用已经合成的像素。
为什么说三维纹理的合成和二维纹理的合成,本质是一致的了?因为三维纹理合成需要局部铺平,来构造邻域,然后的匹配和二维的思想是完全一致的。
下面是我自己实现代码的合成结果。纹理合成的代码网上还是能够找到的,虽然不是那么好找,这也只限于二维合成的代码。最好不要看csdn上面那个自以为写了加速的代码。
样本纹理:
合成纹理:

本来这个东西我是用CxImage加载了多种格式的图片,结果今天发现在vs2010的debug模型下有内存溢出,调试了半天才找到是它的原因,所以非常不爽,换了GDI+的实现方法。该控件支持加载多种格式的图片,并且在增加的项内部存储的图片路径。重要的一点是必须在控件的OnDestroy里面释放申请的ItemData,否则退出程序时候还是能检测到内存泄露。这个也是找了半天才找到的。郁闷之。
关于该控件类的具体实现过程,有点复杂。使用到是比较简单,绑定到CListCtrl控件上。用InitCtrl初始化,用AddImage添加。
该类还支持多选和全选,以及删除所有的选择。代码如下:

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
#pragma once
// CImageListCtrl

class CImageListCtrl : public CListCtrl
{
DECLARE_DYNAMIC(CImageListCtrl)

public:
CImageListCtrl();
virtual ~CImageListCtrl();
CImageList m_ImageList;
WCHAR m_char16ImgName[MAX_PATH];

protected:
DECLARE_MESSAGE_MAP()

public:
// 添加图片
int AddImage(CString imgPath);
void DelImage(CString imgPath);
void RemoveAllImg(void);
// 获得当前显示所有图片的路径
void GetImgPathList(CStringList strListPath);
BOOL InitCtrl(int nX, int nY);
void DeleteSelItem(void);

private:
int m_nX;
int m_nY;
int m_nNum;

public:
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnDestroy();
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
};
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
// ImageListCtrl.cpp : 实现文件
//

#include "stdafx.h"
#include "CxImage/ximage.h"
#include "ImageListCtrl.h"

// CImageListCtrl

IMPLEMENT_DYNAMIC(CImageListCtrl, CListCtrl)

CImageListCtrl::CImageListCtrl()
{
m_nNum = 0;
}

CImageListCtrl::~CImageListCtrl()
{
m_ImageList.DeleteImageList();
}

BEGIN_MESSAGE_MAP(CImageListCtrl, CListCtrl)
ON_WM_CREATE()
ON_WM_DESTROY()
ON_WM_KEYDOWN()
END_MESSAGE_MAP()

// CImageListCtrl 消息处理程序

int CImageListCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CListCtrl::OnCreate(lpCreateStruct) == -1)
return -1;

return 0;
}

BOOL CImageListCtrl::InitCtrl(int nX, int nY)
{
m_nX = nX;
m_nY = nY;
SetExtendedStyle(LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES);//LVS_EX_CHECKBOXES
if(!m_ImageList.Create(nX, nY, ILC_COLOR24, 1, 1))
{
return FALSE;
}

DeleteAllItems();
m_ImageList.SetBkColor(RGB(125, 125, 0));
if(SetImageList(m_ImageList, LVSIL_NORMAL) == NULL)
{
return FALSE;
}
return TRUE;
}

// 添加图片
/*
int CImageListCtrl::AddImage(CString imgPath)
{
int nEndLen = imgPath.GetLength() - imgPath.ReverseFind('.') - 1;
CString strEnd = imgPath.Right(nEndLen);
strEnd.MakeLower();
if(strEnd.Compare(_T("jpg")) == 0 || strEnd.Compare(_T("gif")) == 0
|| strEnd.Compare(_T("bmp")) == 0 || strEnd.Compare(_T("png")) == 0
|| strEnd.Compare(_T("tga")) == 0 || strEnd.Compare(_T("tiff")) == 0
|| strEnd.Compare(_T("exif")) == 0 || strEnd.Compare(_T("wmf")) == 0
|| strEnd.Compare(_T("emf")) == 0)
{
CxImage image;
//应用CXImage载入图像,本程序是相对路径
image.Load(imgPath);
if (image.IsValid() == false)
{
return 0;
}

image.Resample(m_nX, m_nY, 2);
CDC *pDC = GetDC();//应用CXImage在内存中生产位图
HBITMAP hBit = image.MakeBitmap(pDC->GetSafeHdc());
CBitmap bmp;
bmp.Attach(hBit);
int nResult = m_ImageList.Add(bmp, RGB(255, 255, 255));
bmp.Detach();
ReleaseDC(pDC);

int nIndex = imgPath.ReverseFind(_T('\\'));
CString strFileName = imgPath.Right(imgPath.GetLength() - (nIndex + 1));
InsertItem(m_nNum, strFileName, nResult);
SetItemData(m_nNum, (DWORD_PTR)strdup(imgPath));

RedrawItems(m_nNum, m_nNum);
m_nNum++;

return 1;
}

return 0;
}
*/

int CImageListCtrl::AddImage(CString imgPath)
{
int nEndLen = imgPath.GetLength() - imgPath.ReverseFind('.') - 1;
CString strEnd = imgPath.Right(nEndLen);
strEnd.MakeLower();
if(strEnd.Compare(_T("jpg")) == 0 || strEnd.Compare(_T("gif")) == 0
|| strEnd.Compare(_T("bmp")) == 0 || strEnd.Compare(_T("png")) == 0
|| strEnd.Compare(_T("tga")) == 0 || strEnd.Compare(_T("tiff")) == 0
|| strEnd.Compare(_T("exif")) == 0 || strEnd.Compare(_T("wmf")) == 0
|| strEnd.Compare(_T("emf")) == 0)
{
int nStrLen = imgPath.GetLength();
char* pCharBuf = imgPath.GetBuffer(0);
imgPath.ReleaseBuffer();
int nLen = MultiByteToWideChar(CP_ACP, 0, pCharBuf, nStrLen, NULL, 0);
MultiByteToWideChar(CP_ACP, 0, pCharBuf, nStrLen, m_char16ImgName, nLen);
m_char16ImgName[nLen] = '\0'; //添加字符串结尾,注意不是len+1

Gdiplus::Bitmap* pImage = new Gdiplus::Bitmap(m_char16ImgName, true);
if (pImage == NULL)
{
return 0;
}
Gdiplus::Bitmap* pThumbnail = (Gdiplus::Bitmap*)pImage->GetThumbnailImage(m_nX, m_nY);
HBITMAP hBmp;
pThumbnail->GetHBITMAP(Gdiplus::Color(255, 255, 255), hBmp);

CBitmap bmp;
bmp.Attach(hBmp);
int nResult = m_ImageList.Add(bmp, RGB(255, 255, 255));
bmp.Detach();
int nIndex = imgPath.ReverseFind(_T('\\'));
CString strFileName = imgPath.Right(imgPath.GetLength() - (nIndex + 1));

InsertItem(m_nNum, strFileName, nResult);
SetItemData(m_nNum, (DWORD_PTR)strdup(imgPath));
RedrawItems(m_nNum, m_nNum);

delete pThumbnail;
delete pImage;
m_nNum++;

return 1;
}

return 0;
}

void CImageListCtrl::DelImage(CString imgPath)
{
int nCount = GetItemCount();
for (int i = 0; i < nCount; i++)
{
char *pData = (char *)GetItemData(i);
if(pData != NULL)
{
CString str;
str.Format(_T("%s"), pData);
str = str.Trim();
if(str.CompareNoCase(imgPath) == 0)
{
free(pData);
DeleteItem(i);
break;
}
}
}
}

void CImageListCtrl::RemoveAllImg(void)
{
int nCount = GetItemCount();
for (int i=0;i<nCount;i++)
{
char *pData = (char *)GetItemData(i);
if(pData!=NULL)
free(pData);
}
DeleteAllItems();
m_nNum = 0;
}

// 获得当前显示所有图片的路径
void CImageListCtrl::GetImgPathList(CStringList strListPath)
{
int nCount = GetItemCount();
strListPath.RemoveAll();
for (int i=0;i<nCount;i++)
{
char *pData = (char *)GetItemData(i);
if(pData!=NULL)
{
CString str;
str.Format(_T("%s"),pData);
str = str.Trim();
strListPath.AddTail(str);
}
}
}

void CImageListCtrl::OnDestroy()
{
int nCnt = GetItemCount();
for (int i = 0; i < nCnt; i++)
{
char *pData = (char *)GetItemData(i);
if (pData)
{
delete [] pData;
}
}

CListCtrl::OnDestroy();
}

//删除所有选中的项
void CImageListCtrl::DeleteSelItem(void)
{
POSITION pos = GetFirstSelectedItemPosition();
while(pos != NULL)
{
int nItem = GetNextSelectedItem(pos);
TRACE1("Item %d was selected!\n", nItem);

char *pData = (char *)GetItemData(nItem);
if(pData != NULL)
{
free(pData);
}

DeleteItem(nItem);
pos = GetFirstSelectedItemPosition();
--m_nNum;
}

Arrange(LVA_ALIGNLEFT);
}

void CImageListCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if (nChar == VK_DELETE)
{
DeleteSelItem();
}
else if ((nChar == 'a' || nChar == 'A') GetKeyState(VK_CONTROL))
{
for (int i = m_nNum - 1; i >= 0; --i)
{
SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
}
}

CListCtrl::OnKeyDown(nChar, nRepCnt, nFlags);
}

实现效果如图:

关于MFC方面的问题,欢迎加群:87236798交流。

今天在做一个缩略图的控件的时候发现,CXImage库在VS2010的debug模型下有内存泄露,这可是我查错查了几个小时最后得出的结论。瞬间觉得很不爽,就不用CxImage了。觉得加载不同种格式的需求,GDI+应该能够满足我的要求。那么久果断改为GDI+了。GDI+我也没使用过,果断只能搜索其用法了。
后面发现只需要使用Gdiplus::Bitmap就可以处理多种格式的图片。虽然该类叫做位图,实际上可以处理多种格式,据说从Gdiplus::Image继承而来。
nsp; 使用GDI+据说在vs2010下也不需要初始化该库,vc6和vs2008下面需要初始化。初始化也很简单,在App里面包含个成员ULONG_PTR m_gdiplusToken,在InitInstance里面加上Gdiplus::GdiplusStartupInput gdiplusStartupInput;Gdiplus::GdiplusStartup(m_gdiplusToken, gdiplusStartupInput, NULL);在ExitInstance里面加上Gdiplus::GdiplusShutdown(m_gdiplusToken);就行了。
由于GDI+使用的是Unicode,而我一般用的是多字节工程,所以还需要将图片名字转换为Unicode。具体过程是先把图片名字转换为Unicode,再用Gdiplus::Bitmap类的对象bitmap加载图片,然后锁定图片数据,申请一块数据从bitmap内部提取出像素数据,然后释放图片数据,剩下的就是ogl的操作了。
代码如下:

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
#ifndef XPC_YX_TEXTURE_H
#define XPC_YX_TEXTURE_H

class CxImage;

class CTexture
{
public:
CTexture() { m_byData = NULL; m_szTexName[0] = 0; }
CTexture(const char* pcszFileName) { m_byData = 0; LoadFromFile(pcszFileName); }
~CTexture();
int LoadFromFile(const char* pcszFileName);
void Init2DTex();
void Init1DVertTex();

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

#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
#include "stdafx.h"
#include "Texture.h"
#include "CxImage/ximage.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

//该函数只处理了没有压缩的格式
/*int CTexture::LoadFromFile(const char* pcszFileName)
{
if (pcszFileName != NULL)
{
strcpy(m_szTexName, pcszFileName);
}
CxImage image;

image.Load(pcszFileName);
if (image.IsValid() == false)
{
return 0;
}

m_nWidth = image.GetWidth();
m_nHeight = image.GetHeight();
if (m_byData)
{
delete m_byData;
m_byData = NULL;
}
m_byData = new BYTE[m_nWidth * m_nHeight * 3];
for (int i = 0; i < m_nHeight; ++i)
{
for (int j = 0; j < m_nWidth; ++j)
{
RGBQUAD rgb = image.GetPixelColor(j, i);
m_byData[(i * m_nWidth + j) * 3] = rgb.rgbRed;
m_byData[(i * m_nWidth + j) * 3 + 1] = rgb.rgbGreen;
m_byData[(i * m_nWidth + j) * 3 + 2] = rgb.rgbBlue;
}
}

for (int i = 0; i < m_nHeight; i += 5)
{
for (int j = 0; j < m_nWidth; ++j)
{
m_byData[(i * m_nWidth + j) * 3] = 0;
m_byData[(i * m_nWidth + j) * 3 + 1] = 0;
m_byData[(i * m_nWidth + j) * 3 + 2] = 0;
}
}

return 1;
}*/

int CTexture::LoadFromFile(const char* pcszFileName)
{
if (pcszFileName != NULL)
{
strcpy(m_szTexName, pcszFileName);
}

int nStrLen = strlen(pcszFileName);
int nLen = MultiByteToWideChar(CP_ACP, 0, pcszFileName, nStrLen, NULL, 0);
MultiByteToWideChar(CP_ACP, 0, pcszFileName, nStrLen, m_char16ImgName, nLen);
m_char16ImgName[nLen] = '\0'; //添加字符串结尾,注意不是len+1*/

Gdiplus::Bitmap image(m_char16ImgName);

m_nWidth = image.GetWidth();
m_nHeight = image.GetHeight();
Gdiplus::Rect rect(0, 0, m_nWidth, m_nHeight);
Gdiplus::BitmapData bitmapData;
image.LockBits(rect, Gdiplus::ImageLockModeRead | Gdiplus::ImageLockModeWrite,
PixelFormat24bppRGB, bitmapData);

if (m_byData)
{
delete m_byData;
m_byData = NULL;
}
m_byData = new BYTE[m_nWidth * m_nHeight * 3];

BYTE* pByte = (BYTE*)bitmapData.Scan0;
int nOffset = bitmapData.Stride - m_nWidth * 3;
for (int i = 0; i < m_nHeight; ++i)
{
for (int j = 0; j < m_nWidth; ++j)
{
m_byData[(i * m_nWidth + j) * 3] = pByte[0];
m_byData[(i * m_nWidth + j) * 3 + 1] = pByte[1];
m_byData[(i * m_nWidth + j) * 3 + 2] = pByte[2];
pByte += 3;
}
pByte += nOffset;
}
image.UnlockBits(bitmapData);

return 1;
}

void CTexture::Init2DTex()
{
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_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

int nRet = gluBuild2DMipmaps(GL_TEXTURE_2D, 3, m_nWidth, m_nHeight, GL_RGB, GL_UNSIGNED_BYTE,
m_byData);
if (nRet != 0)
{
AfxMessageBox("加载纹理失败");
}
}

void CTexture::Init1DVertTex()
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, m_uTexName);
glBindTexture(GL_TEXTURE_1D, m_uTexName);

glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

BYTE* pbyData = new BYTE[m_nHeight * 3];
int nAdd = m_nWidth / 2;
for (int i = 0; i < m_nHeight; ++i)
{
pbyData[i * 3] = m_byData[(i * m_nWidth + nAdd) * 3];
pbyData[i * 3 + 1] = m_byData[(i * m_nWidth + nAdd) * 3 + 1];
pbyData[i * 3 + 2] = m_byData[(i * m_nWidth + nAdd) * 3 + 2];
}

int nRet = gluBuild1DMipmaps(GL_TEXTURE_1D, 3, m_nHeight, GL_RGB, GL_UNSIGNED_BYTE,
pbyData);
if (nRet != 0)
{
AfxMessageBox("加载纹理失败");
}

delete pbyData;
}

CTexture::~CTexture()
{
if (m_byData)
delete m_byData;
}

现在,在vs2010和vs2008下,不需要使用额外的库就能处理多种格式的纹理了。下面是一个贴图效果,用的是一维纹理显示了sdf值在模型顶点上的分布。

处理多种格式本身就是件麻烦的事情。但是使用cximage的话,就可以完美解决这个问题了。cximage可以处理多种常见格式的图片,比如bmp,png,jpg等。
那么如何使用任意尺寸的图片了。glTexImage2D必须使用长宽二次方的图片,使用非二次方的图片,必须用glTexSubImage2D进行处理,具体方法,可以参考 OpenGL使用长宽非二次方纹理 。
那么如何避免使用glTexSubImage2D了。我们可以使用函数gluBuild2DMipmaps来完成以上两个函数完成的工作,还不需要对纹理坐标进行放缩。这是一件非常方便的事情。
下面提供我的纹理类。使用方法很简单。先调用加载图片函数,再调用初始化纹理函数,可以选择二维和一维垂直方向的。然后,在绘制的时候使用纹理就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef XPC_YX_TEXTURE_H
#define XPC_YX_TEXTURE_H

class CTexture
{
public:
CTexture() { m_byData = NULL; m_szTexName[0] = 0; }
CTexture(const char* pcszFileName) { m_byData = 0; LoadFromFile(pcszFileName); }
~CTexture();
int LoadFromFile(const char* pcszFileName);
void Init2DTex();
void Init1DVertTex();

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

#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
#include "stdafx.h"
#include "Texture.h"
#include "../CxImage/ximage.h"
#include <stdlib.h>
#include <stdio.h>
#pragma comment(lib, "cximage.lib")

int CTexture::LoadFromFile(const char* pcszFileName)
{
if (pcszFileName != NULL)
{
strcpy(m_szTexName, pcszFileName);
}
CxImage image;
image.Load(pcszFileName);
if (image.IsValid() == false)
{
return 0;
}

m_nWidth = image.GetWidth();
m_nHeight = image.GetHeight();
if (m_byData)
{
delete m_byData;
}
m_byData = new BYTE[m_nWidth * m_nHeight * 3];
for (int i = 0; i < m_nHeight; ++i)
{
for (int j = 0; j < m_nWidth; ++j)
{
RGBQUAD rgb = image.GetPixelColor(j, i);
m_byData[(i * m_nWidth + j) * 3] = rgb.rgbRed;
m_byData[(i * m_nWidth + j) * 3 + 1] = rgb.rgbGreen;
m_byData[(i * m_nWidth + j) * 3 + 2] = rgb.rgbBlue;
}
}

return 1;
}

void CTexture::Init2DTex()
{
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_MODULATE);

int nRet = gluBuild2DMipmaps(GL_TEXTURE_2D, 3, m_nWidth, m_nHeight, GL_RGB, GL_UNSIGNED_BYTE,
m_byData);
if (nRet != 0)
{
AfxMessageBox("加载纹理失败");
}
}

void CTexture::Init1DVertTex()
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, m_uTexName);
glBindTexture(GL_TEXTURE_1D, m_uTexName);

glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

BYTE* pbyData = new BYTE[m_nHeight * 3];
int nAdd = m_nWidth / 2;
for (int i = 0; i < m_nHeight; ++i)
{
pbyData[i * 3] = m_byData[(i * m_nWidth + nAdd) * 3];
pbyData[i * 3 + 1] = m_byData[(i * m_nWidth + nAdd) * 3 + 1];
pbyData[i * 3 + 2] = m_byData[(i * m_nWidth + nAdd) * 3 + 2];
}

int nRet = gluBuild1DMipmaps(GL_TEXTURE_1D, 3, m_nHeight, GL_RGB, GL_UNSIGNED_BYTE,
pbyData);
if (nRet != 0)
{
AfxMessageBox("加载纹理失败");
}

delete pbyData;
}

CTexture::~CTexture()
{
if (m_byData)
delete m_byData;
}

要使用该类,还得配置CxImage。下面这张图片是使用一维垂直纹理的效果。

这篇论文讲的是交互式模型分割。用户用画笔在模型上面绘制出一笔,然后获得相关的分割区域。
这篇论文的思路大致分为三步。第一步,对所有顶点或者面计算sdf值。第二步,将笔画投影作为前景,估计一个混合高斯模型,另外随机选取1000个顶点或面估计一个高斯混合模型。第三步,利用sdf值和高斯混合模型,进行graph-cut。
关于sdf值和graph-cut都可以阅读论文引用的文章从而得到知晓。sdf值的计算可以到提出该观点的作者主页上面下载相关软件。但是该软件只能计算面的sdf值,不过可以再映射到顶点上面。graph-cut的应用更为广泛,这个也能到Yuri Y. Boykov主页上下载相关代码,然后使用即可。
更困难的事情是确定graph-cut的参数,这个需要多次调整。
下面是实现效果。

第一步,先把鼠标轨迹点逆投影到三维世界的观察坐标系。
第二步,把第一步获得的三维坐标点集合投射到模型上面,获得笔画,就是模型内部的一些相互连接的边。
第三步,沿着第二步获得那些相互连接的边进行欧拉操作分裂边。可能需要多分割几次才会分割出模型的一部分。
第四步,搜索整个模型,对不同的部分进行标记

下面是实现的一些效果。绿色线是鼠标轨迹,红色线是投影到模型上面的边的连接线。


下面具体解释下以上几个步骤。
步骤一,比较简单,网上代码一堆,不过要自己注意理解。

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
glGetIntegerv(GL_VIEWPORT, m_nViewPort);
glGetDoublev(GL_PROJECTION_MATRIX, m_fProjmatrix);
glGetDoublev(GL_MODELVIEW_MATRIX, m_fMvmatrix);

float fWinX, fWinY, fWinZ;
int nX, nY;
double fX, fY, fZ;

m_pt3ds.clear();
for (int i = 0; i < m_ptStroke.size(); ++i)
{
nX = m_ptStroke[i].x;
nY = m_nViewPort[3] - m_ptStroke[i].y - 1;
glReadBuffer(GL_BACK);
glReadPixels(nX, nY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, fWinZ);
//if (fabs(fWinZ - 1.0) > 1e-8)
{
fWinX = nX;
fWinY = nY;
gluUnProject(fWinX, fWinY, fWinZ, m_fMvmatrix, m_fProjmatrix, m_nViewPort,
fX, fY, fZ);
m_pt3ds.push_back(Point_3(fX, fY, fZ));
}
}
m_ptStroke.clear();

该代码就是把m_ptStroke的点转化为观察坐标系的点m_pt3ds。
步骤二,大致思路如下:第一步,把所有鼠标经过的点,都投影回观察空间。第二步,找到离第一个鼠标点p[0],最近的模型内的顶点pClosed。第三步,循环剩余的鼠标点,如果当前鼠标点离pClosed的相邻顶点更近的话,那么将pClosed更新为上一次找到的pClosed的相邻顶点。所有的这些,pClosed点就是笔画的投影点。

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
FILE* fpLog = fopen("log.txt", "w");
char szStr[MAX_PATH];
CString str;
str.Format("%d", pt3ds.size());
//AfxMessageBox(str);
sprintf(szStr, "pt3ds.size():%d\n", pt3ds.size());
fprintf(fpLog, "%s", szStr);

if (pt3ds.size() == 0)
{
return;
}

double fX = CGAL::to_double(pt3ds[0].x());
double fY = CGAL::to_double(pt3ds[0].y());
double fZ = CGAL::to_double(pt3ds[0].z());
double fX1, fY1, fZ1;

//获得离第一个投影点最近的halfedge_handle
Halfedge_handle heClosed = facets_begin()->halfedge();
double fDiff = 1e10;
for(Face_iterator pf = facets_begin();
pf != facets_end(); pf++)
{
Halfedge_handle he = pf->halfedge();

do
{
Point_3 p = he->vertex()->point();
fX1 = CGAL::to_double(p.x());
fY1 = CGAL::to_double(p.y());
fZ1 = CGAL::to_double(p.z());
double fTemp = (fX1 - fX) * (fX1 - fX) + (fY1 - fY) * (fY1 - fY)
+ (fZ1 - fZ) * (fZ1 - fZ);
if (fTemp < fDiff)
{
fDiff = fTemp;
heClosed = he;
}
he = he->next();
}while(he!= pf->halfedge());
}

vector<Halfedge_handle> vh;
vh.push_back(heClosed);
for (unsigned i = 1; i < pt3ds.size(); ++i)
{
sprintf(szStr, "处理第:%d个笔画点\n", i);
fprintf(fpLog, szStr);

fX = CGAL::to_double(pt3ds[i].x());
fY = CGAL::to_double(pt3ds[i].y());
fZ = CGAL::to_double(pt3ds[i].z());
sprintf(szStr, "笔画点坐标为:%f,%f,%f\n", fX, fY, fZ);
fprintf(fpLog, szStr);

Halfedge_handle next = heClosed;
Point_3 p = heClosed->vertex()->point();

fX1 = CGAL::to_double(p.x());
fY1 = CGAL::to_double(p.y());
fZ1 = CGAL::to_double(p.z());
sprintf(szStr, "heClosed坐标为:%f,%f,%f\n", fX1, fY1, fZ1);
fprintf(fpLog, szStr);

fDiff = (fX1 - fX) * (fX1 - fX) + (fY1 - fY) * (fY1 - fY)
+ (fZ1 - fZ) * (fZ1 - fZ);

Vertex_handle v = heClosed->vertex();
Halfedge_around_vertex_circulator he, end;
he = end = v->vertex_begin();
CGAL_For_all(he, end)
{
Halfedge_handle heTemp = he->opposite();
p = heTemp->vertex()->point();
fX1 = CGAL::to_double(p.x());
fY1 = CGAL::to_double(p.y());
fZ1 = CGAL::to_double(p.z());

double fTemp = (fX1 - fX) * (fX1 - fX) + (fY1 - fY) * (fY1 - fY)
+ (fZ1 - fZ) * (fZ1 - fZ);

if (fTemp < fDiff heClosed != he)
{
sprintf(szStr, "heTemp坐标为:%f,%f,%f\n", fX1, fY1, fZ1);
fprintf(fpLog, szStr);

sprintf(szStr, "fDiff:%f,fTemp:%f\n", fDiff, fTemp);
fprintf(fpLog, szStr);

if (std::find(vh.begin(), vh.end(), heTemp) != vh.end())
{
continue;
}

//if (vh.size() != 1 heClosed->facet() == heTemp->facet())
{
// continue;
}
fDiff = fTemp;
next = heTemp;
//sprintf(szStr, "next坐标为:%f,%f,%f\n", fX1, fY1, fZ1);
//fprintf(fpLog, szStr);
}
}

//去掉已经出现过的顶点
if (next != heClosed)
{
heClosed = next;
vh.push_back(heClosed);
}
}

glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glColor3f(1.0, 0.0, 0.0);
glLineWidth(3.0);
glBegin(GL_LINE_STRIP);
for (int i = 0; i < vh.size(); ++i)
{
glVertex3d(CGAL::to_double(vh[i]->vertex()->point().x()),
CGAL::to_double(vh[i]->vertex()->point().y()),
CGAL::to_double(vh[i]->vertex()->point().z()));
sprintf(szStr, "i:%d, x:%f,y:%f,z:%f\n", i,
CGAL::to_double(vh[i]->vertex()->point().x()),
CGAL::to_double(vh[i]->vertex()->point().y()),
CGAL::to_double(vh[i]->vertex()->point().z()));
fprintf(fpLog, szStr);
}
glEnd();
glFlush();

步骤三是欧拉操作。我的思路是将经过的边都变成边界边,方便第四步的搜索。首先,split_edge。然后,把沿着边的相邻的两个面沿着原来的边split_facet,再删除多余的面。那么,该经过的边就会变成边界边。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (int i = 1; i < vh.size(); ++i)
{
Halfedge_handle hBegOne = vh[i]->next()->next();
fprintf(fpLog ,"处理i:%d\n", i);
split_edge(vh[i]);

Halfedge_handle hBegTwo = vh[i]->opposite()->next();
Halfedge_handle hEndOne = vh[i];
Halfedge_handle hEndTwo = hBegTwo->next()->next();
split_facet(hBegOne, hEndOne);
split_facet(hBegTwo, hEndTwo);
erase_facet(hEndOne);
erase_facet(hBegTwo);
fprintf(fpLog, "is border:%d\n", hBegOne->next()->is_border_edge());
}

步骤五,对整个模型的面进行dfs即可了。代码如下:

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
void SetComponent()
{
for (Facet_iterator i = facets_begin(); i != facets_end(); ++i)
{
i->component(-1);
}

int nComponent = 1;
for (Facet_iterator i = facets_begin(); i != facets_end(); ++i)
{
if (i->component() == -1)
{
DfsComponent(i, nComponent);
nComponent++;
}

}

}

void DfsComponent(Facet_iterator i, int nComponent)
{
CString str;
str.Format("处理第:%d个面", i->tag());
//AfxMessageBox(str);
i->component(nComponent);

Halfedge_around_facet_circulator j = i->facet_begin();

do {
Halfedge_handle hfTemp = j;
if (hfTemp->is_border_edge())
{
//AfxMessageBox("找到边界边");
continue;
}
Halfedge_handle hf = j->opposite();
Facet_handle fh = hf->facet();

if (fh->component() == -1)
{
DfsComponent(fh, nComponent);
}
} while ( ++j != i->facet_begin());

}

因为每个面里面都有个代表属于哪个组件的标记,这样就实现了模型的分割。利用该方法就能实现分割出狗的腿和头部等等。

代码分享链接:链接: http://pan.baidu.com/s/1eQ50rf0 密码: 7f6v