Skip to content

Instantly share code, notes, and snippets.

@catree
Last active August 29, 2015 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save catree/1319a9b59f4f1df57da7 to your computer and use it in GitHub Desktop.
Save catree/1319a9b59f4f1df57da7 to your computer and use it in GitHub Desktop.
How to detect faces on an image and display number labels, intented to be used for school year group photo
/**
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
**/
#include <iostream>
#include <algorithm>
#include <fstream>
#include <opencv2/opencv.hpp>
#define DEBUG 0
/**
@author: Catree
@date: 2015/07/11
**/
typedef struct face_info_t {
cv::Point face_center;
cv::Size face_size;
cv::Point label_center;
cv::Size img_size;
face_info_t() : face_center(), face_size(), label_center(), img_size()
{
}
face_info_t(const cv::Point &center, const cv::Size &size, const cv::Point &lbl_center, const cv::Size _img_size)
: face_center(center), face_size(size), label_center(lbl_center), img_size(_img_size)
{
}
bool operator<(const face_info_t& face_info) const
{
int top1 = (int) (face_center.y - face_size.height*0.5);
int bottom1 = (int) (face_center.y + face_size.height*0.5);
int top2 = (int) (face_info.face_center.y - face_info.face_size.height*0.5);
int bottom2 = (int) (face_info.face_center.y + face_info.face_size.height*0.5);
if( (top1 >= top2 && top1 <= bottom2) || (bottom1 >= top2 && bottom1 <= bottom2) ||
(top2 >= top1 && top2 <= bottom1) || (bottom2 >= top1 && bottom2 <= bottom1) )
{
return (face_center.x < face_info.face_center.x);
}
return (face_center.y < face_info.face_center.y);
}
} face_info_t;
std::vector<face_info_t> remove_same_faces(const std::vector<face_info_t> &vectorOfFaces)
{
std::vector<face_info_t> vectorOfUniqueFaces;
std::vector<size_t> vectorOfDuplicateIndexes;
for(size_t i = 0; i < vectorOfFaces.size(); i++)
{
int top = (int) (vectorOfFaces[i].face_center.x - vectorOfFaces[i].face_size.width*0.5);
int left = (int) (vectorOfFaces[i].face_center.y - vectorOfFaces[i].face_size.height*0.5);
cv::Rect r1(top, left, vectorOfFaces[i].face_size.width, vectorOfFaces[i].face_size.height);
bool duplicate = false;
for(size_t j = i+1; j < vectorOfFaces.size(); j++)
{
if(std::find(vectorOfDuplicateIndexes.begin(), vectorOfDuplicateIndexes.end(), j) == vectorOfDuplicateIndexes.end())
{
top = (int) (vectorOfFaces[j].face_center.x - vectorOfFaces[j].face_size.width*0.5);
left = (int) (vectorOfFaces[j].face_center.y - vectorOfFaces[j].face_size.height*0.5);
cv::Rect r2(top, left, vectorOfFaces[i].face_size.width, vectorOfFaces[i].face_size.height);
cv::Rect r_intersection = r1 & r2;
if(r_intersection.width > 0 && r_intersection.height > 0)
{
duplicate = true;
vectorOfDuplicateIndexes.push_back(j);
}
}
}
if(!duplicate)
{
vectorOfUniqueFaces.push_back(vectorOfFaces[i]);
}
}
return vectorOfUniqueFaces;
}
void display_number(cv::Mat &img, const int number, const cv::Point &center, const cv::Size &label_box_size=cv::Size(30,30),
const cv::Scalar label_box_bcg_color=cv::Scalar(255,255,255))
{
cv::Mat label_box(label_box_size, CV_8UC3);
label_box.setTo(label_box_bcg_color);
std::stringstream ss;
ss << number;
double factor = label_box_size.width / 30.0;
if(number < 10)
{
cv::putText(label_box, ss.str(), cv::Point(label_box.cols/2-6*factor, label_box.rows/2+5*factor), cv::FONT_HERSHEY_PLAIN, factor, cv::Scalar(0,0,0), 2);
}
else
{
cv::putText(label_box, ss.str(), cv::Point(label_box.cols/2-10*factor, label_box.rows/2+5*factor), cv::FONT_HERSHEY_PLAIN, factor, cv::Scalar(0,0,0), 2);
}
label_box.copyTo(img(cv::Rect(center.x, center.y, label_box.cols, label_box.rows)));
}
void detect_faces_and_display_labels(cv::Mat &img, cv::CascadeClassifier &face_cascade, const bool display_result=false)
{
std::vector<cv::Rect> faces;
face_cascade.detectMultiScale(img, faces);
int size = std::min(img.rows, img.cols) / 24;
cv::Size label_box_size(size, size);
std::vector<face_info_t> vectorOfFaces(faces.size());
for (size_t i = 0; i < faces.size(); i++)
{
cv::Point center((int) (faces[i].x + faces[i].width*0.5), (int) (faces[i].y + faces[i].height*0.5));
#if DEBUG
ellipse(img, center, cv::Size(faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, cv::Scalar(255, 0, 255), 2);
#endif
cv::Point label_box(center.x - size/2, center.y + faces[i].height - size/2);
vectorOfFaces[i] = face_info_t(center, faces[i].size(), label_box, img.size());
}
std::sort (vectorOfFaces.begin(), vectorOfFaces.end());
vectorOfFaces = remove_same_faces(vectorOfFaces);
int label = 1;
for(std::vector<face_info_t>::const_iterator it = vectorOfFaces.begin(); it != vectorOfFaces.end(); ++it, label++)
{
display_number(img, label, it->label_center, label_box_size);
}
if(display_result)
{
cv::imshow("Image with tags", img);
std::cout << "Press a key to continue..." << std::endl;
cv::waitKey(0);
}
}
cv::Mat read_image(const std::string &filepath)
{
cv::Mat img = cv::imread(filepath);
return img;
}
void help()
{
std::cout << "Option: -h <print this help>" << std::endl;
std::cout << "Option: -cascade <filepath for the cascade classifier learning file>" << std::endl;
std::cout << "Option: -image_list <filepath for the text file containing the images to process>" << std::endl;
std::cout << "Option: -display <display the result for each image>" << std::endl;
}
int main(int argc, char*argv[])
{
std::string casacade_filepath = "haarcascade_frontalface_alt2.xml";
std::string image_list_filepath = "image_list.txt";
bool display_result = false;
for(int i = 1; i < argc; i++)
{
if(std::string(argv[i]) == "-h")
{
help();
}
else if(std::string(argv[i]) == "-cascade")
{
if(i+1 < argc)
{
casacade_filepath = argv[i+1];
}
}
else if(std::string(argv[i]) == "-image_list")
{
if(i+1 < argc)
{
image_list_filepath = argv[i+1];
}
}
else if(std::string(argv[i]) == "-display")
{
display_result = true;
}
}
std::ifstream file(image_list_filepath.c_str());
if(!file.is_open())
{
std::cerr << "Problem with the filepath for the learning file !" << std::endl;
return -1;
}
std::string line;
std::vector<std::string> vectorOfImageFilepaths;
while(getline(file, line))
{
vectorOfImageFilepaths.push_back(line);
}
cv::CascadeClassifier face_cascade;
if(!face_cascade.load(casacade_filepath))
{
std::cerr << "Problem with the filepath for cascade classifier learning file !" << std::endl;
return -1;
}
for(std::vector<std::string>::const_iterator it = vectorOfImageFilepaths.begin(); it != vectorOfImageFilepaths.end(); ++it)
{
cv::Mat img = read_image(*it);
if(!img.empty())
{
std::string filenama_tagged = (*it) + "_tagged.jpg";
detect_faces_and_display_labels(img, face_cascade, display_result);
std::cout << "Save image: " << filenama_tagged << std::endl;
cv::imwrite(filenama_tagged, img);
}
else
{
std::cerr << "The image " << line << " cannot be read !" << std::endl;
}
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment