├── 500_lines_vs2015 ├── readme.md ├── sdm_common.h ├── test_sdm_facealign.cpp └── train_sdm_facealign.cpp ├── CMakeLists.txt ├── bin ├── data │ ├── helen │ │ ├── testset │ │ │ ├── 30427236_1.jpg │ │ │ ├── 30427236_1.pts │ │ │ ├── 30427236_2.jpg │ │ │ ├── 30427236_2.pts │ │ │ ├── 30542618_1.jpg │ │ │ ├── 30542618_1.pts │ │ │ ├── 30844800_1.jpg │ │ │ ├── 30844800_1.pts │ │ │ ├── 31681454_1.jpg │ │ │ └── 31681454_1.pts │ │ └── trainset │ │ │ ├── 10405146_1.jpg │ │ │ ├── 10405146_1.pts │ │ │ ├── 10405299_1.jpg │ │ │ ├── 10405299_1.pts │ │ │ ├── 10405424_1.jpg │ │ │ ├── 10405424_1.pts │ │ │ ├── 10406776_1.jpg │ │ │ ├── 10406776_1.pts │ │ │ ├── 1629243_1.jpg │ │ │ ├── 1629243_1.pts │ │ │ ├── 1691766_1.jpg │ │ │ ├── 1691766_1.pts │ │ │ ├── 232194_1.jpg │ │ │ ├── 232194_1.pts │ │ │ ├── 2908549_1.jpg │ │ │ └── 2908549_1.pts │ └── readme ├── face_sdm.bin ├── face_sdm.yml ├── haarcascade_frontalface_alt2.xml ├── test_sdm_facealign └── train_sdm_facealign ├── build.sh ├── crop ├── figure_68_markup.jpg ├── meanfaces.png └── test_tile.jpg ├── examples ├── .test_image.cpp.swp ├── test_image.cpp ├── test_testset.cpp ├── test_webcam.cpp └── train_sdm_facealign.cpp ├── readme.md ├── run.sh └── src ├── sdm_common.h ├── sdm_fit.cpp ├── sdm_fit.h └── test_image.cpp /500_lines_vs2015/readme.md: -------------------------------------------------------------------------------- 1 | # Train an SDM for face alignment in 500 lines of C++ code 2 | 3 | * The theory is based on the idea of _Supervised Descent Method and Its Applications to Face Alignment_, from X. Xiong & F. De la Torre, CVPR 2013 4 | 5 | * OpenCV's Haar-based face detection only has around 75% detection rate, so for those in trainset fail to detect faces, the face rectangle shoud be initialized by annotated eyes' postions. So I leanrn a matrix to map eyes' postions to face rectangle. 6 | 7 | * OpenCV ```cv::HOGDescriptor``` is used to extract HOG feature descriptors around face landmarks' region like this: 8 | 9 | ``` c++ 10 | Mat Img; 11 | vector pt_shape; 12 | ... 13 | HOGDescriptor hog; 14 | int half_wid = hog.blockSize.width/2; 15 | vector des; 16 | 17 | vector pos; 18 | for (int j = 0; j < pt_shape.size(); j++) 19 | pos.push_back(Point(pt_shape[j].x - half_wid + 0.5, pt_shape[j].y - half_wid + 0.5)); 20 | 21 | hog.compute(Img, des, winStride, Size(0, 0), pos); 22 | ``` 23 | 24 | * Some samples in trainset are used for testing the model, so the preprocessing steps are same for all images: crop face region (by face detection or by eyes' position) and align it to same size, then add borders to extract HOGs. 25 | 26 | * Some results: 27 | 28 | * blue dots: init position (meanshape), same for all 29 | 30 | * red dos: after first round descent iteration 31 | 32 | * green dots: after all five rounds descent iteration 33 | 34 | ![test_tile](https://github.com/wanglin193/SupervisedDescentMethod/blob/master/crop/test_tile.jpg) 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /500_lines_vs2015/sdm_common.h: -------------------------------------------------------------------------------- 1 | using namespace std; 2 | using namespace cv; 3 | 4 | #define HOG_GLOBAL -1 5 | #define HOG_ALL 0 6 | 7 | typedef vector shape2d; 8 | 9 | void set_global_hog(HOGDescriptor&hog, int roi_size, int pix_in_cell, int cell_in_block, int n_orient) 10 | { 11 | int cell_in_win = (int)((float)roi_size / (float)pix_in_cell); 12 | hog.winSize = Size(cell_in_win*pix_in_cell, cell_in_win*pix_in_cell); 13 | hog.cellSize = Size(pix_in_cell * cell_in_block, pix_in_cell*cell_in_block); 14 | hog.blockSize = cv::Size(cell_in_block * hog.cellSize.width, cell_in_block * hog.cellSize.height); 15 | hog.blockStride = hog.cellSize; 16 | hog.nbins = n_orient; 17 | } 18 | void set_hog_params(HOGDescriptor&hog, int pix_in_cell, int cell_in_block, int n_orient) 19 | { 20 | hog.cellSize = Size(pix_in_cell, pix_in_cell); 21 | hog.blockSize = cv::Size(cell_in_block * hog.cellSize.width, cell_in_block * hog.cellSize.height); 22 | hog.winSize = hog.blockSize; //this define feature region 23 | hog.blockStride = hog.winSize;//useless when set hog.winSize = hog.blockSize; 24 | hog.nbins = n_orient; 25 | } 26 | void set_HOGs(vector& hogs,int global_size,vector& vnum_lmks) 27 | { 28 | int num_hog = hogs.size(); 29 | int pix_in_cell = (int)(global_size*0.1 + 0.5); 30 | 31 | if (vnum_lmks[0] == HOG_GLOBAL) 32 | set_global_hog(hogs[0], global_size, 8, 4, 9); 33 | else 34 | set_hog_params(hogs[0], pix_in_cell, 4, 6); 35 | 36 | if (num_hog > 1) set_hog_params(hogs[1], pix_in_cell, 4, 6); 37 | if (num_hog > 2) set_hog_params(hogs[2], pix_in_cell, 4, 4); 38 | for (int i = 3; i mats_read(const char* filename) 43 | { 44 | vector vM; 45 | FILE* file = fopen(filename, "rb"); 46 | if (file == NULL) 47 | return vM; 48 | 49 | int num; 50 | fread(&num, sizeof(int), 1, file); 51 | 52 | for (int i = 0; i < num; i++) 53 | { 54 | int headData[3]; 55 | fread(headData, sizeof(int), 3, file); 56 | Mat M(headData[1], headData[0], headData[2]); 57 | fread(M.data, sizeof(char), M.step * M.rows, file); 58 | vM.push_back(M); 59 | } 60 | fclose(file); 61 | return vM; 62 | } 63 | bool mats_write(const char* filename, vector& vM) 64 | { 65 | FILE* file = fopen(filename, "wb"); 66 | if (file == NULL || vM.empty()) 67 | return false; 68 | 69 | int num = vM.size(); 70 | fwrite(&num, sizeof(int), 1, file); 71 | 72 | for (int i = 0; i < num; i++) 73 | { 74 | Mat M = vM[i];// cout << M.step << " " << M.cols << "*" << M.rows << endl; 75 | int headData[3] = { M.cols, M.rows, M.type() }; 76 | fwrite(headData, sizeof(int), 3, file); 77 | fwrite(M.data, sizeof(char), M.step * M.rows, file); 78 | } 79 | fclose(file); 80 | return true; 81 | } 82 | 83 | inline cv::Mat shape2d_to_mat(vector&pts) 84 | { 85 | int numpts = pts.size(); 86 | Mat v(numpts * 2, 1, CV_32F); 87 | for (int j = 0; j < numpts; j++) 88 | { 89 | v.at(2 * j) = pts[j].x; 90 | v.at(2 * j + 1) = pts[j].y; 91 | } 92 | return v; 93 | } 94 | inline shape2d mat_to_shape2d(Mat & v) 95 | { 96 | int numpts = v.rows / 2; 97 | assert(v.cols == 1); 98 | shape2d pts(numpts, cv::Point2f(0, 0)); 99 | 100 | for (int j = 0; j < numpts; j++) 101 | { 102 | pts[j].x = v.at(2 * j); 103 | pts[j].y = v.at(2 * j + 1); 104 | } 105 | return pts; 106 | } 107 | 108 | shape2d read_pts_landmarks(const std::string filename) 109 | { 110 | using std::getline; 111 | shape2d landmarks; 112 | landmarks.reserve(68); 113 | 114 | std::ifstream file(filename); 115 | if (!file.is_open()) { 116 | throw std::runtime_error(string("Could not open landmark file: " + filename)); 117 | } 118 | 119 | string line; 120 | // Skip the first 3 lines, they're header lines: 121 | getline(file, line); // 'version: 1' 122 | getline(file, line); // 'n_points : 68' 123 | getline(file, line); // '{' 124 | 125 | while (getline(file, line)) 126 | { 127 | if (line == "}") { // end of the file 128 | break; 129 | } 130 | std::stringstream line_stream(line); 131 | cv::Point2f landmark(0.0f, 0.0f); 132 | if (!(line_stream >> landmark.x >> landmark.y)) { 133 | throw std::runtime_error(string("Landmark format error while parsing the line: " + line)); 134 | } 135 | landmark.x -= 1.0f; 136 | landmark.y -= 1.0f; 137 | landmarks.emplace_back(landmark); 138 | } 139 | return landmarks; 140 | }; 141 | void read_names_pair(const string &strListName, const string &strFilePath, 142 | const string& pts_ext, const string& img_ext, 143 | vector &vstrShapeName, vector &vstrImageName) 144 | { 145 | ifstream fNameFile; 146 | fNameFile.open(strListName.c_str()); 147 | vstrShapeName.clear(); 148 | vstrImageName.clear(); 149 | while (!fNameFile.eof()) 150 | { 151 | string s; 152 | getline(fNameFile, s); 153 | if (!s.empty()) 154 | { 155 | stringstream ss; 156 | ss << s; 157 | string ptname = strFilePath + "/" + ss.str() + pts_ext; 158 | string imagename = ptname.substr(0, ptname.find(".pts")) + img_ext; 159 | 160 | vstrShapeName.push_back(ptname); 161 | vstrImageName.push_back(imagename); 162 | } 163 | } 164 | } 165 | 166 | vector extract_HOG_descriptor(Mat & roiImg, vector& pt_shape, HOGDescriptor& hog, int num_idx_lmk, vector& idx_lmk) 167 | { 168 | int half_wid = hog.winSize.width >> 1; 169 | vector des; 170 | vector pos; 171 | if (num_idx_lmk == HOG_GLOBAL) //face region 172 | { 173 | pos.push_back(Point(roiImg.cols / 2 - half_wid, roiImg.rows / 2 - half_wid)); 174 | } 175 | else if (num_idx_lmk == HOG_ALL) 176 | { 177 | for (int j = 0; j < pt_shape.size(); j++) 178 | pos.push_back(Point(pt_shape[j].x - half_wid + 0.5, pt_shape[j].y - half_wid + 0.5)); 179 | } 180 | else 181 | { 182 | for (int j = 0; j < num_idx_lmk; j++) 183 | { 184 | Point2f pcorner = pt_shape[idx_lmk[j]] - Point2f(half_wid, half_wid); 185 | pos.push_back(Point(pcorner.x + 0.5, pcorner.y + 0.5)); 186 | } 187 | } 188 | hog.compute(roiImg, des, Size(0, 0), Size(0, 0), pos); //Size winStride = Size(0, 0); 189 | des.push_back(1.0); 190 | return des; 191 | } 192 | 193 | void draw_shape(Mat& imcanvas, shape2d& pts, cv::Scalar color) 194 | { 195 | for (int j = 0; j < pts.size(); j++) 196 | cv::circle(imcanvas, cv::Point(pts[j].x, pts[j].y), 2, color, -1); 197 | } 198 | -------------------------------------------------------------------------------- /500_lines_vs2015/test_sdm_facealign.cpp: -------------------------------------------------------------------------------- 1 | #include "opencv2/opencv.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | #include "sdm_common.h" 7 | 8 | struct SDM 9 | { 10 | int numpts; 11 | int NUM_ROUND; 12 | int normal_face_width; 13 | int border; 14 | int min_face_size; 15 | shape2d shape_mean; 16 | vector vnum_lmks; 17 | vector > vlmk_all; 18 | vector hogs; 19 | vector Mreg; 20 | cv::CascadeClassifier face_cascade; 21 | void set_min_face(int sz) { min_face_size = sz; } 22 | vector detected_faces; 23 | 24 | SDM() { min_face_size = 50; }; 25 | 26 | bool init(string& configfile) 27 | { 28 | string name_M, face_model;// = "haarcascade_frontalface_alt2.xml"; 29 | cv::FileStorage fs(configfile, cv::FileStorage::READ); 30 | if (!fs.isOpened()) { cout << "ERROR: Wrong path to settings" << endl; return false; } 31 | fs["NUM_LMKS"] >> numpts; 32 | fs["TRAIN_ROUND"] >> NUM_ROUND; 33 | fs["LMKS_EACH_ROUND"] >> vnum_lmks; 34 | fs["NUM_LMKS"] >> numpts; 35 | for (int i = 0; i < vnum_lmks.size(); i++) 36 | { 37 | vector obj; 38 | fs["ID_IN_ROUND_" + to_string(_Longlong(i))] >> obj; 39 | vlmk_all.push_back(obj); 40 | } 41 | fs["NORMAL_WID"] >> normal_face_width; 42 | fs["NORMAL_BORDER"] >> border; 43 | fs["OpenCV_HAAR"] >> face_model; 44 | 45 | fs["Mean_Shape"] >> shape_mean; 46 | fs["SDM_Mat"] >> name_M; 47 | face_cascade.load(face_model); 48 | 49 | Mreg = mats_read(name_M.c_str()); 50 | hogs.resize(NUM_ROUND); 51 | 52 | //set HOG size 53 | set_HOGs(hogs, normal_face_width + border, vnum_lmks); 54 | return true; 55 | } 56 | void regression(Mat &roiImg, shape2d &pts) 57 | { 58 | for (int r = 0; r < Mreg.size(); r++) 59 | { 60 | vector des = extract_HOG_descriptor(roiImg, pts, hogs[r], vnum_lmks[r], vlmk_all[r]); 61 | Mat rr = Mat(des.size(), 1, CV_32F, des.data()); 62 | Mat v_shape = Mreg[r] * rr + shape2d_to_mat(pts); 63 | pts = mat_to_shape2d(v_shape); 64 | } 65 | } 66 | bool fit(Mat & img_, vector& shapes) 67 | { 68 | cv::Mat img, img_gray; 69 | 70 | if (img_.channels() > 1) 71 | cvtColor(img_, img_gray, CV_BGR2GRAY); 72 | else 73 | img_gray = img_.clone(); 74 | 75 | face_cascade.detectMultiScale(img_gray, detected_faces, 1.2, 4, 0, cv::Size(min_face_size, min_face_size)); 76 | 77 | if (detected_faces.size() == 0) 78 | return false; 79 | 80 | shapes.clear(); 81 | for (int i = 0; i < detected_faces.size(); i++) 82 | { 83 | cv::Rect rectface = detected_faces[i]; 84 | cv::Mat roiImg; 85 | int normal_roi_width = normal_face_width + border * 2; 86 | 87 | float scale = (float)normal_face_width / (float)rectface.width; 88 | float shift_x = border - scale*rectface.x, shift_y = border - scale*rectface.y; 89 | Mat mat2roi = (Mat_(2, 3) << scale, 0, shift_x, 0, scale, shift_y); 90 | warpAffine(img_gray, roiImg, mat2roi, Size(normal_roi_width, normal_roi_width), INTER_LINEAR, BORDER_CONSTANT, 128); 91 | 92 | shape2d pts(shape_mean.size()); 93 | if (1)//detect mode: start from meanshape 94 | { 95 | for (int j = 0; j < shape_mean.size(); j++) 96 | pts[j] = shape_mean[j]; 97 | } 98 | //else //tracking mode: update shape from current state (or previous frame) 99 | //{ 100 | // for (int j = 0; j < pts.size(); j++) 101 | // { 102 | // pts[j].x = 103 | // pts[j].y = 104 | // } 105 | //} 106 | regression(roiImg, pts); 107 | if (1) 108 | { 109 | string win_name = "roi "; 110 | cv::Rect roi(border, border, normal_face_width, normal_face_width); 111 | cv::rectangle(roiImg, roi, cv::Scalar(255)); 112 | draw_shape(roiImg, pts, cv::Scalar(255)); 113 | imshow(win_name + to_string(_Longlong(i)), roiImg); 114 | } 115 | //back to original img 116 | float scale_back = 1.0 / mat2roi.at(0, 0); 117 | float dx_back = -scale_back*mat2roi.at(0, 2); 118 | float dy_back = -scale_back*mat2roi.at(1, 2); 119 | for (int j = 0; j < pts.size(); j++) 120 | { 121 | pts[j].x = pts[j].x*scale_back + dx_back; 122 | pts[j].y = pts[j].y*scale_back + dy_back; 123 | } 124 | shapes.push_back(pts); 125 | } 126 | return true; 127 | } 128 | }; 129 | enum { SDM_OK, LOAD_MODEL_FAIL, INPUT_FAIL }; 130 | int test_image(cv::Mat & img) 131 | { 132 | SDM sdm_face; 133 | sdm_face.set_min_face(30); 134 | if (sdm_face.init(string("face_sdm.yml"))) 135 | cout << "Load SDM model\n"; 136 | else 137 | return LOAD_MODEL_FAIL; 138 | 139 | float scale = 1.0; 140 | if (img.rows <= img.cols && img.rows > 500) scale = 480.0 / img.rows; 141 | if (img.rows > img.cols && img.cols > 500) scale = 480.0 / img.cols; 142 | 143 | if (scale < 1.0) 144 | cv::resize(img, img, cv::Size(), scale, scale, cv::INTER_LINEAR); 145 | 146 | vector shapes; 147 | 148 | if (sdm_face.fit(img, shapes)) 149 | { 150 | cout << shapes.size() << " faces found.\n"; 151 | for (int j = 0; j < shapes.size(); j++) 152 | { 153 | cv::rectangle(img, sdm_face.detected_faces[j], cv::Scalar(0,0,255)); 154 | draw_shape(img, shapes[j], cv::Scalar(255, 0, 0)); 155 | } 156 | imshow("image", img); 157 | waitKey(); 158 | } 159 | return SDM_OK; 160 | } 161 | int test_on_testset() 162 | { 163 | SDM sdm_face; 164 | if (sdm_face.init(string("face_sdm.yml"))) 165 | cout << "Load SDM model\n"; 166 | else 167 | return LOAD_MODEL_FAIL; 168 | 169 | string imagepath = "../../helen/testset"; 170 | string ptlistname = "../../helen/testset/ptlist.txt"; //dir *.pts/b > ptlist.txt 171 | vector vstrPts, vstrImg; 172 | 173 | read_names_pair(ptlistname, imagepath, string(), ".jpg", vstrPts, vstrImg); 174 | if (vstrImg.empty()) 175 | return INPUT_FAIL; 176 | 177 | for (int i = 0; i < vstrImg.size(); i++) 178 | { 179 | cv::Mat img = cv::imread(vstrImg[i], -1);//-1 origimage,0 grayscale 180 | float scale = 1.0; 181 | if (img.rows <= img.cols && img.rows > 400) scale = 320.0 / img.rows; 182 | if (img.rows > img.cols && img.cols > 400) scale = 320.0 / img.cols; 183 | 184 | if (scale < 1.0) 185 | cv::resize(img, img, cv::Size(), scale, scale, cv::INTER_LINEAR); 186 | 187 | //cout << vstrImg[i] << endl; 188 | vector shapes; 189 | 190 | if (sdm_face.fit(img, shapes)) 191 | { 192 | cout << shapes.size() << " faces found.\n"; 193 | for (int j = 0; j < shapes.size(); j++) 194 | draw_shape(img, shapes[j], cv::Scalar(255, 0, 0)); 195 | 196 | imshow("faces", img); 197 | waitKey(100); 198 | imwrite(vstrImg[i] + ".jpg", img); 199 | } 200 | else 201 | cout << "No face found\n"; 202 | } 203 | return SDM_OK; 204 | } 205 | 206 | static double time_begin; 207 | #define TIC (time_begin = (double)cvGetTickCount()); 208 | #define TOC (printf("Time = %g ms \r", ((double)cvGetTickCount() - time_begin)/((double)cvGetTickFrequency()*1000.) ) ); 209 | 210 | int test_webcam() 211 | { 212 | SDM sdm_face; 213 | sdm_face.set_min_face(80); 214 | if (sdm_face.init(string("face_sdm.yml"))) 215 | cout << "Load SDM model\n"; 216 | else 217 | return LOAD_MODEL_FAIL; 218 | 219 | cv::Mat img, gray; 220 | 221 | VideoCapture capture; 222 | capture.open(0); 223 | 224 | if (capture.isOpened()) 225 | { 226 | std::cout << "Capture is opened." << std::endl; 227 | for (;;) 228 | { 229 | capture >> img; 230 | if (img.empty()) 231 | { 232 | cout << " capture error.\n"; 233 | continue; 234 | } 235 | // resize(img, img, Size(), 0.5, 0.5, CV_INTER_LINEAR); 236 | cvtColor(img, gray, CV_BGR2GRAY); 237 | vector shapes; 238 | TIC; 239 | if (sdm_face.fit(gray, shapes)) 240 | { 241 | //sdm_face.show_crop(); 242 | //cout << shapes.size() << " faces found.\n"; 243 | for (int j = 0; j < shapes.size(); j++) 244 | { 245 | cv::rectangle(img, sdm_face.detected_faces[j], cv::Scalar(0, 0, 255)); 246 | draw_shape(img, shapes[j], cv::Scalar(255, 0, 0)); 247 | } 248 | } 249 | TOC; 250 | imshow("Video", img); 251 | if (waitKey(10) == 27) 252 | break; 253 | } 254 | } 255 | else 256 | { 257 | cout << " NO camera input." << endl; 258 | return INPUT_FAIL; 259 | } 260 | return SDM_OK; 261 | } 262 | 263 | void main() 264 | { 265 | int ret = -1; 266 | ret = test_webcam(); 267 | 268 | //if (ret != SDM_OK) 269 | if (0) 270 | ret = test_on_testset(); 271 | 272 | if (0) 273 | { 274 | cv::Mat im = imread("325564774_1.jpg"); 275 | ret = test_image(im); 276 | } 277 | } -------------------------------------------------------------------------------- /500_lines_vs2015/train_sdm_facealign.cpp: -------------------------------------------------------------------------------- 1 | #include "opencv2/opencv.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "sdm_common.h" 8 | //-------------params for training---------- 9 | const int NUM_ROUND = 5; 10 | vector hogs(NUM_ROUND); 11 | 12 | int num_lmks[NUM_ROUND] = { HOG_GLOBAL,15,22,22,HOG_ALL }; 13 | int id_lmks[NUM_ROUND][68] = { { }, 14 | { 2,5,8,11,14, 31,33,35, 36, 39,42,45,56,57,58 },//15 15 | { 1, 3, 6, 8, 10, 13, 15, 18, 20, 23, 25, 30, 31, 35, 36, 39, 42, 45, 48, 51, 54, 57 },//22 16 | { 1, 3, 6, 8, 10, 13, 15, 18, 20, 23, 25, 30, 31, 35, 36, 39, 42, 45, 48, 51, 54, 57 }, 17 | {} }; 18 | 19 | int normal_face_width = 128; //normalize face detect rectangle 20 | int border = 64; //normalize ROI size = normal_face_width + 2*border 21 | string face_model = "haarcascade_frontalface_alt2.xml"; 22 | //-------------------------------------------- 23 | //in case fail to detect face, use this to init face Rect: 24 | //rect_x_y_w = M_eyes_to_rect * eyes_x1_x2_y 25 | cv::Mat M_eyes_to_rect; 26 | 27 | shape2d shape_mean; 28 | vector train_shapes; 29 | vector train_samples; 30 | 31 | //L = M*R ---> L*RT = M*R*RT ---> L*RT*(lambda*I+R*RT)^-1 = M 32 | cv::Mat solve_norm_equation(cv::Mat& L, cv::Mat& R) 33 | { 34 | cv::Mat M; 35 | cv::Mat LRT = L*R.t(), RRT = R*R.t(); 36 | 37 | cout << "Begin solving..."; 38 | float lambda = 0.5;// 0.050f * ((float)cv::norm(RRT)) / (float)(RRT.rows); 39 | 40 | for (int i = 0; i < RRT.cols - 1; i++) 41 | RRT.at(i, i) = lambda + RRT.at(i, i); 42 | M = LRT*RRT.inv(cv::DECOMP_LU); 43 | cout << " done." << endl; 44 | return M; 45 | } 46 | void train_map_facerect_and_eyes(vector& vstrPts, vector& vstrImg) 47 | { 48 | int eyes_index[4] = { 36,39,42,45 }; 49 | cv::CascadeClassifier face_cascade; 50 | if (!face_cascade.load(face_model)) 51 | { 52 | cout << "Error loading face detection model." << endl; 53 | return; 54 | } 55 | 56 | cout << "Train mapping between eyes and detect-rectangle ....." << endl; 57 | int good = 0, numsamples = 200; 58 | cv::Mat L = Mat::zeros(3, 3, CV_32F); 59 | cv::Mat R = Mat::zeros(3, 3, CV_32F); 60 | cv::Mat M; //L=M*R 61 | for (int i = 0; i < numsamples/*vstrPts.size()*/; i++) 62 | { 63 | shape2d pts = read_pts_landmarks(vstrPts[i]); 64 | cv::Mat img = cv::imread(vstrImg[i], 0); 65 | 66 | float scale = 1.0; 67 | if (img.rows <= img.cols && img.rows > 200) scale = 160.0 / img.rows; 68 | if (img.rows > img.cols && img.cols > 200) scale = 160.0 / img.cols; 69 | 70 | cv::resize(img, img, cv::Size(), scale, scale, cv::INTER_LINEAR); 71 | vector detected_faces; 72 | face_cascade.detectMultiScale(img, detected_faces, 1.2, 4, 0, cv::Size(50, 50)); 73 | 74 | if (detected_faces.size() == 0) 75 | continue; 76 | 77 | cv::Point2f p0 = (pts[eyes_index[0]] + pts[eyes_index[1]])*scale*0.5; 78 | cv::Point2f p1 = (pts[eyes_index[2]] + pts[eyes_index[3]])*scale*0.5; 79 | 80 | cv::Rect rectface = detected_faces[0]; 81 | if (rectface.contains(p0) && rectface.contains(p1)) 82 | { 83 | good++; 84 | 85 | Mat ll = (Mat_(3, 1) << rectface.x, rectface.y, (rectface.width + rectface.height) / 2); 86 | Mat rr = (Mat_(3, 1) << p0.x, (p0.y + p1.y) / 2, p1.x); 87 | L = L + ll*rr.t(); 88 | R = R + rr*rr.t(); 89 | } 90 | } 91 | cout << "Collect " << good << " good faces in " << numsamples << " images. " << endl; 92 | 93 | M = L*R.inv(); 94 | M_eyes_to_rect = M.clone(); 95 | cout << "M_eyes_to_rect:\n" << M_eyes_to_rect << endl << endl; 96 | } 97 | void preprocess_images(vector& vstrPts, vector& vstrImg) 98 | { 99 | int eyes_index[4] = { 36,39,42,45 }; 100 | cv::CascadeClassifier face_cascade; 101 | face_cascade.load(face_model); 102 | 103 | bool showimg = false; 104 | Mat imcanvas; 105 | RNG rng((unsigned)time(NULL)); 106 | cout << "Collecting cropped " << vstrPts.size() << " faces and normlized shapes ..."; 107 | int count = 0; 108 | for (int i = 0; i < vstrPts.size(); i++) 109 | { 110 | shape2d pts = read_pts_landmarks(vstrPts[i]); 111 | cv::Mat img_ = cv::imread(vstrImg[i], 0);//-1 origimage,0 grayscale 112 | cv::Mat img; 113 | float scale = 1.0; 114 | if (img_.rows <= img_.cols && img_.rows > 500) scale = 400.0 / img_.rows; 115 | if (img_.rows > img_.cols && img_.cols > 500) scale = 400.0 / img_.cols; 116 | cv::resize(img_, img, cv::Size(), scale, scale, cv::INTER_LINEAR); 117 | 118 | vector detected_faces; 119 | face_cascade.detectMultiScale(img, detected_faces, 1.2, 4, 0, cv::Size(50, 50)); 120 | 121 | cv::Point2f p0 = (pts[eyes_index[0]] + pts[eyes_index[1]])*scale*0.5; 122 | cv::Point2f p1 = (pts[eyes_index[2]] + pts[eyes_index[3]])*scale*0.5; 123 | 124 | int idx = -1; 125 | for (int i = 0; i < detected_faces.size(); i++) 126 | { 127 | //eye lmks in this face rectangle 128 | if (detected_faces[i].contains(p0) && detected_faces[i].contains(p1)) 129 | idx = i; 130 | } 131 | 132 | cv::Rect rectface; 133 | //from eyes' position to get face detection rectangle 134 | if (detected_faces.size() == 0 || idx == -1) 135 | { 136 | Mat rr = (Mat_(3, 1) << p0.x, (p0.y + p1.y) / 2, p1.x); 137 | Mat ll = M_eyes_to_rect*rr; 138 | rectface = cv::Rect(ll.at(0), ll.at(1), ll.at(2), ll.at(2)); 139 | if (showimg) 140 | { 141 | cv::circle(img, Point(p0.x, p0.y), 2, cv::Scalar(255, 255, 255), -1); 142 | cv::circle(img, Point(p1.x, p1.y), 2, cv::Scalar(255, 255, 255), -1); 143 | cv::rectangle(img, rectface, cv::Scalar(255, 255, 255), 1); 144 | } 145 | } 146 | else 147 | { 148 | rectface = detected_faces[idx]; 149 | if (showimg) 150 | { 151 | cv::rectangle(img, rectface, cv::Scalar(0, 0, 255), 1); 152 | 153 | //from face detection rect to get eyes' position 154 | Mat ll = (Mat_(3, 1) << rectface.x, rectface.y, rectface.width); 155 | Mat rr = M_eyes_to_rect.inv()*ll; //rr = M^-1*ll 156 | cv::circle(img, Point(rr.at(0), rr.at(1)), 2, cv::Scalar(0, 0, 255), -1); 157 | cv::circle(img, Point(rr.at(2), rr.at(1)), 2, cv::Scalar(0, 0, 255), -1); 158 | } 159 | } 160 | if (showimg) 161 | { 162 | imshow("img", img); 163 | waitKey(100); 164 | } 165 | /////////perturb/////////// 166 | float sigma = 0.15; 167 | float n1 = rng.gaussian(sigma), n2 = rng.gaussian(sigma), n3 = rng.gaussian(sigma); 168 | 169 | rectface.x += (n1-n3*0.5)*(float)rectface.width; 170 | rectface.y += (n2-n3*0.5)*(float)rectface.width; 171 | rectface.width += n3*(float)rectface.width ; 172 | // cout << n1 << " " << n2 << " " << n3 << endl; 173 | 174 | cv::Mat roiImg; 175 | int normal_roi_width = normal_face_width + border * 2; 176 | 177 | float scale_again = (float)normal_face_width / (float)rectface.width; 178 | float shift_x = border - scale_again*rectface.x, shift_y = border - scale_again*rectface.y; 179 | Mat mat2roi = (Mat_(2, 3) << scale_again, 0, shift_x, 0, scale_again, shift_y); 180 | warpAffine(img, roiImg, mat2roi, Size(normal_roi_width, normal_roi_width), INTER_LINEAR, BORDER_CONSTANT, 128); 181 | 182 | //lmks on normalized ROI 183 | float scale2roi = scale_again*scale; 184 | float dx = mat2roi.at(0, 2); 185 | float dy = mat2roi.at(1, 2); 186 | for (int j = 0; j < pts.size(); j++) 187 | { 188 | pts[j].x = pts[j].x *scale2roi + dx; 189 | pts[j].y = pts[j].y *scale2roi + dy; 190 | } 191 | 192 | //average shape at normal scale 193 | if (i == 0) 194 | { 195 | shape_mean.resize(pts.size()); 196 | for (int j = 0; j < pts.size(); j++) 197 | shape_mean[j] = cv::Point2f(pts[j].x, pts[j].y); 198 | } 199 | else 200 | { 201 | for (int j = 0; j < pts.size(); j++) 202 | shape_mean[j] = shape_mean[j] + pts[j]; 203 | } 204 | count++; 205 | 206 | train_shapes.push_back(pts); 207 | train_samples.push_back(roiImg); 208 | 209 | if (showimg) 210 | { 211 | cvtColor(roiImg, imcanvas, COLOR_GRAY2RGB); 212 | HOGDescriptor hog = hogs[3]; 213 | int half_wid = hog.blockSize.width >> 1; 214 | for (int j = 0; j < pts.size(); j++) 215 | { 216 | if (pts[j].xroiImg.cols || pts[j].y + half_wid > roiImg.rows) 217 | cout << "Warning: HOG region out of image :" << i << " at lmk " << j << endl; 218 | cv::circle(imcanvas, cv::Point(pts[j].x, pts[j].y), 2, cv::Scalar(0, 0, 255), -1); 219 | Rect r(pts[j].x - half_wid, pts[j].y - half_wid, hog.blockSize.width, hog.blockSize.width); 220 | cv::rectangle(imcanvas, r, cv::Scalar(0, 255, 0), 1); 221 | } 222 | char name[200]; 223 | sprintf(name, "./crop/f_%d.png", i); 224 | imwrite(name, imcanvas); 225 | imshow("Face ROI", imcanvas); 226 | waitKey(10); 227 | } 228 | }//end collecting 229 | 230 | for (int j = 0; j < shape_mean.size(); j++) shape_mean[j] = shape_mean[j] * (1.0f / (float)count); 231 | cout << " done." << endl; 232 | if (showimg) 233 | { 234 | cv::Mat avgimg, temp; 235 | train_samples[0].convertTo(avgimg, CV_32FC1); 236 | 237 | for (int i = 1; i < train_samples.size(); i++) 238 | { 239 | train_samples[i].convertTo(temp, CV_32FC1); 240 | avgimg = avgimg + temp; 241 | } 242 | avgimg = avgimg / train_samples.size(); 243 | 244 | HOGDescriptor hog = hogs[0]; //draw hog[0] feature rect 245 | int half_wid = hog.blockSize.width >> 1; 246 | avgimg.convertTo(imcanvas, CV_8UC1); 247 | cvtColor(imcanvas, imcanvas, COLOR_GRAY2RGB); 248 | 249 | for (int j = 0; j < shape_mean.size(); j++) 250 | { 251 | cv::circle(imcanvas, cv::Point(shape_mean[j].x, shape_mean[j].y), 2, cv::Scalar(0, 0, 255), -1); 252 | Rect r(shape_mean[j].x - half_wid, shape_mean[j].y - half_wid, hog.blockSize.width, hog.blockSize.width); 253 | cv::rectangle(imcanvas, r, cv::Scalar(0, 255, 0), 1); 254 | } 255 | cout << "Press any to continue." << endl; 256 | imshow("meanface with shape_mean", imcanvas); 257 | waitKey(); 258 | } 259 | } 260 | void set_idx_lmks(vector& num_lmks, vector >& lmk_all) 261 | { 262 | for (int i = 0; i < num_lmks.size(); i++) 263 | { 264 | if (num_lmks[i] == HOG_GLOBAL || num_lmks[i] == HOG_ALL) 265 | { 266 | vector t; 267 | lmk_all.push_back(t); 268 | } 269 | else 270 | { 271 | vector t(id_lmks[i], id_lmks[i] + num_lmks[i]); 272 | lmk_all.push_back(t); 273 | } 274 | } 275 | } 276 | void train_by_regress() 277 | { 278 | int numsample_test = 100; //left some for test 279 | int numsample = train_shapes.size() - numsample_test; 280 | int numpts = shape_mean.size(); 281 | 282 | cout << "Training SDM on " << numsample << " images, left " << numsample_test << " for testing ..." << endl; 283 | 284 | vector vnum_lmks(num_lmks, num_lmks + NUM_ROUND); 285 | vector > vlmk_all; 286 | set_idx_lmks(vnum_lmks, vlmk_all); 287 | 288 | //set HOG size 289 | set_HOGs(hogs, normal_face_width + border, vnum_lmks); 290 | 291 | for (int i = 0; i < hogs.size(); i++) 292 | { 293 | cout << "HOG Round: " << i << " winsize: " << hogs[i].winSize << ", length of descriptor: " << hogs[i].getDescriptorSize() << endl; 294 | if (num_lmks[i] > 0) for (int j = 0; j < vnum_lmks[i]; j++) cout << id_lmks[i][j] << ","; 295 | else { num_lmks[i] == 0 ? cout << " use all 68 lmks" : cout << " use whole face ROI"; }; cout << endl; 296 | } 297 | cout << endl; 298 | 299 | vector shape_current_all; 300 | //init as meashape 301 | for (int i = 0; i < numsample; i++) 302 | shape_current_all.push_back(shape_mean); 303 | 304 | Mat mDiffShape(numpts * 2, numsample, CV_32F); 305 | Mat mFeature; 306 | vector Mreg; 307 | for (int r = 0; r < hogs.size(); r++) 308 | { 309 | cout << "---- Round " << r << " -----" << endl; 310 | for (int i = 0; i < numsample; i++) 311 | { 312 | Mat roiImg = train_samples[i]; 313 | vector des = extract_HOG_descriptor(roiImg, shape_current_all[i], hogs[r], vnum_lmks[r], vlmk_all[r]); 314 | if (i == 0) 315 | { 316 | mFeature.create(des.size(), numsample, CV_32F); 317 | cout << " training sample: " << numsample << ", length of feature: " << des.size() << endl; 318 | } 319 | 320 | Mat rr = Mat(des.size(), 1, CV_32F, des.data()); 321 | rr.copyTo(mFeature.col(i)); //must use copyTo 322 | 323 | Mat v_dest = shape2d_to_mat(train_shapes[i]) - shape2d_to_mat(shape_current_all[i]); 324 | v_dest.copyTo(mDiffShape.col(i)); 325 | }//end collect faces 326 | 327 | //regression 328 | cv::Mat M = solve_norm_equation(mDiffShape, mFeature); 329 | Mreg.push_back(M); 330 | 331 | //renew current shapes 332 | Mat mIncShape = M*mFeature; 333 | float E = 0.0f; 334 | for (int i = 0; i < numsample; i++) 335 | { 336 | Mat v_new = mIncShape.col(i) + shape2d_to_mat(shape_current_all[i]); 337 | shape_current_all[i] = mat_to_shape2d(v_new); 338 | 339 | Mat v_err = shape2d_to_mat(train_shapes[i]) - v_new; 340 | E = E + cv::norm(v_err, NORM_L2); 341 | } 342 | E /= numsample; 343 | cout << " Avg Shape Error = " << E << endl; 344 | }//end training round 345 | 346 | cout << "Save training result ..."; 347 | string name_M = "face_sdm.bin"; 348 | FileStorage fs("face_sdm.yml", FileStorage::WRITE); 349 | if (fs.isOpened()) 350 | { 351 | fs << "NUM_LMKS" << numpts; 352 | fs << "TRAIN_ROUND" << NUM_ROUND; 353 | fs << "LMKS_EACH_ROUND" << vnum_lmks; 354 | for (int i = 0; i < vlmk_all.size(); i++) 355 | { 356 | string id_lmk = "ID_IN_ROUND_"; 357 | //if (vnum_lmks[i] != -1 && vnum_lmks[i] != 0) 358 | fs << id_lmk + to_string(_Longlong(i)) << vlmk_all[i]; 359 | } 360 | 361 | fs << "NORMAL_WID" << normal_face_width << "NORMAL_BORDER" << border; 362 | fs << "OpenCV_HAAR" << face_model; 363 | fs << "SDM_Mat" << name_M; 364 | fs << "Mean_Shape" << shape_mean; 365 | fs.release(); 366 | } 367 | mats_write(name_M.c_str(), Mreg); 368 | cout << " done." << endl; 369 | 370 | if (1) 371 | { 372 | cout << "Testing on unseen images ... "; 373 | Mat imcanvas; 374 | for (int i = numsample; i < train_samples.size(); i++)//on rest image in trainset 375 | { 376 | Mat roiImg = train_samples[i]; 377 | cvtColor(roiImg, imcanvas, COLOR_GRAY2RGB); 378 | draw_shape(imcanvas, shape_mean, cv::Scalar(255, 0, 0)); 379 | shape2d pts = shape_mean; 380 | for (int r = 0; r < Mreg.size(); r++) 381 | { 382 | vector des = extract_HOG_descriptor(roiImg, pts, hogs[r], vnum_lmks[r], vlmk_all[r]); 383 | Mat rr = Mat(des.size(), 1, CV_32F, des.data()); 384 | Mat v_shape = Mreg[r] * rr + shape2d_to_mat(pts); 385 | pts = mat_to_shape2d(v_shape); 386 | 387 | if (r == 0) draw_shape(imcanvas, pts, cv::Scalar(0, 0, 255)); 388 | } 389 | draw_shape(imcanvas, pts, cv::Scalar(0, 255, 0)); 390 | char name[200]; 391 | sprintf(name, "./crop/test_%d.png", i); 392 | imwrite(name, imcanvas); 393 | } 394 | cout << "done\n"; 395 | } 396 | } 397 | void main() 398 | { 399 | string imagepath = "../../helen/trainset"; 400 | string ptlistname = "../../helen/trainset/ptlist.txt"; //dir *.pts/b > ptlist.txt 401 | vector vstrPts, vstrImg; 402 | 403 | read_names_pair(ptlistname, imagepath, string(), ".jpg", vstrPts, vstrImg); 404 | train_map_facerect_and_eyes(vstrPts, vstrImg); 405 | 406 | preprocess_images(vstrPts, vstrImg); 407 | train_by_regress(); 408 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | project(faceali) 4 | 5 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 6 | 7 | find_package(OpenCV 3.0 QUIET) 8 | 9 | include_directories( 10 | ${PROJECT_SOURCE_DIR}/include 11 | ${PROJECT_SOURCE_DIR}/src 12 | ) 13 | 14 | set(SDM_SRC 15 | src/sdm_fit.cpp 16 | ) 17 | 18 | set(SDM_LIB "sdmface") 19 | add_library(${SDM_LIB} STATIC ${SDM_SRC}) 20 | target_link_libraries(${SDM_LIB} 21 | ${OpenCV_LIBS} 22 | ) 23 | 24 | set(LIBRARY_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib") 25 | set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) 26 | 27 | # training sdm face model 28 | add_executable(train_sdm_facealign examples/train_sdm_facealign.cpp) 29 | target_link_libraries(train_sdm_facealign ${OpenCV_LIBS}) 30 | 31 | # fitting on images 32 | add_executable(test_image examples/test_image.cpp) 33 | target_link_libraries(test_image ${SDM_LIB} ${OpenCV_LIBS}) 34 | 35 | add_executable(test_testset examples/test_testset.cpp) 36 | target_link_libraries(test_testset ${SDM_LIB} ${OpenCV_LIBS}) 37 | 38 | add_executable(test_webcam examples/test_webcam.cpp) 39 | target_link_libraries(test_webcam ${SDM_LIB} ${OpenCV_LIBS}) 40 | 41 | 42 | -------------------------------------------------------------------------------- /bin/data/helen/testset/30427236_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/testset/30427236_1.jpg -------------------------------------------------------------------------------- /bin/data/helen/testset/30427236_1.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 498.801220 504.771171 5 | 516.076459 571.681686 6 | 518.038170 628.516761 7 | 537.659069 681.591460 8 | 568.079952 726.574567 9 | 606.974762 762.223040 10 | 647.693994 785.442205 11 | 679.657854 819.609080 12 | 741.808679 818.946134 13 | 798.001540 791.724729 14 | 829.588955 739.792378 15 | 874.808888 688.637232 16 | 917.963929 641.119832 17 | 942.262702 589.916047 18 | 947.183243 534.686759 19 | 932.533974 484.984463 20 | 916.879878 417.358068 21 | 497.213815 478.286481 22 | 515.726243 462.683805 23 | 541.301970 456.143959 24 | 571.144905 455.544262 25 | 593.745486 456.128192 26 | 677.574910 444.409329 27 | 714.974634 422.330344 28 | 758.387376 412.990320 29 | 803.184471 417.450685 30 | 838.937627 439.683787 31 | 647.631190 494.444468 32 | 650.716804 535.095232 33 | 652.790479 576.203387 34 | 655.153595 617.400630 35 | 627.135230 624.388606 36 | 645.591047 634.155172 37 | 667.510444 642.043329 38 | 692.691852 627.890476 39 | 711.817660 611.173854 40 | 539.866421 518.107865 41 | 560.186372 509.226719 42 | 580.459828 506.487053 43 | 604.702638 509.148236 44 | 581.579341 514.373296 45 | 561.705084 517.844535 46 | 718.456504 489.618126 47 | 738.163245 477.157550 48 | 762.416333 474.094796 49 | 789.781961 473.520555 50 | 765.066354 481.719066 51 | 741.438157 486.614396 52 | 605.892074 671.099399 53 | 631.012049 664.820618 54 | 655.109485 659.704138 55 | 680.952440 662.859835 56 | 712.990314 651.873796 57 | 752.299685 642.364102 58 | 802.512148 633.147540 59 | 763.855425 682.593424 60 | 728.692536 707.771239 61 | 694.970979 716.058721 62 | 664.825795 714.247998 63 | 634.277741 692.217631 64 | 623.117273 673.229421 65 | 657.262839 672.804059 66 | 684.332619 673.376857 67 | 715.844213 663.825111 68 | 785.779286 642.866695 69 | 720.923336 682.662611 70 | 687.665179 690.061233 71 | 659.781064 688.425991 72 | } -------------------------------------------------------------------------------- /bin/data/helen/testset/30427236_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/testset/30427236_2.jpg -------------------------------------------------------------------------------- /bin/data/helen/testset/30427236_2.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 168.831609 387.347550 5 | 179.179520 426.730133 6 | 184.890969 454.505236 7 | 196.455519 491.890389 8 | 213.450715 529.020788 9 | 240.253573 561.743655 10 | 273.129219 588.187338 11 | 326.098668 613.095475 12 | 363.937088 615.366800 13 | 398.682309 597.598311 14 | 428.369960 571.418715 15 | 454.551960 535.476824 16 | 473.855717 492.892904 17 | 480.222537 449.254303 18 | 478.824193 408.320052 19 | 477.258153 370.125034 20 | 477.422519 329.318209 21 | 193.084427 373.580500 22 | 206.825219 345.311972 23 | 236.808715 332.174235 24 | 268.970592 331.814255 25 | 299.435094 333.161976 26 | 339.411916 328.655110 27 | 364.246902 316.431880 28 | 394.232219 309.788666 29 | 423.594809 316.846731 30 | 449.120493 332.316374 31 | 324.812005 361.521140 32 | 329.037547 388.432886 33 | 333.068989 415.648718 34 | 337.284589 443.714437 35 | 302.380665 457.865255 36 | 320.496851 462.188681 37 | 339.804578 465.490037 38 | 355.467425 456.480204 39 | 369.917703 447.693925 40 | 227.494146 380.929449 41 | 245.692402 371.984402 42 | 263.820683 369.846035 43 | 284.916165 375.019149 44 | 264.969939 379.264114 45 | 245.988651 380.859147 46 | 363.125468 364.154731 47 | 379.541186 353.548802 48 | 397.638818 350.814163 49 | 417.810131 352.551485 50 | 400.005351 358.428608 51 | 382.409285 363.268766 52 | 256.826686 497.021155 53 | 288.543223 492.109700 54 | 317.185994 488.977106 55 | 341.171779 489.275613 56 | 364.781617 481.637290 57 | 393.234949 479.039621 58 | 422.181553 474.891684 59 | 398.282081 507.913225 60 | 374.820529 526.126554 61 | 349.202299 534.253165 62 | 324.858047 534.095665 63 | 293.346117 524.815996 64 | 275.184811 500.375311 65 | 318.965774 499.089607 66 | 342.813517 498.731741 67 | 367.661718 492.110940 68 | 407.192400 481.883666 69 | 371.358812 509.467062 70 | 345.940598 517.409424 71 | 321.634601 518.323152 72 | } -------------------------------------------------------------------------------- /bin/data/helen/testset/30542618_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/testset/30542618_1.jpg -------------------------------------------------------------------------------- /bin/data/helen/testset/30542618_1.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 64.275530 137.821361 5 | 66.465218 189.549350 6 | 70.001354 240.798087 7 | 79.017507 295.138196 8 | 95.889441 346.556146 9 | 131.270290 382.258878 10 | 174.406520 404.087306 11 | 210.328232 427.000000 12 | 279.417471 427.000000 13 | 337.217236 427.000000 14 | 370.843363 401.416262 15 | 403.519077 378.497924 16 | 441.862455 341.355401 17 | 457.656846 295.527953 18 | 455.074912 241.145226 19 | 463.110224 188.878144 20 | 466.315865 135.659622 21 | 86.744547 142.095612 22 | 109.096465 110.334072 23 | 146.828433 103.616708 24 | 187.112330 103.748634 25 | 220.882807 118.079851 26 | 283.046862 113.742071 27 | 312.688798 91.691854 28 | 352.026238 89.801886 29 | 389.792386 90.144062 30 | 419.376283 109.814977 31 | 259.021950 149.018330 32 | 260.044261 185.983126 33 | 261.189659 224.530375 34 | 262.840506 262.575921 35 | 219.911969 284.857025 36 | 240.615672 287.533291 37 | 262.941156 291.953976 38 | 285.466955 286.090991 39 | 302.570649 280.891716 40 | 133.628774 162.049155 41 | 157.359706 147.398634 42 | 184.593214 146.413582 43 | 208.600049 164.560535 44 | 183.267668 168.831569 45 | 155.532982 169.387415 46 | 310.755807 159.639914 47 | 332.864220 140.448597 48 | 360.592327 137.721266 49 | 388.234873 150.135907 50 | 364.933500 158.269773 51 | 336.407551 160.629037 52 | 177.115196 331.167642 53 | 210.078422 336.740951 54 | 245.156337 335.837854 55 | 267.553462 342.947070 56 | 286.367150 333.394034 57 | 318.227042 332.342411 58 | 353.661866 327.653517 59 | 323.005591 347.861197 60 | 291.385998 359.943781 61 | 269.849610 361.960350 62 | 247.146046 360.826132 63 | 210.361960 351.395122 64 | 192.464060 337.042490 65 | 245.814356 347.705307 66 | 268.264531 350.878925 67 | 288.564170 346.417510 68 | 344.486522 329.807126 69 | 288.564170 346.417510 70 | 268.264531 350.878925 71 | 245.814356 347.705307 72 | } -------------------------------------------------------------------------------- /bin/data/helen/testset/30844800_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/testset/30844800_1.jpg -------------------------------------------------------------------------------- /bin/data/helen/testset/30844800_1.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 285.585323 535.124790 5 | 285.881901 594.098917 6 | 304.283453 650.463241 7 | 324.917511 706.732363 8 | 349.232473 762.043737 9 | 391.446632 807.899859 10 | 444.965168 840.930763 11 | 513.349939 864.384388 12 | 576.910010 861.183621 13 | 629.919858 837.001122 14 | 670.801043 799.309069 15 | 710.209740 760.286872 16 | 738.813094 710.084160 17 | 749.038296 655.702093 18 | 752.490087 599.577514 19 | 756.084009 537.102146 20 | 748.603748 474.693525 21 | 299.074250 511.253112 22 | 321.143069 474.167269 23 | 356.785649 458.381817 24 | 393.283147 448.337178 25 | 434.489801 446.722460 26 | 522.437791 430.928379 27 | 567.342848 416.698492 28 | 610.564222 403.682424 29 | 660.255622 413.357655 30 | 695.377398 439.217371 31 | 491.471382 495.633491 32 | 496.420709 527.228789 33 | 501.482825 558.939207 34 | 507.039529 591.765779 35 | 479.142698 640.768590 36 | 499.313838 643.303391 37 | 520.116266 642.413543 38 | 537.459159 633.202737 39 | 552.713858 623.321332 40 | 358.599892 537.691516 41 | 377.010385 516.444403 42 | 406.144412 507.455775 43 | 441.774215 521.116010 44 | 416.577451 531.431688 45 | 388.517849 540.974652 46 | 553.214174 495.904514 47 | 574.993669 473.478847 48 | 605.194895 465.509196 49 | 637.144890 475.516763 50 | 613.154044 488.499651 51 | 585.542294 492.672134 52 | 454.711026 731.025672 53 | 481.752636 701.048086 54 | 511.558081 682.724806 55 | 534.824772 684.557780 56 | 562.999573 668.896260 57 | 598.912329 672.245946 58 | 633.347654 683.725633 59 | 609.976816 707.273385 60 | 580.534482 722.136858 61 | 550.187570 733.977109 62 | 528.871588 741.697757 63 | 495.339362 748.083576 64 | 469.467057 726.357271 65 | 518.077070 708.948684 66 | 540.182797 704.851259 67 | 570.530835 693.145076 68 | 616.338179 687.171790 69 | 570.530835 693.145076 70 | 540.182797 704.851259 71 | 518.077070 708.948684 72 | } -------------------------------------------------------------------------------- /bin/data/helen/testset/31681454_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/testset/31681454_1.jpg -------------------------------------------------------------------------------- /bin/data/helen/testset/31681454_1.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 295.290350 247.423760 5 | 288.919748 281.755264 6 | 290.403514 315.957611 7 | 289.967093 348.390379 8 | 295.068690 381.706636 9 | 305.243423 413.429766 10 | 328.230557 441.694609 11 | 351.316665 463.153791 12 | 380.603026 473.402109 13 | 413.551368 474.259129 14 | 442.357589 468.943291 15 | 477.296487 451.034346 16 | 502.455068 426.083159 17 | 519.577595 394.598775 18 | 536.370458 362.428704 19 | 550.559634 334.816788 20 | 561.376854 299.553195 21 | 315.519507 238.737910 22 | 333.502252 216.902715 23 | 361.411606 211.484704 24 | 387.134146 222.691228 25 | 409.542791 234.897962 26 | 452.204318 243.443162 27 | 478.393474 238.330021 28 | 503.771766 241.891951 29 | 525.077911 255.491236 30 | 538.580995 274.559559 31 | 424.501372 264.326708 32 | 419.895138 282.529452 33 | 415.813457 299.609588 34 | 411.508243 317.844421 35 | 381.825101 328.439230 36 | 395.273932 334.258435 37 | 407.812303 339.720237 38 | 422.070335 339.254128 39 | 434.914586 337.665227 40 | 347.854906 255.711503 41 | 364.249117 250.390858 42 | 380.799868 254.751503 43 | 391.646495 268.356286 44 | 377.404724 267.172593 45 | 359.744748 264.291259 46 | 456.892054 280.182845 47 | 473.143756 272.678780 48 | 487.655023 274.956031 49 | 497.586274 286.296309 50 | 484.987835 286.458002 51 | 470.787166 284.498041 52 | 340.212436 359.682843 53 | 365.489192 354.582450 54 | 388.680475 354.148336 55 | 403.322485 361.920162 56 | 423.753367 361.644038 57 | 446.616731 370.811297 58 | 464.040867 385.179627 59 | 438.976183 404.784481 60 | 414.519739 408.244954 61 | 394.539117 408.802221 62 | 377.626003 404.286025 63 | 356.664564 390.435629 64 | 346.587938 364.795870 65 | 386.703936 361.696707 66 | 402.644391 367.730502 67 | 422.475607 367.634196 68 | 456.244267 385.679190 69 | 416.511311 393.930305 70 | 397.661109 393.780861 71 | 380.377211 390.334050 72 | } -------------------------------------------------------------------------------- /bin/data/helen/trainset/10405146_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/trainset/10405146_1.jpg -------------------------------------------------------------------------------- /bin/data/helen/trainset/10405146_1.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 93.541170 752.476703 5 | 117.662904 888.662784 6 | 173.765024 998.968068 7 | 232.594430 1099.388976 8 | 314.436854 1206.253712 9 | 430.254724 1296.735090 10 | 543.522883 1347.896123 11 | 656.728592 1374.082152 12 | 728.780122 1352.026398 13 | 796.810656 1318.174664 14 | 854.000040 1225.063703 15 | 899.960171 1137.124328 16 | 937.326674 1053.556386 17 | 963.145507 978.329622 18 | 981.387858 901.853205 19 | 984.841854 822.694213 20 | 986.318450 716.690422 21 | 208.903085 715.305627 22 | 289.627918 684.600295 23 | 372.726716 681.854890 24 | 455.115756 687.777367 25 | 541.054295 706.297208 26 | 757.742144 647.273011 27 | 797.572854 608.435698 28 | 837.349759 578.645779 29 | 888.235002 556.023482 30 | 940.184042 557.555000 31 | 645.091738 771.045915 32 | 666.826333 854.035708 33 | 690.381713 936.146003 34 | 713.495389 1016.727047 35 | 601.041202 1063.044954 36 | 654.863112 1071.616757 37 | 707.046728 1072.014255 38 | 741.283000 1053.384650 39 | 765.751719 1023.512483 40 | 344.704355 813.498918 41 | 396.607462 773.177911 42 | 459.825721 763.317818 43 | 511.165791 800.744319 44 | 462.730371 822.862619 45 | 400.008928 833.890788 46 | 728.886799 757.878179 47 | 760.541180 703.526870 48 | 817.539574 684.201091 49 | 873.022057 705.234206 50 | 843.068453 741.721109 51 | 788.924515 757.777053 52 | 500.565215 1153.185206 53 | 579.255028 1148.340244 54 | 660.276562 1129.653885 55 | 695.995486 1129.790829 56 | 735.896016 1107.696090 57 | 783.665845 1097.966421 58 | 831.292132 1071.342941 59 | 794.072528 1141.894940 60 | 754.164878 1196.121864 61 | 714.950343 1215.757161 62 | 677.342255 1226.255478 63 | 593.484789 1219.463272 64 | 554.591929 1158.220747 65 | 663.208959 1152.793329 66 | 698.852950 1145.245937 67 | 741.051791 1129.954492 68 | 815.881142 1087.623425 69 | 741.051791 1129.954492 70 | 702.369341 1158.459358 71 | 666.663379 1167.916855 72 | } -------------------------------------------------------------------------------- /bin/data/helen/trainset/10405299_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/trainset/10405299_1.jpg -------------------------------------------------------------------------------- /bin/data/helen/trainset/10405299_1.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 488.765449 571.671901 5 | 479.861899 656.151896 6 | 475.887913 739.546718 7 | 477.998783 819.972379 8 | 497.175352 886.782517 9 | 521.050146 943.163169 10 | 550.849506 1000.722430 11 | 592.417167 1062.757792 12 | 643.829503 1081.769656 13 | 696.499760 1089.777830 14 | 755.076852 1070.157530 15 | 815.506264 1041.628090 16 | 874.426186 1001.707888 17 | 925.926749 967.168374 18 | 969.106103 916.315757 19 | 1000.631360 857.400202 20 | 1027.047452 791.661181 21 | 530.465004 555.351126 22 | 572.414025 572.873637 23 | 610.084781 600.233209 24 | 647.496644 632.788989 25 | 681.531711 662.200246 26 | 839.901046 706.965041 27 | 890.994198 692.827035 28 | 940.844187 693.016976 29 | 989.231762 700.208109 30 | 1025.947280 709.075797 31 | 737.693410 725.178546 32 | 719.481729 782.733255 33 | 700.730677 840.711112 34 | 682.725425 898.013500 35 | 629.373301 876.120824 36 | 651.374917 899.613094 37 | 677.715870 924.153349 38 | 715.231225 918.738074 39 | 745.075279 910.416622 40 | 572.109902 649.724426 41 | 609.265392 647.268877 42 | 650.913151 663.218944 43 | 666.127993 700.711149 44 | 630.310158 694.763116 45 | 590.669552 679.305882 46 | 813.786290 750.927870 47 | 858.682728 732.342206 48 | 901.890081 745.866791 49 | 926.702723 772.423837 50 | 892.782199 779.742856 51 | 852.841523 769.647601 52 | 571.453434 890.171944 53 | 608.450647 913.030909 54 | 651.717799 932.496431 55 | 674.604010 947.144320 56 | 703.236192 947.634458 57 | 744.676105 960.456202 58 | 785.445110 962.463818 59 | 734.016395 999.101138 60 | 684.524130 1010.549536 61 | 652.208184 1004.361896 62 | 626.279159 993.128865 63 | 592.828439 956.621324 64 | 588.522517 907.359014 65 | 648.054541 943.028068 66 | 671.207946 955.107646 67 | 700.744629 959.391105 68 | 747.309945 966.195045 69 | 697.304858 969.598116 70 | 666.230232 966.855516 71 | 642.719695 955.124639 72 | } -------------------------------------------------------------------------------- /bin/data/helen/trainset/10405424_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/trainset/10405424_1.jpg -------------------------------------------------------------------------------- /bin/data/helen/trainset/10405424_1.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 375.965656 513.370994 5 | 384.193087 613.583435 6 | 394.204197 693.713804 7 | 403.727926 776.049546 8 | 431.999486 860.865886 9 | 481.462571 935.534845 10 | 531.066827 993.973753 11 | 574.222857 1028.673790 12 | 644.739984 1039.296358 13 | 716.606142 1038.662270 14 | 784.762575 1009.589837 15 | 853.802777 972.844399 16 | 912.319525 924.227173 17 | 965.282971 857.462547 18 | 1007.671141 779.048519 19 | 1037.160181 698.668638 20 | 1054.518312 607.889928 21 | 423.127259 427.160555 22 | 469.316859 404.571775 23 | 522.035501 406.344228 24 | 569.952193 422.390336 25 | 608.074730 447.529465 26 | 778.714929 468.617509 27 | 846.794556 429.698617 28 | 905.316675 427.470449 29 | 960.551223 449.253999 30 | 991.349003 490.714603 31 | 677.020529 521.979923 32 | 665.860801 584.900889 33 | 653.499812 648.361391 34 | 641.205018 711.858152 35 | 581.015061 728.504767 36 | 609.999101 742.838199 37 | 642.981844 756.838915 38 | 679.455657 753.295167 39 | 718.220933 747.089179 40 | 485.255689 514.631852 41 | 515.902533 493.263566 42 | 563.379375 496.768159 43 | 596.950635 532.857817 44 | 556.070499 538.740893 45 | 512.138954 534.349856 46 | 766.452678 552.592863 47 | 810.423264 525.031935 48 | 856.669650 529.506828 49 | 892.344704 559.775286 50 | 858.527115 574.051230 51 | 810.544272 568.428409 52 | 526.178921 805.114510 53 | 571.050508 798.749640 54 | 613.265940 794.766750 55 | 644.929791 808.867391 56 | 682.153813 803.183829 57 | 736.155957 817.452352 58 | 783.373611 839.778314 59 | 728.268331 886.889315 60 | 671.025697 906.814765 61 | 629.106685 906.242229 62 | 593.267200 896.473158 63 | 558.110604 870.915343 64 | 546.827771 812.364693 65 | 611.328058 815.694673 66 | 643.871339 823.160491 67 | 679.480444 823.198265 68 | 768.054375 840.939120 69 | 676.129939 861.763948 70 | 635.684668 861.149130 71 | 600.859578 852.703146 72 | } -------------------------------------------------------------------------------- /bin/data/helen/trainset/10406776_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/trainset/10406776_1.jpg -------------------------------------------------------------------------------- /bin/data/helen/trainset/10406776_1.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 615.089707 573.690236 5 | 619.649239 647.190878 6 | 634.818475 722.980687 7 | 674.438132 790.871965 8 | 716.972352 849.310561 9 | 761.653046 906.682822 10 | 812.578110 973.690215 11 | 868.293905 1029.215708 12 | 919.726690 1032.287856 13 | 982.881408 1019.200566 14 | 1076.549601 980.141569 15 | 1164.805437 922.347182 16 | 1232.427174 854.379157 17 | 1273.809060 758.648536 18 | 1296.377578 632.870188 19 | 1293.864809 515.255047 20 | 1285.934295 413.601783 21 | 559.264723 448.177825 22 | 574.431824 422.307398 23 | 605.801812 416.031708 24 | 642.593411 417.349649 25 | 675.033849 427.028336 26 | 822.300204 372.652815 27 | 881.537267 315.308258 28 | 954.772581 292.036282 29 | 1033.560138 290.411076 30 | 1097.312732 316.912122 31 | 751.060957 486.763875 32 | 748.173196 548.910703 33 | 743.126077 611.974990 34 | 738.485714 675.327871 35 | 728.820063 716.946581 36 | 758.069946 725.826374 37 | 789.800259 726.153116 38 | 828.444223 714.281102 39 | 864.001729 698.734918 40 | 635.858483 538.518977 41 | 646.386303 504.627113 42 | 688.795437 491.161876 43 | 730.229649 517.820295 44 | 699.600357 536.909407 45 | 662.114526 547.480473 46 | 878.015786 477.278984 47 | 901.928040 439.075339 48 | 951.464610 425.117861 49 | 1004.686357 441.776064 50 | 971.426118 469.217619 51 | 925.427630 480.113083 52 | 767.501461 836.659508 53 | 772.628723 817.989844 54 | 803.665675 796.815026 55 | 830.806046 801.591506 56 | 856.290397 783.829073 57 | 924.829610 782.808691 58 | 1004.697445 775.405742 59 | 955.814296 838.650575 60 | 896.725482 874.700273 61 | 860.670934 885.401501 62 | 827.357527 887.634290 63 | 794.683198 870.577199 64 | 778.307248 831.594275 65 | 813.689873 831.394981 66 | 840.874589 832.566992 67 | 870.917113 821.418167 68 | 986.195954 783.790462 69 | 870.917113 821.418167 70 | 840.874589 832.566992 71 | 813.689873 831.394981 72 | } -------------------------------------------------------------------------------- /bin/data/helen/trainset/1629243_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/trainset/1629243_1.jpg -------------------------------------------------------------------------------- /bin/data/helen/trainset/1629243_1.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 155.448151 373.064295 5 | 158.383444 406.717588 6 | 162.513402 434.935411 7 | 175.984709 460.738097 8 | 194.104677 486.336216 9 | 217.479718 511.378601 10 | 248.489345 533.390828 11 | 281.294831 551.371168 12 | 314.475083 552.788835 13 | 345.340054 546.093322 14 | 372.222516 528.317956 15 | 396.296024 503.461638 16 | 417.453087 475.430369 17 | 426.752056 441.978725 18 | 428.148852 405.021640 19 | 427.197368 366.677546 20 | 424.659469 326.707078 21 | 170.810991 332.802654 22 | 183.996278 314.584193 23 | 206.820419 304.989018 24 | 231.911271 303.674693 25 | 254.641437 310.039867 26 | 297.474985 302.802587 27 | 323.903234 288.341062 28 | 348.442891 282.179147 29 | 375.206844 287.251346 30 | 394.546342 300.648716 31 | 279.489505 339.342424 32 | 281.954883 359.479178 33 | 284.231617 379.633950 34 | 286.494228 400.362417 35 | 266.327035 424.641808 36 | 280.753407 427.297794 37 | 293.530033 428.020745 38 | 305.711325 422.177902 39 | 317.068376 415.671746 40 | 201.978788 357.529197 41 | 213.487527 347.272208 42 | 232.961286 344.304127 43 | 253.039373 352.020788 44 | 235.964879 360.287925 45 | 216.890817 364.837172 46 | 315.892160 341.887220 47 | 333.841029 326.089206 48 | 350.633611 322.923577 49 | 364.705503 327.299809 50 | 355.748370 340.496094 51 | 339.412949 344.285677 52 | 255.157201 473.638858 53 | 268.347298 458.607087 54 | 284.090172 453.628899 55 | 296.207672 454.291640 56 | 311.054897 450.933476 57 | 327.579713 452.346367 58 | 347.217541 461.066255 59 | 331.402889 476.501964 60 | 317.506977 485.738474 61 | 300.924149 489.406967 62 | 287.791611 486.891378 63 | 274.209473 482.205817 64 | 263.632681 470.250881 65 | 285.592311 466.341127 66 | 297.752747 466.301840 67 | 312.695372 464.541807 68 | 337.651715 463.159533 69 | 312.695372 464.541807 70 | 297.752747 466.301840 71 | 285.592311 466.341127 72 | } -------------------------------------------------------------------------------- /bin/data/helen/trainset/1691766_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/trainset/1691766_1.jpg -------------------------------------------------------------------------------- /bin/data/helen/trainset/1691766_1.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 98.968743 340.454975 5 | 109.965812 368.902334 6 | 120.493074 396.432684 7 | 133.094062 428.269305 8 | 149.007565 453.485807 9 | 175.117319 473.951147 10 | 198.191275 486.531048 11 | 219.262163 495.700323 12 | 246.067159 496.157674 13 | 269.215909 491.040441 14 | 294.201890 473.454959 15 | 317.319636 453.772739 16 | 336.580902 429.754207 17 | 348.330187 396.395324 18 | 356.511267 362.656389 19 | 360.482008 330.142775 20 | 365.271506 300.514076 21 | 110.858891 312.914681 22 | 121.302046 289.659182 23 | 143.803461 281.113146 24 | 168.255888 281.304051 25 | 193.173297 288.456931 26 | 236.809865 284.250202 27 | 258.463957 271.701904 28 | 281.591616 263.778966 29 | 306.395683 264.599980 30 | 329.220821 276.267300 31 | 220.877461 311.736859 32 | 223.606225 334.437361 33 | 226.524650 355.938586 34 | 230.030486 379.419704 35 | 201.209214 390.773152 36 | 218.371271 391.730058 37 | 232.715021 392.351607 38 | 248.200330 387.266297 39 | 264.010911 381.018913 40 | 138.017939 324.350255 41 | 150.995162 313.676514 42 | 169.280233 312.298633 43 | 185.180380 322.969974 44 | 168.999708 328.863950 45 | 151.428916 331.044992 46 | 257.800735 316.220914 47 | 273.310831 297.478586 48 | 293.310745 296.114005 49 | 309.482372 301.647580 50 | 297.921663 312.093521 51 | 278.643516 316.130434 52 | 192.897951 433.571973 53 | 205.618224 416.060172 54 | 220.202500 408.145833 55 | 231.754222 410.359112 56 | 248.080426 406.068850 57 | 264.585032 410.630354 58 | 275.659924 426.710482 59 | 268.383310 434.185379 60 | 253.589131 441.750678 61 | 235.957525 445.623312 62 | 223.840896 445.219696 63 | 208.185910 440.050066 64 | 201.014142 432.640802 65 | 221.968839 421.994998 66 | 233.337314 422.247393 67 | 249.972276 419.860405 68 | 269.397970 427.359627 69 | 250.003904 424.788593 70 | 234.276525 427.662440 71 | 222.336291 428.055211 72 | } -------------------------------------------------------------------------------- /bin/data/helen/trainset/232194_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/trainset/232194_1.jpg -------------------------------------------------------------------------------- /bin/data/helen/trainset/232194_1.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 494.752844 566.164983 5 | 508.698857 645.348954 6 | 524.833894 713.917517 7 | 541.095167 777.267451 8 | 574.656087 832.200465 9 | 629.350448 879.571411 10 | 693.905158 917.687573 11 | 755.941047 952.441528 12 | 816.514747 949.595943 13 | 870.363706 934.473118 14 | 919.079490 882.192326 15 | 959.104469 832.646456 16 | 998.038843 775.439284 17 | 1019.793989 715.159361 18 | 1029.034459 665.123117 19 | 1033.215130 615.829678 20 | 1032.797025 552.772278 21 | 548.979941 554.900647 22 | 598.921974 545.002528 23 | 648.455419 540.869162 24 | 702.771796 545.004071 25 | 750.246685 561.324161 26 | 819.402288 552.581857 27 | 862.091921 532.742600 28 | 912.636295 516.372109 29 | 968.449618 510.857998 30 | 1012.027626 524.557362 31 | 798.535709 584.183957 32 | 803.487433 640.529714 33 | 812.476282 700.331446 34 | 818.720433 749.009045 35 | 730.204968 742.642594 36 | 769.341396 764.639715 37 | 819.151907 777.619402 38 | 855.067085 751.287858 39 | 880.191592 724.609585 40 | 623.548460 586.854717 41 | 647.027756 583.711170 42 | 679.089159 581.857658 43 | 700.080280 586.764122 44 | 679.267755 591.448132 45 | 647.080038 592.327300 46 | 865.025744 573.036201 47 | 893.425826 558.796813 48 | 921.789792 556.403347 49 | 953.974441 558.671810 50 | 923.990209 571.709747 51 | 895.758716 574.597129 52 | 663.831903 787.853282 53 | 719.455752 795.095802 54 | 768.601601 798.680312 55 | 806.210042 801.963928 56 | 841.747204 793.632935 57 | 881.268038 780.449889 58 | 930.415827 764.324361 59 | 882.871587 808.186295 60 | 847.820872 831.579931 61 | 811.482431 836.221291 62 | 769.914751 838.954464 63 | 720.993799 821.673135 64 | 679.933935 793.445967 65 | 768.632515 813.434976 66 | 808.877038 814.128961 67 | 843.709436 807.058589 68 | 907.479433 774.972579 69 | 843.709436 807.058589 70 | 808.877038 814.128961 71 | 768.632515 813.434976 72 | } -------------------------------------------------------------------------------- /bin/data/helen/trainset/2908549_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/data/helen/trainset/2908549_1.jpg -------------------------------------------------------------------------------- /bin/data/helen/trainset/2908549_1.pts: -------------------------------------------------------------------------------- 1 | version: 1 2 | n_points: 68 3 | { 4 | 74.180725 356.894839 5 | 90.137142 402.006328 6 | 104.698631 437.796712 7 | 123.004963 469.905705 8 | 145.951464 499.912360 9 | 179.349384 523.715778 10 | 218.494569 541.176115 11 | 257.347406 553.316562 12 | 288.029992 551.079546 13 | 316.980124 537.698887 14 | 331.903183 507.628129 15 | 345.564601 480.570320 16 | 359.705972 446.332288 17 | 365.987618 418.026939 18 | 371.004085 383.027104 19 | 368.773220 347.928692 20 | 358.954914 311.401482 21 | 112.721086 327.085595 22 | 140.668044 310.982664 23 | 166.743528 302.041003 24 | 195.464382 300.075610 25 | 225.724821 303.698148 26 | 274.836105 294.574109 27 | 295.567171 283.136127 28 | 317.280845 277.451398 29 | 338.679017 275.995171 30 | 355.408988 278.698121 31 | 258.433110 333.742570 32 | 265.765438 355.895183 33 | 273.909821 377.743444 34 | 282.314211 400.073094 35 | 252.440978 431.606928 36 | 267.750399 432.272960 37 | 284.043803 430.828518 38 | 293.744452 423.907144 39 | 304.431828 415.811314 40 | 153.194408 354.738954 41 | 167.735240 342.019481 42 | 189.824037 338.241046 43 | 206.510444 347.894427 44 | 192.829310 354.773190 45 | 170.872238 358.563996 46 | 291.101981 331.487865 47 | 307.058626 316.469376 48 | 322.888661 313.111584 49 | 339.754560 320.222404 50 | 327.850190 329.723434 51 | 310.006431 331.740875 52 | 229.949281 484.655525 53 | 253.287284 468.797564 54 | 272.724434 456.543107 55 | 287.906421 458.561910 56 | 299.755601 449.715348 57 | 315.505423 451.871961 58 | 328.373283 454.274215 59 | 322.458579 473.666465 60 | 310.801825 489.513782 61 | 299.478487 498.712837 62 | 283.485582 503.551947 63 | 261.065273 500.954564 64 | 240.638478 482.074953 65 | 275.141729 471.262377 66 | 289.634625 467.435370 67 | 302.335877 460.616981 68 | 321.085458 457.407319 69 | 305.396297 473.851149 70 | 292.982381 481.343179 71 | 278.188010 484.893795 72 | } -------------------------------------------------------------------------------- /bin/data/readme: -------------------------------------------------------------------------------- 1 | Download helen.zip from 2 | 3 | https://ibug.doc.ic.ac.uk/resources/facial-point-annotations/ 4 | 5 | and extract here. 6 | -------------------------------------------------------------------------------- /bin/face_sdm.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/face_sdm.bin -------------------------------------------------------------------------------- /bin/face_sdm.yml: -------------------------------------------------------------------------------- 1 | %YAML:1.0 2 | --- 3 | NUM_LMKS: 68 4 | TRAIN_ROUND: 5 5 | LMKS_EACH_ROUND: [ -1, 15, 22, 22, 0 ] 6 | ID_IN_ROUND_0: [] 7 | ID_IN_ROUND_1: [ 2, 5, 8, 11, 14, 31, 33, 35, 36, 39, 42, 45, 56, 57, 58 ] 8 | ID_IN_ROUND_2: [ 1, 3, 6, 8, 10, 13, 15, 18, 20, 23, 25, 30, 31, 35, 36, 9 | 39, 42, 45, 48, 51, 54, 57 ] 10 | ID_IN_ROUND_3: [ 1, 3, 6, 8, 10, 13, 15, 18, 20, 23, 25, 30, 31, 35, 36, 11 | 39, 42, 45, 48, 51, 54, 57 ] 12 | ID_IN_ROUND_4: [] 13 | NORMAL_WID: 128 14 | NORMAL_BORDER: 64 15 | OpenCV_HAAR: "haarcascade_frontalface_alt2.xml" 16 | SDM_Mat: "face_sdm.bin" 17 | Mean_Shape: [ 7.22406693e+01, 1.16115669e+02, 7.26001892e+01, 18 | 1.31224258e+02, 7.44523849e+01, 1.46285263e+02, 7.78420486e+01, 19 | 1.60833481e+02, 8.39716568e+01, 1.74224945e+02, 9.33170242e+01, 20 | 1.85846054e+02, 1.04591995e+02, 1.95426178e+02, 1.16863205e+02, 21 | 2.02954956e+02, 1.30452255e+02, 2.04861328e+02, 1.43840332e+02, 22 | 2.02190399e+02, 1.55789902e+02, 1.94404922e+02, 1.66909805e+02, 23 | 1.84793961e+02, 1.75974487e+02, 1.73188965e+02, 1.81946762e+02, 24 | 1.59532425e+02, 1.85070694e+02, 1.44819412e+02, 1.86561447e+02, 25 | 1.29835144e+02, 1.86903000e+02, 1.14669502e+02, 8.14464417e+01, 26 | 1.05020027e+02, 8.85629807e+01, 9.79904938e+01, 9.89906082e+01, 27 | 9.58675690e+01, 1.09836174e+02, 9.73094711e+01, 1.19535698e+02, 28 | 1.01535828e+02, 1.36784637e+02, 1.00692787e+02, 1.47089249e+02, 29 | 9.63017960e+01, 1.57985245e+02, 9.47778931e+01, 1.68548767e+02, 30 | 9.70595856e+01, 1.75670776e+02, 1.03982178e+02, 1.28626801e+02, 31 | 1.12867607e+02, 1.28664490e+02, 1.22523773e+02, 1.28737778e+02, 32 | 1.32063141e+02, 1.28877823e+02, 1.41939987e+02, 1.16603638e+02, 33 | 1.48985275e+02, 1.22639198e+02, 1.51042114e+02, 1.28876099e+02, 34 | 1.52633942e+02, 1.35132568e+02, 1.50860657e+02, 1.40744598e+02, 35 | 1.48916962e+02, 9.32850571e+01, 1.14521965e+02, 9.97205276e+01, 36 | 1.10834030e+02, 1.07271622e+02, 1.11006020e+02, 1.13891151e+02, 37 | 1.15936272e+02, 1.07128372e+02, 1.17083046e+02, 9.94389343e+01, 38 | 1.17171547e+02, 1.43008774e+02, 1.15476402e+02, 1.50022232e+02, 39 | 1.10139893e+02, 1.57523270e+02, 1.10138954e+02, 1.63840347e+02, 40 | 1.13615059e+02, 1.58148514e+02, 1.16216209e+02, 1.50607468e+02, 41 | 1.16513306e+02, 1.06817848e+02, 1.66929428e+02, 1.15183548e+02, 42 | 1.63518570e+02, 1.23495644e+02, 1.61708298e+02, 1.28854965e+02, 43 | 1.63164078e+02, 1.34981674e+02, 1.61621170e+02, 1.43495300e+02, 44 | 1.63402969e+02, 1.51785568e+02, 1.66204086e+02, 1.43789322e+02, 45 | 1.74358871e+02, 1.35747757e+02, 1.78089920e+02, 1.29190552e+02, 46 | 1.78880493e+02, 1.23254295e+02, 1.78332764e+02, 1.15077026e+02, 47 | 1.74908890e+02, 1.10196327e+02, 1.67252060e+02, 1.23464348e+02, 48 | 1.66490829e+02, 1.28928619e+02, 1.67056396e+02, 1.35120255e+02, 49 | 1.66303223e+02, 1.48396469e+02, 1.66697311e+02, 1.35275009e+02, 50 | 1.70791473e+02, 1.28982010e+02, 1.71652222e+02, 1.23380440e+02, 51 | 1.71043213e+02 ] 52 | -------------------------------------------------------------------------------- /bin/test_sdm_facealign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/test_sdm_facealign -------------------------------------------------------------------------------- /bin/train_sdm_facealign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/bin/train_sdm_facealign -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | cd bin/data/helen 2 | 3 | cd trainset 4 | ls *.pts -1>ptlist.txt 5 | cd .. 6 | 7 | cd testset 8 | ls *.pts -1>ptlist.txt 9 | cd ../../../.. 10 | 11 | cd bin 12 | mkdir output 13 | cd .. 14 | 15 | mkdir build 16 | cd build 17 | cmake .. 18 | make 19 | 20 | 21 | -------------------------------------------------------------------------------- /crop/figure_68_markup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/crop/figure_68_markup.jpg -------------------------------------------------------------------------------- /crop/meanfaces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/crop/meanfaces.png -------------------------------------------------------------------------------- /crop/test_tile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/crop/test_tile.jpg -------------------------------------------------------------------------------- /examples/.test_image.cpp.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wanglin193/SupervisedDescentMethod/901f8ca3e225f4011c455d2371dd3bc80ee4a41d/examples/.test_image.cpp.swp -------------------------------------------------------------------------------- /examples/test_image.cpp: -------------------------------------------------------------------------------- 1 | #include "opencv2/opencv.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | #include "sdm_fit.h" 7 | 8 | int test_image(cv::Mat & img) 9 | { 10 | SDM sdm_face; 11 | sdm_face.set_min_face(30); 12 | string config_name = "face_sdm.yml"; 13 | if (sdm_face.init(config_name)) 14 | cout << "Load SDM model\n"; 15 | else 16 | return LOAD_MODEL_FAIL; 17 | 18 | float scale = 1.0; 19 | if (img.rows <= img.cols && img.rows > 500) scale = 480.0 / img.rows; 20 | if (img.rows > img.cols && img.cols > 500) scale = 480.0 / img.cols; 21 | 22 | if (scale < 1.0) 23 | cv::resize(img, img, cv::Size(), scale, scale, cv::INTER_LINEAR); 24 | 25 | vector shapes; 26 | 27 | if (sdm_face.fit(img, shapes)) 28 | { 29 | cout << shapes.size() << " faces found.\n"; 30 | for (int j = 0; j < shapes.size(); j++) 31 | { 32 | cv::rectangle(img, sdm_face.detected_faces[j], cv::Scalar(0,0,255)); 33 | draw_shape(img, shapes[j], cv::Scalar(255, 0, 0)); 34 | } 35 | imshow("image", img); 36 | waitKey(); 37 | } 38 | return SDM_OK; 39 | } 40 | 41 | 42 | int main() 43 | { 44 | int ret = -1; 45 | 46 | cv::Mat im = imread("data/helen/testset/325564774_1.jpg"); 47 | ret = test_image(im); 48 | 49 | return ret; 50 | } -------------------------------------------------------------------------------- /examples/test_testset.cpp: -------------------------------------------------------------------------------- 1 | #include "opencv2/opencv.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | #include "sdm_fit.h" 7 | 8 | int test_on_testset() 9 | { 10 | SDM sdm_face; 11 | string config_name = "face_sdm.yml"; 12 | if (sdm_face.init(config_name)) 13 | cout << "Load SDM model\n"; 14 | else 15 | return LOAD_MODEL_FAIL; 16 | 17 | string imagepath = "data/helen/testset"; 18 | //dir *.pts/b>ptlist.txt 19 | //ls *.pts -1>ptlist.txt 20 | string ptlistname = imagepath + "/ptlist.txt"; 21 | vector vstrPts, vstrImg; 22 | 23 | read_names_pair(ptlistname, imagepath, string(), ".jpg", vstrPts, vstrImg); 24 | if (vstrImg.empty()) 25 | return INPUT_FAIL; 26 | 27 | for (int i = 0; i < vstrImg.size(); i++) 28 | { 29 | cv::Mat img = cv::imread(vstrImg[i], -1);//-1 origimage,0 grayscale 30 | float scale = 1.0; 31 | if (img.rows <= img.cols && img.rows > 400) scale = 320.0 / img.rows; 32 | if (img.rows > img.cols && img.cols > 400) scale = 320.0 / img.cols; 33 | 34 | if (scale < 1.0) 35 | cv::resize(img, img, cv::Size(), scale, scale, cv::INTER_LINEAR); 36 | 37 | cout << vstrImg[i] << endl; 38 | vector shapes; 39 | 40 | TIC; 41 | int ret = sdm_face.fit(img, shapes); 42 | TOC; 43 | if (ret) 44 | { 45 | cout << shapes.size() << " faces found.\n"; 46 | for (int j = 0; j < shapes.size(); j++) 47 | draw_shape(img, shapes[j], cv::Scalar(255, 0, 0)); 48 | 49 | imshow("faces", img); 50 | waitKey(100); 51 | 52 | //find name without path 53 | int pos1=vstrImg[i].find_last_of('/'); 54 | string file_name(vstrImg[i].substr(pos1+1)); 55 | imwrite("output/" + file_name, img); 56 | } 57 | else 58 | cout << "No face found\n"; 59 | } 60 | return SDM_OK; 61 | } 62 | 63 | 64 | int main() 65 | { 66 | int ret = test_on_testset(); 67 | 68 | return ret; 69 | } -------------------------------------------------------------------------------- /examples/test_webcam.cpp: -------------------------------------------------------------------------------- 1 | #include "opencv2/opencv.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | #include "sdm_fit.h" 7 | 8 | int test_webcam() 9 | { 10 | SDM sdm_face; 11 | sdm_face.set_min_face(80); 12 | string config_name = "face_sdm.yml"; 13 | if (sdm_face.init(config_name)) 14 | cout << "Load SDM model\n"; 15 | else 16 | return LOAD_MODEL_FAIL; 17 | 18 | cv::Mat img, gray; 19 | 20 | VideoCapture capture; 21 | capture.open(0); 22 | 23 | if (capture.isOpened()) 24 | { 25 | std::cout << "Capture is opened." << std::endl; 26 | for (;;) 27 | { 28 | capture >> img; 29 | if (img.empty()) 30 | { 31 | cout << " capture error.\n"; 32 | continue; 33 | } 34 | // resize(img, img, Size(), 0.5, 0.5, CV_INTER_LINEAR); 35 | cvtColor(img, gray, CV_BGR2GRAY); 36 | vector shapes; 37 | TIC; 38 | if (sdm_face.fit(gray, shapes)) 39 | { 40 | //sdm_face.show_crop(); 41 | //cout << shapes.size() << " faces found.\n"; 42 | for (int j = 0; j < shapes.size(); j++) 43 | { 44 | cv::rectangle(img, sdm_face.detected_faces[j], cv::Scalar(0, 0, 255)); 45 | draw_shape(img, shapes[j], cv::Scalar(255, 0, 0)); 46 | } 47 | } 48 | TOC; 49 | imshow("Video", img); 50 | if (waitKey(10) == 27) 51 | break; 52 | } 53 | } 54 | else 55 | { 56 | cout << " NO camera input." << endl; 57 | return INPUT_FAIL; 58 | } 59 | return SDM_OK; 60 | } 61 | 62 | int main() 63 | { 64 | int ret = test_webcam(); 65 | 66 | return ret; 67 | } -------------------------------------------------------------------------------- /examples/train_sdm_facealign.cpp: -------------------------------------------------------------------------------- 1 | #include "opencv2/opencv.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "sdm_common.h" 8 | //-------------params for training---------- 9 | const int NUM_ROUND = 5; 10 | vector hogs(NUM_ROUND); 11 | 12 | int num_lmks[NUM_ROUND] = { HOG_GLOBAL,15,22,22,HOG_ALL }; 13 | int id_lmks[NUM_ROUND][68] = { { }, 14 | { 2,5,8,11,14, 31,33,35, 36, 39,42,45,56,57,58 },//15 15 | { 1, 3, 6, 8, 10, 13, 15, 18, 20, 23, 25, 30, 31, 35, 36, 39, 42, 45, 48, 51, 54, 57 },//22 16 | { 1, 3, 6, 8, 10, 13, 15, 18, 20, 23, 25, 30, 31, 35, 36, 39, 42, 45, 48, 51, 54, 57 }, 17 | {} }; 18 | 19 | int normal_face_width = 128; //normalize face detect rectangle 20 | int border = 64; //normalize ROI size = normal_face_width + 2*border 21 | string face_model = "haarcascade_frontalface_alt2.xml"; 22 | //-------------------------------------------- 23 | //in case fail to detect face, use this to init face Rect: 24 | //rect_x_y_w = M_eyes_to_rect * eyes_x1_x2_y 25 | cv::Mat M_eyes_to_rect; 26 | 27 | shape2d shape_mean; 28 | vector train_shapes; 29 | vector train_samples; 30 | 31 | //L = M*R ---> L*RT = M*R*RT ---> L*RT*(lambda*I+R*RT)^-1 = M 32 | cv::Mat solve_norm_equation(cv::Mat& L, cv::Mat& R) 33 | { 34 | cv::Mat M; 35 | cv::Mat LRT = L*R.t(), RRT = R*R.t(); 36 | 37 | cout << "Begin solving..."; 38 | float lambda = 0.5;// 0.050f * ((float)cv::norm(RRT)) / (float)(RRT.rows); 39 | 40 | for (int i = 0; i < RRT.cols - 1; i++) 41 | RRT.at(i, i) = lambda + RRT.at(i, i); 42 | M = LRT*RRT.inv(cv::DECOMP_LU); 43 | cout << " done." << endl; 44 | return M; 45 | } 46 | 47 | //3dof face rect to 3dof eyes position 48 | void train_map_facerect_and_eyes(vector& vstrPts, vector& vstrImg) 49 | { 50 | int eyes_index[4] = { 36,39,42,45 }; 51 | cv::CascadeClassifier face_cascade; 52 | if (!face_cascade.load(face_model)) 53 | { 54 | cout << "Error loading face detection model." << endl; 55 | return; 56 | } 57 | 58 | cout << "Train mapping between eyes and detect-rectangle ....." << endl; 59 | int good = 0, numsamples = 200; 60 | cv::Mat L = Mat::zeros(3, 3, CV_32F); 61 | cv::Mat R = Mat::zeros(3, 3, CV_32F); 62 | cv::Mat M; //L=M*R 63 | for (int i = 0; i < numsamples/*vstrPts.size()*/; i++) 64 | { 65 | shape2d pts = read_pts_landmarks(vstrPts[i]); 66 | cv::Mat img = cv::imread(vstrImg[i], 0); 67 | 68 | float scale = 1.0; 69 | if (img.rows <= img.cols && img.rows > 200) scale = 160.0 / img.rows; 70 | if (img.rows > img.cols && img.cols > 200) scale = 160.0 / img.cols; 71 | 72 | cv::resize(img, img, cv::Size(), scale, scale, cv::INTER_LINEAR); 73 | vector detected_faces; 74 | face_cascade.detectMultiScale(img, detected_faces, 1.2, 4, 0, cv::Size(50, 50)); 75 | 76 | if (detected_faces.size() == 0) 77 | continue; 78 | 79 | cv::Point2f p0 = (pts[eyes_index[0]] + pts[eyes_index[1]])*scale*0.5; 80 | cv::Point2f p1 = (pts[eyes_index[2]] + pts[eyes_index[3]])*scale*0.5; 81 | 82 | cv::Rect rectface = detected_faces[0]; 83 | if (rectface.contains(p0) && rectface.contains(p1)) 84 | { 85 | good++; 86 | 87 | Mat ll = (Mat_(3, 1) << rectface.x, rectface.y, (rectface.width + rectface.height) / 2); 88 | Mat rr = (Mat_(3, 1) << p0.x, (p0.y + p1.y) / 2, p1.x); 89 | L = L + ll*rr.t(); 90 | R = R + rr*rr.t(); 91 | } 92 | } 93 | cout << "Collect " << good << " good faces in " << numsamples << " images. " << endl; 94 | 95 | M = L*R.inv(); 96 | M_eyes_to_rect = M.clone(); 97 | cout << "M_eyes_to_rect:\n" << M_eyes_to_rect << endl << endl; 98 | } 99 | 100 | void preprocess_images(vector& vstrPts, vector& vstrImg) 101 | { 102 | int eyes_index[4] = { 36,39,42,45 }; 103 | cv::CascadeClassifier face_cascade; 104 | face_cascade.load(face_model); 105 | 106 | bool showimg = true; 107 | Mat imcanvas; 108 | RNG rng((unsigned)time(NULL)); 109 | cout << "Collecting cropped " << vstrPts.size() << " faces and normlized shapes ..."; 110 | int count = 0; 111 | for (int i = 0; i < vstrPts.size(); i++) 112 | { 113 | shape2d pts = read_pts_landmarks(vstrPts[i]); 114 | cv::Mat img_ = cv::imread(vstrImg[i], 0);//-1 origimage,0 grayscale 115 | cv::Mat img; 116 | float scale = 1.0; 117 | if (img_.rows <= img_.cols && img_.rows > 500) scale = 400.0 / img_.rows; 118 | if (img_.rows > img_.cols && img_.cols > 500) scale = 400.0 / img_.cols; 119 | cv::resize(img_, img, cv::Size(), scale, scale, cv::INTER_LINEAR); 120 | 121 | vector detected_faces; 122 | face_cascade.detectMultiScale(img, detected_faces, 1.2, 4, 0, cv::Size(50, 50)); 123 | 124 | cv::Point2f p0 = (pts[eyes_index[0]] + pts[eyes_index[1]])*scale*0.5; 125 | cv::Point2f p1 = (pts[eyes_index[2]] + pts[eyes_index[3]])*scale*0.5; 126 | 127 | int idx = -1; 128 | for (int i = 0; i < detected_faces.size(); i++) 129 | { 130 | //eye lmks in this face rectangle 131 | if (detected_faces[i].contains(p0) && detected_faces[i].contains(p1)) 132 | idx = i; 133 | } 134 | 135 | cv::Rect rectface; 136 | //from eyes' position to get face detection rectangle 137 | if (detected_faces.size() == 0 || idx == -1) 138 | { 139 | Mat rr = (Mat_(3, 1) << p0.x, (p0.y + p1.y) / 2, p1.x); 140 | Mat ll = M_eyes_to_rect*rr; 141 | rectface = cv::Rect(ll.at(0), ll.at(1), ll.at(2), ll.at(2)); 142 | if (showimg) 143 | { 144 | cv::circle(img, Point(p0.x, p0.y), 2, cv::Scalar(255, 255, 255), -1); 145 | cv::circle(img, Point(p1.x, p1.y), 2, cv::Scalar(255, 255, 255), -1); 146 | cv::rectangle(img, rectface, cv::Scalar(255, 255, 255), 1); 147 | } 148 | } 149 | else 150 | { 151 | rectface = detected_faces[idx]; 152 | if (showimg) 153 | { 154 | cv::rectangle(img, rectface, cv::Scalar(0, 0, 255), 1); 155 | 156 | //from face detection rect to get eyes' position 157 | Mat ll = (Mat_(3, 1) << rectface.x, rectface.y, rectface.width); 158 | Mat rr = M_eyes_to_rect.inv()*ll; //rr = M^-1*ll 159 | cv::circle(img, Point(rr.at(0), rr.at(1)), 2, cv::Scalar(0, 0, 255), -1); 160 | cv::circle(img, Point(rr.at(2), rr.at(1)), 2, cv::Scalar(0, 0, 255), -1); 161 | } 162 | } 163 | if (showimg) 164 | { 165 | imshow("img", img); 166 | waitKey(100); 167 | } 168 | /////////perturb/////////// 169 | float sigma = 0.15; 170 | float n1 = rng.gaussian(sigma), n2 = rng.gaussian(sigma), n3 = rng.gaussian(sigma); 171 | 172 | rectface.x += (n1-n3*0.5)*(float)rectface.width; 173 | rectface.y += (n2-n3*0.5)*(float)rectface.width; 174 | rectface.width += n3*(float)rectface.width ; 175 | // cout << n1 << " " << n2 << " " << n3 << endl; 176 | 177 | cv::Mat roiImg; 178 | int normal_roi_width = normal_face_width + border * 2; 179 | 180 | float scale_again = (float)normal_face_width / (float)rectface.width; 181 | float shift_x = border - scale_again*rectface.x, shift_y = border - scale_again*rectface.y; 182 | Mat mat2roi = (Mat_(2, 3) << scale_again, 0, shift_x, 0, scale_again, shift_y); 183 | warpAffine(img, roiImg, mat2roi, Size(normal_roi_width, normal_roi_width), INTER_LINEAR, BORDER_CONSTANT, 128); 184 | 185 | //lmks on normalized ROI 186 | float scale2roi = scale_again*scale; 187 | float dx = mat2roi.at(0, 2); 188 | float dy = mat2roi.at(1, 2); 189 | for (int j = 0; j < pts.size(); j++) 190 | { 191 | pts[j].x = pts[j].x *scale2roi + dx; 192 | pts[j].y = pts[j].y *scale2roi + dy; 193 | } 194 | 195 | //average shape at normal scale 196 | if (i == 0) 197 | { 198 | shape_mean.resize(pts.size()); 199 | for (int j = 0; j < pts.size(); j++) 200 | shape_mean[j] = cv::Point2f(pts[j].x, pts[j].y); 201 | } 202 | else 203 | { 204 | for (int j = 0; j < pts.size(); j++) 205 | shape_mean[j] = shape_mean[j] + pts[j]; 206 | } 207 | count++; 208 | 209 | train_shapes.push_back(pts); 210 | train_samples.push_back(roiImg); 211 | 212 | if (showimg) 213 | { 214 | cvtColor(roiImg, imcanvas, COLOR_GRAY2RGB); 215 | HOGDescriptor hog = hogs[3]; 216 | int half_wid = hog.blockSize.width >> 1; 217 | for (int j = 0; j < pts.size(); j++) 218 | { 219 | if (pts[j].xroiImg.cols || pts[j].y + half_wid > roiImg.rows) 220 | cout << "Warning: HOG region out of image :" << i << " at lmk " << j << endl; 221 | cv::circle(imcanvas, cv::Point(pts[j].x, pts[j].y), 2, cv::Scalar(0, 0, 255), -1); 222 | Rect r(pts[j].x - half_wid, pts[j].y - half_wid, hog.blockSize.width, hog.blockSize.width); 223 | cv::rectangle(imcanvas, r, cv::Scalar(0, 255, 0), 1); 224 | } 225 | char name[200]; 226 | sprintf(name, "output/f_%d.png", i); 227 | imwrite(name, imcanvas); 228 | imshow("Face ROI", imcanvas); 229 | waitKey(10); 230 | } 231 | }//end collecting 232 | 233 | for (int j = 0; j < shape_mean.size(); j++) shape_mean[j] = shape_mean[j] * (1.0f / (float)count); 234 | cout << " done." << endl; 235 | if (showimg) 236 | { 237 | cv::Mat avgimg, temp; 238 | train_samples[0].convertTo(avgimg, CV_32FC1); 239 | 240 | for (int i = 1; i < train_samples.size(); i++) 241 | { 242 | train_samples[i].convertTo(temp, CV_32FC1); 243 | avgimg = avgimg + temp; 244 | } 245 | avgimg = avgimg / train_samples.size(); 246 | 247 | HOGDescriptor hog = hogs[0]; //draw hog[0] feature rect 248 | int half_wid = hog.blockSize.width >> 1; 249 | avgimg.convertTo(imcanvas, CV_8UC1); 250 | cvtColor(imcanvas, imcanvas, COLOR_GRAY2RGB); 251 | 252 | for (int j = 0; j < shape_mean.size(); j++) 253 | { 254 | cv::circle(imcanvas, cv::Point(shape_mean[j].x, shape_mean[j].y), 2, cv::Scalar(0, 0, 255), -1); 255 | Rect r(shape_mean[j].x - half_wid, shape_mean[j].y - half_wid, hog.blockSize.width, hog.blockSize.width); 256 | cv::rectangle(imcanvas, r, cv::Scalar(0, 255, 0), 1); 257 | } 258 | cout << "Press any to continue." << endl; 259 | imshow("meanface with shape_mean", imcanvas); 260 | waitKey(); 261 | } 262 | } 263 | 264 | void set_idx_lmks(vector& num_lmks, vector >& lmk_all) 265 | { 266 | for (int i = 0; i < num_lmks.size(); i++) 267 | { 268 | if (num_lmks[i] == HOG_GLOBAL || num_lmks[i] == HOG_ALL) 269 | { 270 | vector t; 271 | lmk_all.push_back(t); 272 | } 273 | else 274 | { 275 | vector t(id_lmks[i], id_lmks[i] + num_lmks[i]); 276 | lmk_all.push_back(t); 277 | } 278 | } 279 | } 280 | 281 | void train_by_regress() 282 | { 283 | int numsample_test = 10; //left some for test 284 | int numsample = train_shapes.size() - numsample_test; 285 | int numpts = shape_mean.size(); 286 | 287 | cout << "Training SDM on " << numsample << " images, left " << numsample_test << " for testing ..." << endl; 288 | 289 | vector vnum_lmks(num_lmks, num_lmks + NUM_ROUND); 290 | vector > vlmk_all; 291 | set_idx_lmks(vnum_lmks, vlmk_all); 292 | 293 | //set HOG size 294 | set_HOGs(hogs, normal_face_width + border, vnum_lmks); 295 | 296 | for (int i = 0; i < hogs.size(); i++) 297 | { 298 | cout << "HOG Round: " << i << " winsize: " << hogs[i].winSize << ", length of descriptor: " << hogs[i].getDescriptorSize() << endl; 299 | if (num_lmks[i] > 0) for (int j = 0; j < vnum_lmks[i]; j++) cout << id_lmks[i][j] << ","; 300 | else { num_lmks[i] == 0 ? cout << " use all 68 lmks" : cout << " use whole face ROI"; }; cout << endl; 301 | } 302 | cout << endl; 303 | 304 | vector shape_current_all; 305 | //init as meashape 306 | for (int i = 0; i < numsample; i++) 307 | shape_current_all.push_back(shape_mean); 308 | 309 | Mat mDiffShape(numpts * 2, numsample, CV_32F); 310 | Mat mFeature; 311 | vector Mreg; 312 | for (int r = 0; r < hogs.size(); r++) 313 | { 314 | cout << "---- Round " << r << " -----" << endl; 315 | for (int i = 0; i < numsample; i++) 316 | { 317 | Mat roiImg = train_samples[i]; 318 | vector des = extract_HOG_descriptor(roiImg, shape_current_all[i], hogs[r], vnum_lmks[r], vlmk_all[r]); 319 | if (i == 0) 320 | { 321 | mFeature.create(des.size(), numsample, CV_32F); 322 | cout << " training sample: " << numsample << ", length of feature: " << des.size() << endl; 323 | } 324 | 325 | Mat rr = Mat(des.size(), 1, CV_32F, des.data()); 326 | rr.copyTo(mFeature.col(i)); //must use copyTo 327 | 328 | Mat v_dest = shape2d_to_mat(train_shapes[i]) - shape2d_to_mat(shape_current_all[i]); 329 | v_dest.copyTo(mDiffShape.col(i)); 330 | }//end collect faces 331 | 332 | //regression 333 | cv::Mat M = solve_norm_equation(mDiffShape, mFeature); 334 | Mreg.push_back(M); 335 | 336 | //renew current shapes 337 | Mat mIncShape = M*mFeature; 338 | float E = 0.0f; 339 | for (int i = 0; i < numsample; i++) 340 | { 341 | Mat v_new = mIncShape.col(i) + shape2d_to_mat(shape_current_all[i]); 342 | shape_current_all[i] = mat_to_shape2d(v_new); 343 | 344 | Mat v_err = shape2d_to_mat(train_shapes[i]) - v_new; 345 | E = E + cv::norm(v_err, NORM_L2); 346 | } 347 | E /= numsample; 348 | cout << " Avg Shape Error = " << E << endl; 349 | }//end training round 350 | 351 | cout << "Save training result ..."; 352 | string name_M = "face_sdm.bin"; 353 | FileStorage fs("face_sdm.yml", FileStorage::WRITE); 354 | if (fs.isOpened()) 355 | { 356 | fs << "NUM_LMKS" << numpts; 357 | fs << "TRAIN_ROUND" << NUM_ROUND; 358 | fs << "LMKS_EACH_ROUND" << vnum_lmks; 359 | for (int i = 0; i < vlmk_all.size(); i++) 360 | { 361 | string id_lmk = "ID_IN_ROUND_"; 362 | //if (vnum_lmks[i] != -1 && vnum_lmks[i] != 0) 363 | // fs << id_lmk + to_string(_Longlong(i)) << vlmk_all[i]; 364 | fs << id_lmk + to_string(long(i)) << vlmk_all[i]; 365 | } 366 | 367 | fs << "NORMAL_WID" << normal_face_width << "NORMAL_BORDER" << border; 368 | fs << "OpenCV_HAAR" << face_model; 369 | fs << "SDM_Mat" << name_M; 370 | fs << "Mean_Shape" << shape_mean; 371 | fs.release(); 372 | } 373 | mats_write(name_M.c_str(), Mreg); 374 | cout << " done." << endl; 375 | 376 | if (1) 377 | { 378 | cout << "Testing on unseen images ... "; 379 | Mat imcanvas; 380 | for (int i = numsample; i < train_samples.size(); i++)//on rest image in trainset 381 | { 382 | Mat roiImg = train_samples[i]; 383 | cvtColor(roiImg, imcanvas, COLOR_GRAY2RGB); 384 | draw_shape(imcanvas, shape_mean, cv::Scalar(255, 0, 0)); 385 | shape2d pts = shape_mean; 386 | for (int r = 0; r < Mreg.size(); r++) 387 | { 388 | vector des = extract_HOG_descriptor(roiImg, pts, hogs[r], vnum_lmks[r], vlmk_all[r]); 389 | Mat rr = Mat(des.size(), 1, CV_32F, des.data()); 390 | Mat v_shape = Mreg[r] * rr + shape2d_to_mat(pts); 391 | pts = mat_to_shape2d(v_shape); 392 | 393 | if (r == 0) draw_shape(imcanvas, pts, cv::Scalar(0, 0, 255)); 394 | } 395 | draw_shape(imcanvas, pts, cv::Scalar(0, 255, 0)); 396 | char name[200]; 397 | sprintf(name, "output/test_%d.png", i); 398 | imwrite(name, imcanvas); 399 | } 400 | cout << "done\n"; 401 | } 402 | } 403 | 404 | int main() 405 | { 406 | string imagepath = "data/helen/trainset"; 407 | string ptlistname = imagepath + "/ptlist.txt"; //dir *.pts/b > ptlist.txt 408 | vector vstrPts, vstrImg; 409 | 410 | read_names_pair(ptlistname, imagepath, string(), ".jpg", vstrPts, vstrImg); 411 | if(vstrPts.size()<30) 412 | { 413 | cout<<"Need more face images in path " << imagepath << endl; 414 | return 1; 415 | } 416 | 417 | train_map_facerect_and_eyes(vstrPts, vstrImg); 418 | 419 | preprocess_images(vstrPts, vstrImg); 420 | train_by_regress(); 421 | return 0; 422 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # SDM for face alignment 2 | 3 | 1. Try training result on test set 4 | ~~~ 5 | cd ./bin 6 | ./test_sdm_facealign 7 | ~~~ 8 | 9 | 2. Create pts file name list for load training images, use 10 | ~~~ 11 | cd trainset 12 | ls *.pts -1>ptlist.txt 13 | ~~~ 14 | or in Windows use 15 | ~~~ 16 | cd trainset 17 | dir *.pts/b>ptlist.txt 18 | ~~~ 19 | 20 | 3. Some results: 21 | 22 | * blue dots: init position (meanshape), same for all 23 | 24 | * red dos: after first round descent iteration 25 | 26 | * green dots: after all five rounds descent iteration 27 | 28 | ![test_tile](https://github.com/wanglin193/SupervisedDescentMethod/blob/master/crop/test_tile.jpg) 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | cd bin 2 | ./train_sdm_facealign 3 | ./test_sdm_facealign 4 | 5 | -------------------------------------------------------------------------------- /src/sdm_common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | using namespace std; 4 | using namespace cv; 5 | 6 | #define HOG_GLOBAL -1 7 | #define HOG_ALL 0 8 | 9 | static double time_begin; 10 | #define TIC (time_begin = (double)cvGetTickCount()); 11 | #define TOC (printf(" Time = %g ms \r", ((double)cvGetTickCount() - time_begin)/((double)cvGetTickFrequency()*1000.) ) ); 12 | 13 | typedef vector shape2d; 14 | 15 | inline void set_global_hog(HOGDescriptor&hog, int roi_size, int pix_in_cell, int cell_in_block, int n_orient) 16 | { 17 | int cell_in_win = (int)((float)roi_size / (float)pix_in_cell); 18 | hog.winSize = Size(cell_in_win*pix_in_cell, cell_in_win*pix_in_cell); 19 | hog.cellSize = Size(pix_in_cell * cell_in_block, pix_in_cell*cell_in_block); 20 | hog.blockSize = cv::Size(cell_in_block * hog.cellSize.width, cell_in_block * hog.cellSize.height); 21 | hog.blockStride = hog.cellSize; 22 | hog.nbins = n_orient; 23 | } 24 | inline void set_hog_params(HOGDescriptor&hog, int pix_in_cell, int cell_in_block, int n_orient) 25 | { 26 | hog.cellSize = Size(pix_in_cell, pix_in_cell); 27 | hog.blockSize = cv::Size(cell_in_block * hog.cellSize.width, cell_in_block * hog.cellSize.height); 28 | hog.winSize = hog.blockSize; //this define feature region 29 | hog.blockStride = hog.winSize;//useless when set hog.winSize = hog.blockSize; 30 | hog.nbins = n_orient; 31 | } 32 | inline void set_HOGs(vector& hogs,int global_size,vector& vnum_lmks) 33 | { 34 | int num_hog = hogs.size(); 35 | int pix_in_cell = (int)(global_size*0.1 + 0.5); 36 | 37 | if (vnum_lmks[0] == HOG_GLOBAL) 38 | set_global_hog(hogs[0], global_size, 8, 4, 9); 39 | else 40 | set_hog_params(hogs[0], pix_in_cell, 4, 6); 41 | 42 | if (num_hog > 1) set_hog_params(hogs[1], pix_in_cell, 4, 6); 43 | if (num_hog > 2) set_hog_params(hogs[2], pix_in_cell, 4, 4); 44 | for (int i = 3; i mats_read(const char* filename) 49 | { 50 | vector vM; 51 | FILE* file = fopen(filename, "rb"); 52 | if (file == NULL) 53 | return vM; 54 | 55 | int num; 56 | fread(&num, sizeof(int), 1, file); 57 | 58 | for (int i = 0; i < num; i++) 59 | { 60 | int headData[3]; 61 | fread(headData, sizeof(int), 3, file); 62 | Mat M(headData[1], headData[0], headData[2]); 63 | fread(M.data, sizeof(char), M.step * M.rows, file); 64 | vM.push_back(M); 65 | } 66 | fclose(file); 67 | return vM; 68 | } 69 | inline bool mats_write(const char* filename, vector& vM) 70 | { 71 | FILE* file = fopen(filename, "wb"); 72 | if (file == NULL || vM.empty()) 73 | return false; 74 | 75 | int num = vM.size(); 76 | fwrite(&num, sizeof(int), 1, file); 77 | 78 | for (int i = 0; i < num; i++) 79 | { 80 | Mat M = vM[i];// cout << M.step << " " << M.cols << "*" << M.rows << endl; 81 | int headData[3] = { M.cols, M.rows, M.type() }; 82 | fwrite(headData, sizeof(int), 3, file); 83 | fwrite(M.data, sizeof(char), M.step * M.rows, file); 84 | } 85 | fclose(file); 86 | return true; 87 | } 88 | 89 | inline cv::Mat shape2d_to_mat(vector&pts) 90 | { 91 | int numpts = pts.size(); 92 | Mat v(numpts * 2, 1, CV_32F); 93 | for (int j = 0; j < numpts; j++) 94 | { 95 | v.at(2 * j) = pts[j].x; 96 | v.at(2 * j + 1) = pts[j].y; 97 | } 98 | return v; 99 | } 100 | inline shape2d mat_to_shape2d(Mat & v) 101 | { 102 | int numpts = v.rows / 2; 103 | assert(v.cols == 1); 104 | shape2d pts(numpts, cv::Point2f(0, 0)); 105 | 106 | for (int j = 0; j < numpts; j++) 107 | { 108 | pts[j].x = v.at(2 * j); 109 | pts[j].y = v.at(2 * j + 1); 110 | } 111 | return pts; 112 | } 113 | 114 | inline shape2d read_pts_landmarks(const std::string filename) 115 | { 116 | using std::getline; 117 | shape2d landmarks; 118 | landmarks.reserve(68); 119 | 120 | std::ifstream file(filename); 121 | if (!file.is_open()) { 122 | throw std::runtime_error(string("Could not open landmark file: " + filename)); 123 | } 124 | 125 | string line; 126 | // Skip the first 3 lines, they're header lines: 127 | getline(file, line); // 'version: 1' 128 | getline(file, line); // 'n_points : 68' 129 | getline(file, line); // '{' 130 | 131 | while (getline(file, line)) 132 | { 133 | if (line == "}") { // end of the file 134 | break; 135 | } 136 | std::stringstream line_stream(line); 137 | cv::Point2f landmark(0.0f, 0.0f); 138 | if (!(line_stream >> landmark.x >> landmark.y)) { 139 | throw std::runtime_error(string("Landmark format error while parsing the line: " + line)); 140 | } 141 | landmark.x -= 1.0f; 142 | landmark.y -= 1.0f; 143 | landmarks.emplace_back(landmark); 144 | } 145 | return landmarks; 146 | }; 147 | inline void read_names_pair(const string &strListName, const string &strFilePath, 148 | const string& pts_ext, const string& img_ext, 149 | vector &vstrShapeName, vector &vstrImageName) 150 | { 151 | ifstream fNameFile; 152 | fNameFile.open(strListName.c_str()); 153 | vstrShapeName.clear(); 154 | vstrImageName.clear(); 155 | while (!fNameFile.eof()) 156 | { 157 | string s; 158 | getline(fNameFile, s); 159 | if (!s.empty()) 160 | { 161 | stringstream ss; 162 | ss << s; 163 | 164 | //find name without path 165 | string namefull = ss.str() ; 166 | int pos1=namefull.find_last_of('/'); 167 | string file_name(namefull.substr(pos1+1)); 168 | 169 | string ptname = strFilePath + "/" + file_name + pts_ext; 170 | string imagename = ptname.substr(0, ptname.find(".pts")) + img_ext; 171 | 172 | vstrShapeName.push_back(ptname); 173 | vstrImageName.push_back(imagename); 174 | } 175 | } 176 | } 177 | 178 | inline vector extract_HOG_descriptor(Mat & roiImg, vector& pt_shape, HOGDescriptor& hog, int num_idx_lmk, vector& idx_lmk) 179 | { 180 | int half_wid = hog.winSize.width >> 1; 181 | vector des; 182 | vector pos; 183 | if (num_idx_lmk == HOG_GLOBAL) //face region 184 | { 185 | pos.push_back(Point(roiImg.cols / 2 - half_wid, roiImg.rows / 2 - half_wid)); 186 | } 187 | else if (num_idx_lmk == HOG_ALL) 188 | { 189 | for (int j = 0; j < pt_shape.size(); j++) 190 | pos.push_back(Point(pt_shape[j].x - half_wid + 0.5, pt_shape[j].y - half_wid + 0.5)); 191 | } 192 | else 193 | { 194 | for (int j = 0; j < num_idx_lmk; j++) 195 | { 196 | Point2f pcorner = pt_shape[idx_lmk[j]] - Point2f(half_wid, half_wid); 197 | pos.push_back(Point(pcorner.x + 0.5, pcorner.y + 0.5)); 198 | } 199 | } 200 | hog.compute(roiImg, des, Size(0, 0), Size(0, 0), pos); //Size winStride = Size(0, 0); 201 | des.push_back(1.0); 202 | return des; 203 | } 204 | 205 | inline void draw_shape(Mat& imcanvas, shape2d& pts, cv::Scalar color) 206 | { 207 | for (int j = 0; j < pts.size(); j++) 208 | cv::circle(imcanvas, cv::Point(pts[j].x, pts[j].y), 2, color, -1); 209 | } 210 | -------------------------------------------------------------------------------- /src/sdm_fit.cpp: -------------------------------------------------------------------------------- 1 | #include "opencv2/opencv.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | #include "sdm_fit.h" 7 | 8 | bool SDM::init(string& configfile) 9 | { 10 | string name_M, face_model;// = "haarcascade_frontalface_alt2.xml"; 11 | cv::FileStorage fs(configfile, cv::FileStorage::READ); 12 | if (!fs.isOpened()) { cout << "ERROR: Wrong path to settings" << endl; return false; } 13 | fs["NUM_LMKS"] >> numpts; 14 | fs["TRAIN_ROUND"] >> NUM_ROUND; 15 | fs["LMKS_EACH_ROUND"] >> vnum_lmks; 16 | fs["NUM_LMKS"] >> numpts; 17 | for (int i = 0; i < vnum_lmks.size(); i++) 18 | { 19 | vector obj; 20 | //fs["ID_IN_ROUND_" + to_string(_Longlong(i))] >> obj; 21 | fs["ID_IN_ROUND_" + to_string(long(i))] >> obj; 22 | vlmk_all.push_back(obj); 23 | } 24 | fs["NORMAL_WID"] >> normal_face_width; 25 | fs["NORMAL_BORDER"] >> border; 26 | fs["OpenCV_HAAR"] >> face_model; 27 | 28 | fs["Mean_Shape"] >> shape_mean; 29 | fs["SDM_Mat"] >> name_M; 30 | face_cascade.load(face_model); 31 | 32 | Mreg = mats_read(name_M.c_str()); 33 | hogs.resize(NUM_ROUND); 34 | 35 | //set HOG size 36 | set_HOGs(hogs, normal_face_width + border, vnum_lmks); 37 | return true; 38 | } 39 | 40 | void SDM::regression(Mat &roiImg, shape2d &pts) 41 | { 42 | for (int r = 0; r < Mreg.size(); r++) 43 | { 44 | vector des = extract_HOG_descriptor(roiImg, pts, hogs[r], vnum_lmks[r], vlmk_all[r]); 45 | Mat rr = Mat(des.size(), 1, CV_32F, des.data()); 46 | Mat v_shape = Mreg[r] * rr + shape2d_to_mat(pts); 47 | pts = mat_to_shape2d(v_shape); 48 | } 49 | } 50 | 51 | bool SDM::fit(Mat & img_, vector& shapes) 52 | { 53 | cv::Mat img, img_gray; 54 | 55 | if (img_.channels() > 1) 56 | cvtColor(img_, img_gray, CV_BGR2GRAY); 57 | else 58 | img_gray = img_.clone(); 59 | 60 | face_cascade.detectMultiScale(img_gray, detected_faces, 1.2, 4, 0, cv::Size(min_face_size, min_face_size)); 61 | 62 | if (detected_faces.size() == 0) 63 | return false; 64 | 65 | shapes.clear(); 66 | for (int i = 0; i < detected_faces.size(); i++) 67 | { 68 | cv::Rect rectface = detected_faces[i]; 69 | cv::Mat roiImg; 70 | int normal_roi_width = normal_face_width + border * 2; 71 | 72 | float scale = (float)normal_face_width / (float)rectface.width; 73 | float shift_x = border - scale*rectface.x, shift_y = border - scale*rectface.y; 74 | Mat mat2roi = (Mat_(2, 3) << scale, 0, shift_x, 0, scale, shift_y); 75 | warpAffine(img_gray, roiImg, mat2roi, Size(normal_roi_width, normal_roi_width), INTER_LINEAR, BORDER_CONSTANT, 128); 76 | 77 | shape2d pts(shape_mean.size()); 78 | if (1)//detect mode: start from meanshape 79 | { 80 | for (int j = 0; j < shape_mean.size(); j++) 81 | pts[j] = shape_mean[j]; 82 | } 83 | //else //tracking mode: update shape from current state (or previous frame) 84 | //{ 85 | // for (int j = 0; j < pts.size(); j++) 86 | // { 87 | // pts[j].x = 88 | // pts[j].y = 89 | // } 90 | //} 91 | regression(roiImg, pts); 92 | if (1) 93 | { 94 | string win_name = "roi "; 95 | cv::Rect roi(border, border, normal_face_width, normal_face_width); 96 | cv::rectangle(roiImg, roi, cv::Scalar(255)); 97 | draw_shape(roiImg, pts, cv::Scalar(255)); 98 | // imshow(win_name + to_string(_Longlong(i)), roiImg); 99 | imshow(win_name + to_string(long(i)), roiImg); 100 | } 101 | //back to original img 102 | float scale_back = 1.0 / mat2roi.at(0, 0); 103 | float dx_back = -scale_back*mat2roi.at(0, 2); 104 | float dy_back = -scale_back*mat2roi.at(1, 2); 105 | for (int j = 0; j < pts.size(); j++) 106 | { 107 | pts[j].x = pts[j].x*scale_back + dx_back; 108 | pts[j].y = pts[j].y*scale_back + dy_back; 109 | } 110 | shapes.push_back(pts); 111 | } 112 | return true; 113 | } 114 | -------------------------------------------------------------------------------- /src/sdm_fit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "opencv2/opencv.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | #include "sdm_common.h" 8 | 9 | enum { SDM_OK, LOAD_MODEL_FAIL, INPUT_FAIL }; 10 | 11 | struct SDM 12 | { 13 | int numpts; 14 | int NUM_ROUND; 15 | int normal_face_width; 16 | int border; 17 | int min_face_size; 18 | 19 | shape2d shape_mean; 20 | 21 | vector vnum_lmks; 22 | 23 | vector > vlmk_all; 24 | 25 | vector hogs; 26 | 27 | vector Mreg; 28 | 29 | cv::CascadeClassifier face_cascade; 30 | 31 | vector detected_faces; 32 | 33 | SDM() { min_face_size = 50; }; 34 | 35 | void set_min_face(int sz) 36 | { 37 | min_face_size = sz; 38 | } 39 | 40 | bool init(string& configfile); 41 | 42 | void regression(Mat &roiImg, shape2d &pts); 43 | 44 | bool fit(Mat & img_, vector& shapes); 45 | }; 46 | 47 | -------------------------------------------------------------------------------- /src/test_image.cpp: -------------------------------------------------------------------------------- 1 | #include "opencv2/opencv.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | #include "sdm_common.h" 7 | #include "sdm_fit.h" 8 | 9 | int test_image(cv::Mat & img) 10 | { 11 | SDM sdm_face; 12 | sdm_face.set_min_face(30); 13 | string config_name = "face_sdm.yml"; 14 | if (sdm_face.init(config_name)) 15 | cout << "Load SDM model\n"; 16 | else 17 | return LOAD_MODEL_FAIL; 18 | 19 | float scale = 1.0; 20 | if (img.rows <= img.cols && img.rows > 500) scale = 480.0 / img.rows; 21 | if (img.rows > img.cols && img.cols > 500) scale = 480.0 / img.cols; 22 | 23 | if (scale < 1.0) 24 | cv::resize(img, img, cv::Size(), scale, scale, cv::INTER_LINEAR); 25 | 26 | vector shapes; 27 | 28 | if (sdm_face.fit(img, shapes)) 29 | { 30 | cout << shapes.size() << " faces found.\n"; 31 | for (int j = 0; j < shapes.size(); j++) 32 | { 33 | cv::rectangle(img, sdm_face.detected_faces[j], cv::Scalar(0,0,255)); 34 | draw_shape(img, shapes[j], cv::Scalar(255, 0, 0)); 35 | } 36 | imshow("image", img); 37 | waitKey(); 38 | } 39 | return SDM_OK; 40 | } 41 | 42 | 43 | int main() 44 | { 45 | int ret = -1; 46 | 47 | cv::Mat im = imread("data/helen/testset/325564774_1.jpg"); 48 | ret = test_image(im); 49 | 50 | return ret; 51 | } --------------------------------------------------------------------------------