TXT数据转OpenCV中的Mat数据

前言

  本文是以前做的一个小东西的处理前奏,当时也记录过,现在把它翻出来重新看看。那个东西需要利用深度图,Shaun 当时还没拿到 Kinect,就在网上下了一些数据http://eeeweba.ntu.edu.sg/computervision/people/home/renzhou/HandGesture.htm),该数据集包含了彩色图及对应的深度图,但是该数据集没有以图像形式存储深度值,而是用 txt 文本以行列形式存储真正的深度值(单位为 mm),所以并不能直观的看到深度图像,Shaun 需要把这些深度值从 txt 文本提取出来并把它以图像的形式呈现出来,由于需求比较特殊,网上没看到现成的解决的方案,所以 Shaun 只有用现成的轮子自己做一个了。

思路篇

  程序的基本思路是:先找到目录及子目录下的所有 txt 文件路径;再根据路径分别读取 txt 文件,按行读取之后再进行字符串分割提取其中的深度值;为了便于以图像形式显示,将深度值归一化至 0~255 存入 8 位单通道的 Mat 类型数据中,最后以 png 图像形式保存至各个目录。

实现篇

  因为当时还在用 opencv-2.4.11,所以本文所实现的代码是基于 opencv-2.4.11,不过应该只要在 opencv-2.0 版本及以上只要有 Mat 数据结构的都能用,毕竟 Shaun 只用到了 OpenCV 中的 Mat 数据结构。Talk is cheap, show you the code(代码很乱,估计也只用这么一次,所以就没怎么注意了 :-P)。具体 C++ 实现代码为:

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
#include <opencv2/core/core.hpp>  
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

#include <io.h>
#include <direct.h>

#include <fstream>
#include <iostream>
using namespace cv;
using namespace std;

// ******************************************************************
// @refer to [C++文件读写操作(二)逐字符读取文本和逐行读取文本](http://blog.csdn.net/wangshihui512/article/details/8921924)
// [字符串分割(C++)](http://www.cnblogs.com/MikeZhang/archive/2012/03/24/MySplitFunCPP.html)
// [C++读取文件夹中所有的文件或者是特定后缀的文件](http://blog.csdn.net/adong76/article/details/39432467)
// [C/C++ 判断文件夹是否存在以及创建、删除文件夹 windows以及linux通用](http://blog.csdn.net/u012005313/article/details/50688257)
// [Split a string in C++?](http://stackoverflow.com/questions/236129/split-a-string-in-c)
// [Kinect开发学习笔记之(六)带游戏者ID的深度数据的提取](http://blog.csdn.net/zouxy09/article/details/8151044)
// [Depth Map Tutorial](http://www.pages.drexel.edu/~nk752/depthMapTut.html)
// ******************************************************************
// ----- 逐个字符读取文件 --------
void testByChar()
{
fstream testByCharFile;
char c;
testByCharFile.open("./test.txt",ios::in);

while(!testByCharFile.eof())
{
testByCharFile>>c;
cout<<c;
}
testByCharFile.close();
}

// -------- 逐行读取文件 -------------------
void testByLine()
{
char buffer[256];
fstream outFile;
outFile.open("./test.txt",ios::in);

while(!outFile.eof())
{
outFile.getline(buffer, 256, '\n');//getline(char *,int,char) 表示该行字符达到256个或遇到换行就结束
cout<<buffer<<endl;
}
outFile.close();
}

// ------- 分割字符串 --------------
void splitString()
{
char buffer[1280];
fstream outFile;
outFile.open("./test.txt",ios::in);

while(!outFile.eof())
{
outFile.getline(buffer, 1280, '\n');//getline(char *,int,char) 表示该行字符达到1280个或遇到换行就结束
cout<<buffer<<endl;

const char *d = " ,*";
char *p;
p = strtok(buffer, d);
while(p)
{
printf("%s\n", p);
p=strtok(NULL, d);
}
}

outFile.close();
}

