Clion 与 C++基础

导言:这篇文章主要讲述了一些C++地基础知识。供以后不时之需

Ubuntu jsoncpp的安装:

apt-get install 的库:

  • 头文件在:/usr/include/
  • lib目录在:/usr/lib/x86_64-linux-gnu
    1
    2
    link_directories(/usr/lib/x86_64-linux-gnu)
    target_link_libraries(C_test ${OpenCV_LIBS} jsoncpp)

将lib目录添加进link中,然后再在目标lib中添加库的名字(lib/中libjsoncpp,只取库的名称)。

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
Json::Value json_temp;      // 临时对象,供如下代码使用

json_temp["name"] = "huchao";

json_temp["age"] = 26;
Json::Value root; // 表示整个 json 对象

root["key_string"] = Json::Value("value_string"); // 新建一个 Key(名为:key_string),赋予字符串值:"value_string"。

root["key_number"] = Json::Value(12345); // 新建一个 Key(名为:key_number),赋予数值:12345。

root["key_boolean"] = Json::Value(false); // 新建一个 Key(名为:key_boolean),赋予bool值:false。

root["key_double"] = Json::Value(12.345); // 新建一个 Key(名为:key_double),赋予 double 值:12.345。

root["key_object"] = json_temp; // 新建一个 Key(名为:key_object),赋予 json::Value 对象值。

root["key_array"].append("array_string"); // 新建一个 Key(名为:key_array),类型为数组,对第一个元素赋值为字符串:"array_string"。

root["key_array"].append(1234); // 为数组 key_array 赋值,对第二个元素赋值为:1234。

Json::ValueType type = root.type(); // 获得 root 的类型,此处为 objectValue 类型。
Json::StyledWriter writer;

ofstream outfile("../test.json", ios::out);
if (!outfile)
{
cerr << "Failed to open the file!";
return;
}
outfile << writer.write(root) << std::endl;
outfile.close();

/* output

{
"key_array" : [ "array_string", 1234 ],
"key_boolean" : false,
"key_double" : 12.345000000000001,
"key_number" : 12345,
"key_object" : {
"age" : 26,
"name" : "huchao"
},
"key_string" : "value_string"
}



*/

构建复杂json文件:

先构建json树,再写代码会更明确一点:

1
2
3
4
5
6
7
8
9
10
"data"|
|--frame_info x 300|--"frame_index"
| |
| |
| |
| |--"skeleton"|--skeleton_info x m|
| | | |
| | | |--"pose"[1,36]
| | | |
| | | |--"score"[1,18]

  • “”扩起来的是键值,未加””的是数组元素,包含了 x号后面的多少个元素,每一个元素包含了后面的键值对。
  • 构建json数据时,需要从树的最深处开始回溯,先将最后一层的叶子节点构造号,然后加入进去。
  • 数组使用append()函数,元素可以不同类型。
  • 临时变量使用完毕后使用clear()进行清除。
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
void VideoIO::saveInfo2json(vector<vector<float> > bodyInfo_frames) {
string tag[]=
{
"Nose","Neck","RShoulder","RElbow","RWrist",
"LShoulder","LElbow","LWrist","RHip","RKnee",
"RAnkle","LHip","LKnee","LAnkle","REye",
"LEye","REar","LEar"
};
// for(int i = 0 ;i < bodyInfo_frames.size(); i++ ){
// vector<float> bodyInfo = bodyInfo_frames[i];
//
// bodys[0] =
//
// }

vector<float> person1_predict_pose = {0.2,0.3,.5,0.3,0.3,0.1,0.2,0.3,.5,0.3,0.3,0.1,0.2,0.3,.5,0.3,0.3,0.1,0.2,0.3,.5,0.3,0.3,0.1,0.2,0.3,.5,0.3,0.3,0.1,0.2,0.3,.5,0.3,0.3,0.1};
vector<float> person1_predict_score = {-0.1,0.5,-0.5,-0.3,0.9,0.1,-0.1,0.5,-0.5,-0.3,0.9,0.1,0.1,0.5,-0.5,-0.3,0.9,0.1};

vector<float> person2_predict_pose = {0.3,0.1,.002,0.8,0.6,0.2,0.1,0.3,.5,0.3,0.3,0.1,0.2,0.3,.5,0.3,0.3,0.1,0.3,0.1,.002,0.8,0.6,0.2,0.1,0.3,.5,0.3,0.3,0.1,0.2,0.3,.5,0.3,0.3,0.1};
vector<float> person2_predict_score = {-0.5231,0.823,-0.321,-0.3332,0.9231,0.554,-0.5231,0.823,-0.321,-0.3332,0.9231,0.554,-0.5231,0.823,-0.321,-0.3332,0.9231,0.554};

vector<vector<float> > vec_predictions = {person1_predict_pose,person2_predict_pose};
vector<vector<float> > vec_scores = {person1_predict_score, person2_predict_score};

Json::Value root;
Json::Value frame_info;
Json::Value skeleton;
Json::Value skeleton_info;
Json::Value data;
Json::Value pose;
Json::Value score;
for(int frame_index = 0;frame_index < 5; frame_index++){
for(int k =0;k < vec_predictions.size(); k++){
for(int i = 0;i < vec_predictions[k].size(); i++){
pose.append(vec_predictions[k][i]);
}
skeleton_info["pose"] = pose;
pose.clear();
for(int i = 0;i < vec_scores[k].size(); i++){
score.append(vec_scores[k][i]);
}
skeleton_info["score"] = score;
score.clear();

skeleton.append(skeleton_info);
skeleton_info.clear();
}
frame_info["skeleton"] = skeleton;
frame_info["frame_index"] = frame_index;
data.append(frame_info);
frame_info.clear();
}
root["data"] = data;


Json::StyledWriter writer;
ofstream outfile("../test.json", ios::out);
if (!outfile)
{
cerr << "Failed to open the file!";
return;
}
outfile << writer.write(root) << std::endl;
outfile.close();
}

