Some people have asked me how I've aligned the face images in my articles. Giving people my ImageMagick hack is embarrassing for me, so I've decided to rewrite it into a Python script. You don't need to copy and paste it, as the script comes with the source folder of my Guide on Face Recognition: crop_face.py.
The code is really easy to use. To scale, rotate and crop the face image you just need to callCropFace(image, eye_left, eye_right, offset_pct, dest_sz)
, where:
eye_left
is the position of the left eyeeye_right
is the position of the right eyeoffset_pct
is the percent of the image you want to keep next to the eyes (horizontal, vertical direction)dest_sz
is the size of the output imageIf you are using the same offset_pct
and dest_sz
for your images, they are all aligned at the eyes.
Imagine we are given this photo of Arnold Schwarzenegger, which is under a Public Domain license. The(x,y)
-position of the eyes are approximately(252,364)
for the left and(420,366)
for the right eye. Now you only need to define the horizontal offset, vertical offset and the size your scaled, rotated & cropped face should have. Here are some examples:
Configuration | Cropped, Scaled, Rotated Face |
---|---|
0.1 (10%), 0.1 (10%), (200,200) | |
0.2 (20%), 0.2 (20%), (200,200) | |
0.3 (30%), 0.3 (30%), (200,200) | |
0.2 (20%), 0.2 (20%), (70,70) |
#!/usr/bin/env python # Software License Agreement (BSD License) # # Copyright (c) 2012, Philipp Wagner # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # * Neither the name of the author(s) nor the names of its # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import sys, math, Image def Distance(p1,p2): dx = p2[0] - p1[0] dy = p2[1] - p1[1] return math.sqrt(dx*dx+dy*dy) def ScaleRotateTranslate(image, angle, center = None, new_center = None, scale = None, resample=Image.BICUBIC): if (scale is None) and (center is None): return image.rotate(angle=angle, resample=resample) nx,ny = x,y = center sx=sy=1.0 if new_center: (nx,ny) = new_center if scale: (sx,sy) = (scale, scale) cosine = math.cos(angle) sine = math.sin(angle) a = cosine/sx b = sine/sx c = x-nx*a-ny*b d = -sine/sy e = cosine/sy f = y-nx*d-ny*e return image.transform(image.size, Image.AFFINE, (a,b,c,d,e,f), resample=resample) def CropFace(image, eye_left=(0,0), eye_right=(0,0), offset_pct=(0.2,0.2), dest_sz = (70,70)): # calculate offsets in original image offset_h = math.floor(float(offset_pct[0])*dest_sz[0]) offset_v = math.floor(float(offset_pct[1])*dest_sz[1]) # get the direction eye_direction = (eye_right[0] - eye_left[0], eye_right[1] - eye_left[1]) # calc rotation angle in radians rotation = -math.atan2(float(eye_direction[1]),float(eye_direction[0])) # distance between them dist = Distance(eye_left, eye_right) # calculate the reference eye-width reference = dest_sz[0] - 2.0*offset_h # scale factor scale = float(dist)/float(reference) # rotate original around the left eye image = ScaleRotateTranslate(image, center=eye_left, angle=rotation) # crop the rotated image crop_xy = (eye_left[0] - scale*offset_h, eye_left[1] - scale*offset_v) crop_size = (dest_sz[0]*scale, dest_sz[1]*scale) image = image.crop((int(crop_xy[0]), int(crop_xy[1]), int(crop_xy[0]+crop_size[0]), int(crop_xy[1]+crop_size[1]))) # resize it image = image.resize(dest_sz, Image.ANTIALIAS) return image if __name__ == "__main__": image = Image.open("arnie.jpg") CropFace(image, eye_left=(252,364), eye_right=(420,366), offset_pct=(0.1,0.1), dest_sz=(200,200)).save("arnie_10_10_200_200.jpg") CropFace(image, eye_left=(252,364), eye_right=(420,366), offset_pct=(0.2,0.2), dest_sz=(200,200)).save("arnie_20_20_200_200.jpg") CropFace(image, eye_left=(252,364), eye_right=(420,366), offset_pct=(0.3,0.3), dest_sz=(200,200)).save("arnie_30_30_200_200.jpg") CropFace(image, eye_left=(252,364), eye_right=(420,366), offset_pct=(0.2,0.2)).save("arnie_20_20_70_70.jpg")
opencv 仿射变换
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/imgtrans/warp_affine/warp_affine.html
c#code
Image<Bgr, byte> cutImg(Image<Bgr, byte> img,Point eyeleft,Point eyeright) { double[] offset = { 0.2, 0.2 }; int dest_w = 100; int dest_h = 100; double angle = -Math.Atan2(eyeright.Y - eyeleft.Y, eyeright.X -eyeleft.X); double Dik = Math.Sqrt(Math.Pow(eyeleft.X - eyeright.X, 2) + Math.Pow(eyeleft.Y - eyeright.Y, 2)); int offset_h = Convert.ToInt16(offset[0] * dest_w); int offset_v = Convert.ToInt16(offset[1] * dest_h); double reference = dest_w - 2.0 * offset_h; double scale = Dik / reference; img = img.Rotate(angle / Math.PI * 180, eyeleft, INTER.CV_INTER_AREA, new Bgr(Color.Black), true); Rectangle rect = new Rectangle(new Point(Convert.ToInt16(eyeleft.X - scale * offset_h), Convert.ToInt16(eyeleft.Y - scale * offset_v)), new Size(Convert.ToInt16(dest_w * scale), Convert.ToInt16(dest_w * scale))); img.ROI = rect; img = img.Resize(dest_w, dest_h, Emgu.CV.CvEnum.INTER.CV_INTER_AREA); return img; }
C++code
Mat cutImage(Mat src,Point2f eyeleft,Point2f eyeright) { Mat dst; dst=Mat::zeros( src.rows, src.cols, src.type()); float offset[2] = { 0.2, 0.2 }; int dest_w = 100; int dest_h =100; float angle = atan2(eyeright.y - eyeleft.y, eyeright.x -eyeleft.x); float Dik = sqrt(pow(eyeleft.x - eyeright.x, 2) + pow(eyeleft.y - eyeright.y, 2)); int offset_h =offset[0] * dest_w; int offset_v =offset[1] * dest_h; float reference = dest_w - 2.0 * offset_h; float scale = Dik / reference; Mat rot_mat(2,3,CV_32FC1); angle=angle/CV_PI*180.0; rot_mat=getRotationMatrix2D(eyeleft,angle,scale); warpAffine(src,dst,rot_mat,src.size()); Rect rect((int)(eyeleft.x-scale*offset_h),(int)(eyeleft.y - scale * offset_v),(int)(dest_w * scale), (int)(dest_w * scale)); dst=dst(rect); Mat dst2=Mat(Size(dest_w,dest_h),src.type()); resize(dst,dst2,dst2.size()); //namedWindow("warp_window",CV_WINDOW_AUTOSIZE); //imshow("warp_window",dst2); //namedWindow("src_window",CV_WINDOW_AUTOSIZE); //imshow("src_window",dst); //waitKey(0); return dst2; }