// 获取文件夹下指定格式所有文件名
void getAllFormatFiles( string path, string format, vector<string>& files )
{
//文件句柄
long hFile = 0;
//文件信息
struct _finddata_t fileinfo;
string pathName;
if((hFile = _findfirst(pathName.assign(path).append("/*." + format).c_str(),&fileinfo)) != -1)
{
do
{
//如果是目录,迭代之
//如果不是,加入列表
if((fileinfo.attrib & _A_SUBDIR))
{
if(strcmp(fileinfo.name,".") != 0 && strcmp(fileinfo.name,"..") != 0)
{
//files.push_back(p.assign(path).append("/").append(fileinfo.name) );
getAllFormatFiles( pathName.assign(path).append("/").append(fileinfo.name), format, files);
}
}
else
{
files.push_back(pathName.assign(path).append("/").append(fileinfo.name) );
}
}while(_findnext(hFile, &fileinfo) == 0);

_findclose(hFile);
}
}

// http://stackoverflow.com/questions/236129/split-a-string-in-c
// ---- stackoverflow上大神的C++版本分割字符串 --------------------
std::vector<std::string> split(const std::string& text, const std::string& delims)
{
std::vector<std::string> tokens;
std::size_t start = text.find_first_not_of(delims), end = 0;

while((end = text.find_first_of(delims, start)) != std::string::npos)
{
tokens.push_back(text.substr(start, end - start));
start = text.find_first_not_of(delims, end);
}
if(start != std::string::npos)
tokens.push_back(text.substr(start));

return tokens;
}

// 创建文件夹及子文件夹
void makeDir(const string &path)
{
std::vector<std::string> tokens;
std::size_t start = 0, end = 0;
while ((end = path.find('/', start)) != std::string::npos)
{
if (end != start)
{
tokens.push_back(path.substr(0, end));
}
start = end + 1;
}
if (end != start)
{
tokens.push_back(path);
}

vector<string>::const_iterator itp = tokens.begin();
while (itp != tokens.end())
{
if (access(itp->c_str(), 0) == -1) // 判断文件夹是否存在
{
cout<<*itp<<" is not existing"<<endl;
cout<<"now make it"<<endl;
if (mkdir(itp->c_str()) == 0) // 不存在则创建,只能一级一级的创建
{
cout<<"make successfully"<<endl;
}
}
cout << *itp++ <<endl;
}
}

// Txt文件转opencv Mat(txt文件中存的是以行列形式的深度值)
cv::Mat Txt2DepthMat(const string &txtname)
{
cv::Mat result(480, 640, CV_8UC1, cv::Scalar(0));

char buffer[12800]; // 按行读取文件
fstream outFile;
const char *d = ","; // 以,为分割点
char *p; // 分割出的子串
outFile.open(txtname, ios::in);

for (int i = 0; outFile.getline(buffer, 12800, '\n') != NULL && i < result.rows; i++)
{
p = strtok(buffer, d);
for (int j = 0; p && j < result.cols; j++)
{
int realDepth = (atoi(p) & 0xfff8) >> 3; //提取距离信息,高13位
int depth = (int)(256 * realDepth / 0x0fff); //因为提取的信息是距离信息,为了便于显示,这里归一化为0-255
result.at<uchar>(i, j) = cv::saturate_cast<uchar>(depth);
p = strtok(NULL, d);
}
}

outFile.close();

return result;
}

