【opencv】运行opencv3.4中的demo--facial_features.cpp
1.运行opencv3.4中的demo--facial_features.cpp
2.详解opencv中的CommandLineParser类
facial_features.cpp是一个检测人脸、眼睛、鼻子、嘴巴的cpp-demo,涉及到命令行操作,用的是opencv中的CommandLineParser类。facial_features.cpp在..\opencv3_4\opencv\sources\samples\cpp\facial_features.cpp 目录下。
1.运行opencv3.4中的demo--facial_features.cpp
【操作方法】
1.配置属性
配置属性==>调试==>命令参数:hebe2.jpg haarcascade_frontalface_alt.xml -eyes=haarcascade_eye.xml -noses=haarcascade_mcs_nose.xml -mouth=haarcascade_mcs_mouth.xml 注意: 1.每个命令参数之间用空格隔开; 2.上述路径实际是参数的路径,而不只是参数名称。例如,上述第一个参数 hebe2.jpg(实际上我将该图片文件放置在工程文件目录下了,所以可以省略路径) 如果不会设置相对文件路径,就直接将文件绝对路径写下来即可。 例如,hebe2.jpg的存放位置为"d:\opencv3_4\data\hebe2.jpg",则可以将上述第一个参数写为d:\opencv3_4\data\hebe2.jpg 同理,第二个参数的绝对路径为:"d:\opencv3_4\xml\haarcascade_frontalface_alt.xml" ,则可将第二个参数写为:d:\opencv3_4\xml\haarcascade_frontalface_alt.xml 用绝对路径的配置方法为: 配置属性==>调试==>命令参数:d:\opencv3_4\data\hebe2.jpg d:\opencv3_4\xml\haarcascade_frontalface_alt.xml -eyes=d:\opencv3_4\xml\haarcascade_eye.xml -noses=d:\opencv3_4\xml\haarcascade_mcs_nose.xml -mouth=d:\opencv3_4\xml\haarcascade_mcs_mouth.xml
运行程序的参数打印输出的结果如下:
2.修改程序
将源程序中的利用命令行获取路径的方式修改一下:
把这两行替换掉: input_image_path = parser.get<string>(0);
face_cascade_path = parser.get<string>(1);
替换为:input_image_path = argv[1];
face_cascade_path = argv[2];
//input_image_path = parser.get<string>(0); //face_cascade_path = parser.get<string>(1); input_image_path = argv[1]; face_cascade_path = argv[2];
因为,若运行原程序中的获取路径方式,会报错如下:(在没搞清楚原因的情况下,改为上述方式,可使得程序正常运行。)
3.运行结果:
从结果看,face、eyes、nose、mouth都检测出来了。
【程序代码+注释】
//facial_features.cpp: 定义控制台应用程序的入口点。 #include "stdafx.h" /* * Author: Samyak Datta (datta[dot]samyak[at]gmail.com) * * A program to detect facial feature points using * Haarcascade classifiers for face, eyes, nose and mouth * */ #include "opencv2/objdetect.hpp" #include "opencv2/highgui.hpp" #include "opencv2/imgproc.hpp" #include <iostream> #include <cstdio> #include <vector> #include <algorithm> using namespace std; using namespace cv; // Functions for facial feature detection static void help(); static void detectFaces(Mat&, vector<Rect_<int> >&, string); static void detectEyes(Mat&, vector<Rect_<int> >&, string); static void detectNose(Mat&, vector<Rect_<int> >&, string); static void detectMouth(Mat&, vector<Rect_<int> >&, string); static void detectFacialFeaures(Mat&, const vector<Rect_<int> >, string, string, string); string input_image_path; string face_cascade_path, eye_cascade_path, nose_cascade_path, mouth_cascade_path; int main(int argc, char** argv) { cv::CommandLineParser parser(argc, argv, "{eyes||}{nose||}{mouth||}{help h||}"); // 构造函数初始化 for (int i = 0;i < argc;i++) //打印参数信息 { cout << argv[i] << endl; } if (parser.has("help")) { help(); return 0; } //input_image_path = parser.get<string>(0); //face_cascade_path = parser.get<string>(1); input_image_path = argv[1]; face_cascade_path = argv[2]; eye_cascade_path = parser.has("eyes") ? parser.get<string>("eyes") : ""; //通过参数名称获取 参数 nose_cascade_path = parser.has("nose") ? parser.get<string>("nose") : ""; mouth_cascade_path = parser.has("mouth") ? parser.get<string>("mouth") : ""; if (input_image_path.empty() || face_cascade_path.empty()) { cout << "IMAGE or FACE_CASCADE are not specified"; return 1; } // Load image and cascade classifier files Mat image; image = imread(input_image_path); //读取图片 // Detect faces and facial features vector<Rect_<int> > faces; detectFaces(image, faces, face_cascade_path); //检测人脸 detectFacialFeaures(image, faces, eye_cascade_path, nose_cascade_path, mouth_cascade_path); //检测人脸特征 imshow("Result", image); waitKey(0); return 0; } static void help() //包含程序的使用说明和例子 { cout << "\nThis file demonstrates facial feature points detection using Haarcascade classifiers.\n" "The program detects a face and eyes, nose and mouth inside the face." "The code has been tested on the Japanese Female Facial Expression (JAFFE) database and found" "to give reasonably accurate results. \n"; cout << "\nUSAGE: ./cpp-example-facial_features [IMAGE] [FACE_CASCADE] [OPTIONS]\n" "IMAGE\n\tPath to the image of a face taken as input.\n" "FACE_CASCSDE\n\t Path to a haarcascade classifier for face detection.\n" "OPTIONS: \nThere are 3 options available which are described in detail. There must be a " "space between the option and it's argument (All three options accept arguments).\n" "\t-eyes=<eyes_cascade> : Specify the haarcascade classifier for eye detection.\n" "\t-nose=<nose_cascade> : Specify the haarcascade classifier for nose detection.\n" "\t-mouth=<mouth-cascade> : Specify the haarcascade classifier for mouth detection.\n"; cout << "EXAMPLE:\n" "(1) ./cpp-example-facial_features image.jpg face.xml -eyes=eyes.xml -mouth=mouth.xml\n" "\tThis will detect the face, eyes and mouth in image.jpg.\n" "(2) ./cpp-example-facial_features image.jpg face.xml -nose=nose.xml\n" "\tThis will detect the face and nose in image.jpg.\n" "(3) ./cpp-example-facial_features image.jpg face.xml\n" "\tThis will detect only the face in image.jpg.\n"; cout << " \n\nThe classifiers for face and eyes can be downloaded from : " " \nhttps://github.com/opencv/opencv/tree/master/data/haarcascades"; cout << "\n\nThe classifiers for nose and mouth can be downloaded from : " " \nhttps://github.com/opencv/opencv_contrib/tree/master/modules/face/data/cascades\n"; } static void detectFaces(Mat& img, vector<Rect_<int> >& faces, string cascade_path) { CascadeClassifier face_cascade; face_cascade.load(cascade_path); face_cascade.detectMultiScale(img, faces, 1.15, 3, 0 | CASCADE_SCALE_IMAGE, Size(30, 30)); //多尺度检测人脸 return; } static void detectFacialFeaures(Mat& img, const vector<Rect_<int> > faces, string eye_cascade, string nose_cascade, string mouth_cascade) { for (unsigned int i = 0; i < faces.size(); ++i) { // Mark the bounding box enclosing the face Rect face = faces[i]; rectangle(img, Point(face.x, face.y), Point(face.x + face.width, face.y + face.height), //用矩形框标记出人脸 Scalar(255, 0, 0), 1, 4); // Eyes, nose and mouth will be detected inside the face (region of interest) Mat ROI = img(Rect(face.x, face.y, face.width, face.height)); // Check if all features (eyes, nose and mouth) are being detected bool is_full_detection = false; if ((!eye_cascade.empty()) && (!nose_cascade.empty()) && (!mouth_cascade.empty())) is_full_detection = true; // Detect eyes if classifier provided by the user if (!eye_cascade.empty()) // 人眼检测 { vector<Rect_<int> > eyes; detectEyes(ROI, eyes, eye_cascade); // 人眼检测 // Mark points corresponding to the centre of the eyes for (unsigned int j = 0; j < eyes.size(); ++j) { Rect e = eyes[j]; circle(ROI, Point(e.x + e.width / 2, e.y + e.height / 2), 3, Scalar(0, 255, 0), -1, 8); //用圆形 标记出眼睛 /* rectangle(ROI, Point(e.x, e.y), Point(e.x+e.width, e.y+e.height), Scalar(0, 255, 0), 1, 4); */ } } // Detect nose if classifier provided by the user double nose_center_height = 0.0; if (!nose_cascade.empty()) // 鼻子检测 { vector<Rect_<int> > nose; detectNose(ROI, nose, nose_cascade); // Mark points corresponding to the centre (tip) of the nose for (unsigned int j = 0; j < nose.size(); ++j) { Rect n = nose[j]; circle(ROI, Point(n.x + n.width / 2, n.y + n.height / 2), 3, Scalar(0, 255, 0), -1, 8); //用圆形 标记出鼻子 nose_center_height = (n.y + n.height / 2); } } // Detect mouth if classifier provided by the user double mouth_center_height = 0.0; if (!mouth_cascade.empty()) // 嘴巴检测 { vector<Rect_<int> > mouth; detectMouth(ROI, mouth, mouth_cascade); for (unsigned int j = 0; j < mouth.size(); ++j) { Rect m = mouth[j]; mouth_center_height = (m.y + m.height / 2); // The mouth should lie below the nose if ((is_full_detection) && (mouth_center_height > nose_center_height)) // 检验鼻子比嘴巴高 { rectangle(ROI, Point(m.x, m.y), Point(m.x + m.width, m.y + m.height), Scalar(0, 255, 0), 1, 4); //用矩形框标记出嘴巴 } else if ((is_full_detection) && (mouth_center_height <= nose_center_height)) continue; else rectangle(ROI, Point(m.x, m.y), Point(m.x + m.width, m.y + m.height), Scalar(0, 255, 0), 1, 4); } } } return; } static void detectEyes(Mat& img, vector<Rect_<int> >& eyes, string cascade_path) { CascadeClassifier eyes_cascade; eyes_cascade.load(cascade_path); eyes_cascade.detectMultiScale(img, eyes, 1.20, 5, 0 | CASCADE_SCALE_IMAGE, Size(30, 30)); return; } static void detectNose(Mat& img, vector<Rect_<int> >& nose, string cascade_path) { CascadeClassifier nose_cascade; nose_cascade.load(cascade_path); nose_cascade.detectMultiScale(img, nose, 1.20, 5, 0 | CASCADE_SCALE_IMAGE, Size(30, 30)); return; } static void detectMouth(Mat& img, vector<Rect_<int> >& mouth, string cascade_path) { CascadeClassifier mouth_cascade; mouth_cascade.load(cascade_path); mouth_cascade.detectMultiScale(img, mouth, 1.20, 5, 0 | CASCADE_SCALE_IMAGE, Size(30, 30)); return; }
【后续】笔者又调皮了一下,将程序作了如下修改
1.将keys的值添加了“{ faces ||}”,所以变为:
cv::CommandLineParser parser(argc, argv, "{faces||}{eyes||}{nose||}{mouth||}{help h||}"); // 构造函数初始化
这是因为,commandLineParse类中的key的定义方式是
// parse keys 解析key值 std::vector<String> k = impl->split_range_string(keys, '{', '}'); //用“{ }”来分割keys ... std::vector<String> l = impl->split_string(k[i], '|', true); // 用“ | ”来分割开key[i] // parse argv 解析命令行参数 String s(argv[i]); bool hasSingleDash = s.length() > 1 && s[0] == '-'; // 用key的方式定义参数,需要在前面加“-”
2.并且将命令行参数中的face.xml文件前,添加了“-faces",所以第二个命令行参数变为:
“-faces=haarcascade_frontalface_alt.xml ”
3.同时更改回程序:读取第二个参数的方式
//face_cascade_path = argv[2]; face_cascade_path= parser.has("faces") ? parser.get<string>("faces") : "";
4.然后,点击运行,发现OK!
【待解决问题】
通过阅读源代码,发现程序(隐含的)第0个参数,即源程序的路径,opencv中的commandLineParse类的读取第一个参数的方式是
// path to application size_t pos_s = String(argv[0]).find_last_of("/\\"); //通过寻找argv[0]里的最后一个双斜杠“\\”,来找程序的名称 (.exe程序)
而,通过运行程序显示出来的argv[0]为:
发现不带双斜杠“\\”,所以可能造成opencv的commandLineParse类无法读取第0个参数。所以出现开始时的错误情况。这种方法可通过修改opencv源码的方式解决,改为“\”,使二者匹配;或者更改程序本身的源程序路径改为“\\”式样,使得二者匹配,也可。但我不会改,所以只是先到此为止,发现了问题,留待厉害的大神来指点一二吧!
2.详解opencv中的CommandLineParser类
CommandLineParser类的成员变量和函数如下:
class CV_EXPORTS CommandLineParser { public: CommandLineParser(int argc, const char* const argv[], const String& keys); //构造函数 CommandLineParser(const CommandLineParser& parser); //构造函数 CommandLineParser& operator = (const CommandLineParser& parser); ~CommandLineParser(); //析构函数 String getPathToApplication() const; // 返回应用程序路径 Returns application path template <typename T> T get(const String& name, bool space_delete = true) const //通过参数名称获取参数 Access arguments by name { T val = T(); getByName(name, space_delete, ParamType<T>::type, (void*)&val); //调用成员函数getByName return val; } /* String keys = "{@arg1||}{@arg2||}" String val_1 = parser.get<String>(0); // returns "abc", arg1 String val_2 = parser.get<String>(1); // returns "qwe", arg2 */ template <typename T> T get(int index, bool space_delete = true) const //通过索引访问位置参数 Access positional arguments by index { T val = T(); getByIndex(index, space_delete, ParamType<T>::type, (void*)&val); //调用成员函数getByIndex return val; } /* Check if field was provided in the command line */ bool has(const String& name) const; //检查该参数是否 在命令行中 /** @brief Check for parsing errors Returns true if error occurred while accessing the parameters (bad conversion, missing arguments, etc.). Call @ref printErrors to print error messages list. */ bool check() const; // 检查在获取参数时是否有错误发生,若发生error,则打印error信息 /** @brief Set the about message The about message will be shown when @ref printMessage is called, right before arguments table. */ void about(const String& message); /** @brief Print help message This method will print standard help message containing the about message and arguments description. @sa about */ void printMessage() const; //打印help信息 /** @brief Print list of errors occurred @sa check */ void printErrors() const; protected: void getByName(const String& name, bool space_delete, int type, void* dst) const; void getByIndex(int index, bool space_delete, int type, void* dst) const; struct Impl; Impl* impl; };
特将CommandLineParse类的构造函数附上:
CommandLineParser::CommandLineParser(int argc, const char* const argv[], const String& keys) { impl = new Impl; impl->refcount = 1; // path to application size_t pos_s = String(argv[0]).find_last_of("/\\"); if (pos_s == String::npos) { impl->path_to_app = ""; impl->app_name = String(argv[0]); } else { impl->path_to_app = String(argv[0]).substr(0, pos_s); impl->app_name = String(argv[0]).substr(pos_s + 1, String(argv[0]).length() - pos_s); } impl->error = false; impl->error_message = ""; // parse keys std::vector<String> k = impl->split_range_string(keys, '{', '}'); int jj = 0; for (size_t i = 0; i < k.size(); i++) { std::vector<String> l = impl->split_string(k[i], '|', true); CommandLineParserParams p; p.keys = impl->split_string(l[0]); p.def_value = l[1]; p.help_message = cat_string(l[2]); p.number = -1; if (p.keys.size() <= 0) { impl->error = true; impl->error_message = "Field KEYS could not be empty\n"; } else { if (p.keys[0][0] == '@') { p.number = jj; jj++; } impl->data.push_back(p); } } // parse argv jj = 0; for (int i = 1; i < argc; i++) { String s(argv[i]); bool hasSingleDash = s.length() > 1 && s[0] == '-'; if (hasSingleDash) { bool hasDoubleDash = s.length() > 2 && s[1] == '-'; String key = s.substr(hasDoubleDash ? 2 : 1); String value = "true"; size_t equalsPos = key.find('='); if(equalsPos != String::npos) { value = key.substr(equalsPos + 1); key = key.substr(0, equalsPos); } impl->apply_params(key, value); } else { impl->apply_params(jj, s); jj++; } } impl->sort_params(); }
希望大神能解决我上述未解决的问题,欢迎留言!欢迎指正!
------------------------------------------- END -------------------------------------
参考:
https://blog.csdn.net/longji/article/details/78206901