【opencv】运行opencv3.4中的demo--facial_features.cpp

【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


猜你喜欢

转载自blog.csdn.net/u012679707/article/details/80385465