Clion中相对路径失效:

使用父级目录,原因:在CLion工程中,编译文件放在/home/djw931017/dup/cmake-build-debug文件夹中,此时使用相对路径./a.txt是在cmake-build-debug文件夹下寻找文件。因此,需要回到代码文件同目录下的相对路径。

C++获取文件下的所有文件名:

该功能类似于python中os.listdir(path)的功能。

以在linux下面为例:

  • DIR *dir 类型的指针只想了目录文件
  • dirent *ptr 可以得到文件的属性信息
  • readdir(dir) 函数可以将目录文件下的所有文件依次读入
  • d_type: 区分文件的标志,ldx文件。
    • d_type = 4 为目录文件
    • d_type = 8 为文件
    • d_type = 10 为连接文件
      这里为了之后读取.avi文件也能使用该接口,所以将文件和目录等同对待。
      1
      2
      3
      else if(ptr->d_type == 8)    ///file
      //printf("d_name:%s/%s\n",basePath,ptr->d_name);
      files.push_back(ptr->d_name);

当然,如果之后遇到文件和目录在同一级中,那么需要将二者分开然后递归。
下面是源码:

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
#ifdef linux
#include <unistd.h>
#include <dirent.h>
#endif
#ifdef WIN32
#include <direct.h>
#include <io.h>
#endif
vector<string> VideoIO::getFilesName(string path) {
vector<string> files;//存放文件名
#ifdef WIN32
_finddata_t file;
long lf;
//输入文件夹路径
if ((lf=_findfirst(path.c_str(), &file)) == -1) {
cout<<cate_dir<<" not found!!!"<<endl;
} else {
while(_findnext(lf, &file) == 0) {
//输出文件名
//cout<<file.name<<endl;
if (strcmp(file.name, ".") == 0 || strcmp(file.name, "..") == 0)
continue;
files.push_back(file.name);
}
}
_findclose(lf);
#endif
#ifdef linux
DIR *dir;
struct dirent *ptr;
char base[1000];
if ((dir=opendir(path.c_str())) == NULL)
{
perror("Open dir error...");
exit(1);
}
while ((ptr=readdir(dir)) != NULL)
{
if(strcmp(ptr->d_name,".")==0 || strcmp(ptr->d_name,"..")==0) ///current dir OR parrent dir
continue;
else if(ptr->d_type == 8) ///file
//printf("d_name:%s/%s\n",basePath,ptr->d_name);
files.push_back(ptr->d_name);
else if(ptr->d_type == 10) ///link file
//printf("d_name:%s/%s\n",basePath,ptr->d_name);
continue;
else if(ptr->d_type == 4) ///dir
{
files.push_back(ptr->d_name);
/*
memset(base,'\0',sizeof(base));
strcpy(base,basePath);
strcat(base,"/");
strcat(base,ptr->d_nSame);
readFileList(base);
*/
}
}
closedir(dir);
sort(files.begin(),files.end());
return files;
#endif
}

