【opencv】執行opencv3.4中的demo–facial_features.cpp

【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