// 以颜色表示深度信息,越暖(红色)越近,越冷(蓝色)越远
cv::Mat Depth2Color(const cv::Mat &depth)
{
cv::Mat result(depth.size(), CV_8UC3, cv::Scalar::all(0));
int tempDepth, depthRed, depthGreen, depthBlue;
for (int i = 0; i < result.rows; i++)
{
for (int j = 0; j < result.cols; j++)
{
tempDepth = 255 - depth.at<uchar>(i, j);
if(tempDepth < 43)
{
depthRed = tempDepth * 6;
depthGreen = 0;
depthBlue = tempDepth * 6;
}
if(tempDepth > 42 && tempDepth < 85)
{
depthRed = 255 - (tempDepth - 43) * 6;
depthGreen = 0;
depthBlue = 255;
}
if(tempDepth > 84 && tempDepth < 128)
{
depthRed = 0;
depthGreen = (tempDepth - 85) * 6;
depthBlue = 255;
}
if(tempDepth > 127 && tempDepth < 169)
{
depthRed = 0;
depthGreen = 255;
depthBlue = 255 - (tempDepth - 128) * 6;
}
if(tempDepth > 168 && tempDepth < 212)
{
depthRed = (tempDepth - 169) * 6;
depthGreen = 255;
depthBlue = 0;
}
if(tempDepth > 211 && tempDepth < 254)
{
depthRed = 255;
depthGreen = 255 - (tempDepth - 212) * 6;
depthBlue = 0;
}
if(tempDepth > 253)
{
depthRed = 255;
depthGreen = 0;
depthBlue = 0;
}
if (tempDepth == 255)
{
depthRed = 0;
depthGreen = 0;
depthBlue = 0;
}

result.at<Vec3b>(i, j)[0] = depthBlue;
result.at<Vec3b>(i, j)[1] = depthGreen;
result.at<Vec3b>(i, j)[2] = depthRed;
}
}
return result;
}

int main(int argc, char *argv[])
{
string filePath = "C:/Users/XXXXXX/Downloads/NTU-Microsoft-Kinect-HandGesture Dataset/Depth";
vector<string> files;
//读取所有文件
string format = "*"; // 不知道为什么在我电脑读不了特定文件?
getAllFormatFiles(filePath, format, files);

for (int i = 0; i < files.size(); i++)
{
cv::Mat tempMat = Txt2DepthMat(files[i]);
files[i].replace(0, 66, "../data");
files[i].replace(files[i].find(".txt"), files[i].length() - 1, ".png");
cout<< files[i] << endl;
string tempString = files[i].substr(0, files[i].find_last_of("/"));
makeDir(tempString);
cv::imwrite(files[i], tempMat);
}
cout << "File Size: " << files.size() << endl;

//cv::imshow("test", Depth2Color(Txt2DepthMat("./1.txt")));
cv::waitKey(0);
return 0;
}

2018-01-01 BTW:以上代码在 VS2010+Win7 下编译运行通过,在 VS2013+Win10 下 for (int i = 0; outFile.getline(buffer, 12800, '\n') != NULL && i < result.rows; i++) 会报错,可能需要改成 for (int i = 0; outFile.getline(buffer, 12800, '\n') && i < result.rows; i++) ,即去掉后面的 != NULL

后记

  正如前言所说,本文是以前记录过的,一些细节也快忘记,这次重写算是回顾一下吧,这段程序可能也确实只用这么一次,但其中用到了不少 C++ 处理字符串和读写文件等相关知识,而这些知识,在以后有极大的可能会再次用到,因此记录 ↖(^ω^)↗。

参考资料

[1] C++文件读写操作(二)逐字符读取文本和逐行读取文本http://blog.csdn.net/shihui512/article/category/1397194

[2] 字符串分割(C++)http://www.cnblogs.com/MikeZhang/category/345894.html

[3] C++读取文件夹中所有的文件或者是特定后缀的文件http://blog.csdn.net/adong76/article/category/1632029

[4] C/C++ 判断文件夹是否存在以及创建、删除文件夹 windows以及linux通用http://blog.csdn.net/u012005313/article/category/5586103

[5] Split a string in C++?http://stackoverflow.com/questions/236129/split-a-string-in-c

[6] Kinect开发学习笔记之(六)带游戏者ID的深度数据的提取http://blog.csdn.net/zouxy09/article/category/1273380

[7] Depth Map Tutorialhttp://www.pages.drexel.edu/~nk752/depthMapTut.html