返回的文件名是无序的,可以排列后返回。

Clion中使用g++11:

在cmake中添加:

1
set(CMAKE_CXX_STANDARD 14)

Opencv读取视频信息:

opencv 中的 capture类是可以获得视频很多信息的。
使用capture.get()获取数据

  • CV_CAP_PROP_FRAME_COUNT : 总帧数.
  • CV_CAP_PROP_FRAME_HEIGHT: 视频分辨率高度
  • CV_CAP_PROP_FRAME_WIDTH: 视频分辨率宽度
  • CV_CAP_PROP_FRAME_FPS : 视频fps
    样例:
    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
    void VideoIO::loadVideo(string path,string videoName){
    cout<<"正在处理视频: "
    << path
    << " 视频名:"
    << videoName
    << endl;

    cv::VideoCapture capture;
    //读取视频文件
    capture.open(path);
    //判断是否出错
    if (!capture.isOpened()){
    cout << "Load Video Failed" << endl;
    system("pause");
    return;
    }
    //获取视频文件相关信息----总帧数
    long nTotalFrame = capture.get(CV_CAP_PROP_FRAME_COUNT);
    cout << "This video framesNum are:" << nTotalFrame << endl;
    //获取视频相关信息---分辨率(宽、高)
    int frameHeight = capture.get(CV_CAP_PROP_FRAME_HEIGHT);
    int frameWidth = capture.get(CV_CAP_PROP_FRAME_WIDTH);
    cout << "This video is :" << frameWidth << "*" << frameHeight<< endl;
    //获取视频帧率
    double FrameRate = capture.get(CV_CAP_PROP_FPS);
    cout << "The frame rate is:" << FrameRate << endl;
    capture.release();

cv::waitkey()深度解析:

很多时候发现对于waitkey()这个函数的理解并不到位,刚开始学习时只是简单的以为这个是显示图片必须的一行代码,必须紧跟在cv::imshow(“”,)后面,但是后面随着应用的加深,发现waitkey好像和本来理解的不一样。

首先来看官方的解释:

This function should be followed by cv::waitKey function which displays the image for specified milliseconds. Otherwise, it won’t display the image. For example, waitKey(0) will display the window infinitely until any keypress (it is suitable for image display). waitKey(25) will display a frame for 25 ms, after which display will be automatically closed. (If you put it in a loop to read videos, it will display the video frame-by-frame)

意思是:

  • waitKey(0)会永久等待,直到任意键被按下。
  • waitKey(25)会等待25ms,单位是ms计算。
    另外:
  • waitKey()可以返回按下的键的信息使得下面逻辑成立:
    1
    2
    if (char(cv::waitKey(1000 / FrameRate)) == 'q')
    break;

每帧停留的时间中判断是否按键为q,FrameRate可以由capture.get(CV_CAP_PROP_FPS)得到,s/fps-> 每帧保持1000/fps ms,所以在1000/FrameRate的时间中判断按键并且显示该帧的内容,这样帧率会和源视频中一样,这也解释了之前使用opencv加载摄像头存储为新的.avi视频帧率不同的问题,因为没有修改帧率。

C++中类与类之间的关系:

类要在真正要使用时才实例化,比如在一个类的构造函数中实例化其他类是没有必要且会出错的,变量的实例化是在生命周期中才有的,在构造函数执行完后,类中的(其他)类成员将会释放,这时如果是指针访问会出现野指针或者内存泄漏。

所以只有在真正要使用某个类的时候再实例化之,不必过早的将其实例化出来,尽管使用对象(不使用指针,因为对象的话其成员类也是会分配内存的),也会造成内存的浪费。

类的debug函数:

在类中构造一个debug函数会有意想不到的好处,debug函数很简单只用将内部变量_debug开关打开即可,然后每次需要调试的逻辑处利用debug分流,这样会很棒。

C++传参数:

对于某些参数,类似于OpencvMat这样的参数一定要注意是否需要new一个出来,这种情况经常发生在vector vecMat.push_back()的地方,push_back进去的仅是一个引用指针,如果push进去的Mat正在迭代,那么迭代结束后,vecMat里面的东西将会全部相同且等于最后一个push的Mat。

1
2
3
4
5
6
7
cv::Mat frame;
vector<Mat> vecMat;
for (int i = 0; i < count; ++i)
{
frame = cv::imread(path);
vecMat.push_back(frame);
}

上面的代码运行后会发现vecMat全部是最后一个,如果想要将每一帧都加入vec中则需要每次都new一个对象出来,然后将其加入进去。如下:

1
2
3
4
5
6
7
vector<Mat> vecMat;
for (int i = 0; i < count; ++i)
{
cv::Mat frame;
frame = cv::imread(path);
vecMat.push_back(frame);
}

这类问题就类似于向vec中push_back指针,虽然说push_back是复制操作,但是对于每一个新的指针(在值上等于被push进去的指针)都指向了同一个内存单元,而每次迭代修改的就是这个内存单元的值,所以遍历vec时输出的都是这个内存单元最后改变的值。

C++内存管理:

当项目处理数据较大且运行时间长时一定要格外注意内存泄漏的问题。

  • 灵活使用变量的生命周期,{}在大括号内部的变量会随着代码块执行完毕而释放。
  • 函数执行完后会将内存释放

当{}内部使用{}外的变量时一定要格外注意,对于该变量的修改将是持久性的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
vector<Mat> frames;
vio->loadDatasetFile();
int videoNums = vio->PathFilesName_map["videoName"].size();

if(_debug)
videoNums = 2;

int k_init = 0;

if(_debug)
k_init = 1;

for(int k = k_init; k < videoNums; k++){
int current_frame_index = 0;
vector<Skeleton_Info> skeletons;
vio->loadVideoFrames(vio->PathFilesName_map["subVideoPath"][k], vio->PathFilesName_map["videoName"][k], frames);
int framesNum = frames.size();

// Transform the size of frames to the 340x256.
for(int i=0;i< framesNum;i++){
cv::resize(frames[i],frames[i],cv::Size(360,256));
}
}

函数原型:
void VideoIO::loadVideoFrames(string path,string videoName,vector<Mat> & frames)

这段代码中frames泄漏了,因为每一次对于frames的操作都在它原本的基础上进行操作,本身没有释放,所以会一直累加直到内存不足。

这段代码本身是想得到300帧视频帧然后对这300帧迭代进行预测,有一个count记录0-300,count==300时停止循环,所以如果frames在迭代中没有得到释放,那么每次处理的都是frames中的前300帧,即第一个视频中的300帧,出错!

时刻使用frames.clear(),frameMat.clear()等函数来清除内存,并且使用del来释放new出来的对象。

在使用jsoncpp时也需要注意内存:

循环中的json::value对象也需要及时的处理掉:

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
int maxTemporalLength = bodyInfo_frames.size();
for(int frame_index = 0;frame_index < maxTemporalLength; frame_index++){
vector<vector<float> > vec_predictions = bodyInfo_frames[frame_index]["pose"];
vector<vector<float> > vec_scores = bodyInfo_frames[frame_index]["score"];
int peopelNum = vec_predictions.size();
for(int k =0;k < peopelNum; k++){

for(int i = 0;i < vec_predictions[k].size(); i++){
pose.append(vec_predictions[k][i]);
}
skeleton_info["pose"] = pose;
pose.clear();

for(int i = 0;i < vec_scores[k].size(); i++){
score.append(vec_scores[k][i]);
}
skeleton_info["score"] = score;
score.clear();

skeleton.append(skeleton_info);
skeleton_info.clear();
}
frame_info["skeleton"] = skeleton;
//skeleton.clear(); !bug 必须处理掉!需要添加这一行代码!

frame_info["frame_index"] = frame_index;

data.append(frame_info);
frame_info.clear();
}

上面代码skeleton没有clear()掉,造成了数据指数增加。使用完了就要清零,使用完了就要删除的思想!如果下一次循环是需要一个新的无污染的同名迭代变量,那么一定要清除。

opencv Mat.reshape():

函数原型:

1
Mat Mat::reshape(int cn, int rows=0) const

cn: 表示通道数(channels), 如果设为0,则表示保持通道数不变,否则则变为设置的通道数。

rows: 表示矩阵行数。 如果设为0,则表示保持原有的行数不变,否则则变为设置的行数。

reshape()可以将Mat转为指定的shape,但是其本身的存储是按照row来组织,本身看不出chanl的排列,这点可以从下面看出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for(int i=0;i< 36;i++){
vec2.push_back(i);
}
img = img.reshape(4,3);
cout<<"img"<<endl<<img;
/*
[0, 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]
*/
img = img.reshape(0,3);
cout<<"img"<<endl<<img;
/*
[0, 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]
*/

内部的chanl看不出来,其本身的存储与reshape(\*,3)是一样的。但其step是不同的,step的解释参照

总结reshape:以cn为步长在原Mat中按行依次的顺序取数据,按照row进行排列组织。[[0,1],[2,3],[4,5]…],而split();函数正是按照reshape好的数据格式进行切分通道的,即每隔cn个数据取一个。

所以下面两个方式取得的数据是不同的:probMat是一个(46 x 2622)Mat

1
2
3
4
probMat = probMat.reshape(57,46);
std::vector<Mat> chs;
split(probMat, chs);
Mat nose_Mat = chs[1];
1
2
3
4
5
6
7
for (int i = 0; i < 1; i++) {
hmg = probMat(cv::Rect(Point(0, i * 46), Point(46, (i + 1) * 46)));
resize(hmg, hmg, cv::Size(368, 368));
double minVal, maxVal;
Point minP, maxP;
minMaxLoc(hmg, &minVal, &maxVal, &minP, &maxP);
}

第一个代码将其分为57通道的数据,第二个代码也是将其分为57块数据,这两种方法有很大的不同:

  • 第一个是将数据看成了[[[0,1,2,..56],[57,58…113],[…]]],然后每隔通道数取一个组成46x46x57的blob。
  • 第二个是将数据[[0…46x46-1],[46x46…46x46x2-1]…]来组成的。

那么怎么才能使reshape出来的Mat是按顺序的呢?

  1. img = img.reshape(0,cn); 得到每一行为一个通道的值,换句话说,整个通道的值都在一行中
  2. img = img.t(); 转置,使得每一列是同一个通道,每一个通道中的邻接的值下标相差正好为cn个。
  3. img = img.reshape(cn,row); 每隔cn正好得到同一个通道的值。

C++函数重载

在结构体、类中的重载可以不给本身参数:

1
2
3
4
5
6
7
8
struct person{
unsigned int age;
string name;
bool operator > (person & B) const {
return this->age > B.age;
}
person(int ag,string name):age(ag),name(name){};
};

本来>需要两个参数,但是由于本身在结构体中,可以不用传入自身。

运算符的重载:

+、-、×、/

+号一般来说返回的是同一类的数据

1
2
3
4
5
6
7
8
9
10
11
class Box
{
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
}

>、<、==

比较运算符返回的则是bool类型,使条件成立的返回true,注意这种重载可以和algorithm中的sort函数结合使用达到很好的效果。

1
2
3
4
5
6
7
8
struct person{
unsigned int age;
string name;
bool operator > (person & B) const {
return this->age > B.age;
}
person(int ag,string name):age(ag),name(name){};
};

流的重载<<、>>

流操作的重载一般不写在结构体或这类中,因为这类操作通常是在外部进行的,如果写在内部调用时较麻烦,需要A.operator>>()来使用,不能直接使用cout<<A。所以要将其写在全局中。

ostream& operator << (ostream& os, person a){
    os << a.name << "'s age is "<<a.age << endl;
    return os;
}
int main(){
    person a = person(1,"Liu");
    cout<<a;
    return 0;
}