├── AIMakeup.py ├── LICENSE ├── MakupGUI.ui ├── README.md ├── Ui_MakupGUI.py ├── Ui_MakupGUI.spec ├── example.gif ├── make.bat └── raw └── 10.jpg /AIMakeup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Sep 12 12:36:11 2017 4 | 5 | @author: Quantum Liu 6 | """ 7 | 8 | import sys,os,traceback 9 | import cv2 10 | import dlib 11 | import numpy as np 12 | 13 | class NoFace(Exception): 14 | ''' 15 | 没脸 16 | ''' 17 | pass 18 | 19 | class Organ(): 20 | def __init__(self,im_bgr,im_hsv,temp_bgr,temp_hsv,landmark,name,ksize=None): 21 | ''' 22 | 五官部位类 23 | arguments: 24 | im_bgr:uint8 array, inference of BGR image 25 | im_hsv:uint8 array, inference of HSV image 26 | temp_bgr/hsv:global temp image 27 | landmark:array(x,2), landmarks 28 | name:string 29 | ''' 30 | self.im_bgr,self.im_hsv,self.landmark,self.name=im_bgr,im_hsv,landmark,name 31 | self.get_rect() 32 | self.shape=(int(self.bottom-self.top),int(self.right-self.left)) 33 | self.size=self.shape[0]*self.shape[1]*3 34 | self.move=int(np.sqrt(self.size/3)/20) 35 | self.ksize=self.get_ksize() 36 | self.patch_bgr,self.patch_hsv=self.get_patch(self.im_bgr),self.get_patch(self.im_hsv) 37 | self.set_temp(temp_bgr,temp_hsv) 38 | self.patch_mask=self.get_mask_re() 39 | pass 40 | 41 | def set_temp(self,temp_bgr,temp_hsv): 42 | self.im_bgr_temp,self.im_hsv_temp=temp_bgr,temp_hsv 43 | self.patch_bgr_temp,self.patch_hsv_temp=self.get_patch(self.im_bgr_temp),self.get_patch(self.im_hsv_temp) 44 | 45 | def confirm(self): 46 | ''' 47 | 确认操作 48 | ''' 49 | self.im_bgr[:],self.im_hsv[:]=self.im_bgr_temp[:],self.im_hsv_temp[:] 50 | 51 | def update_temp(self): 52 | ''' 53 | 更新临时图片 54 | ''' 55 | self.im_bgr_temp[:],self.im_hsv_temp[:]=self.im_bgr[:],self.im_hsv[:] 56 | 57 | def get_ksize(self,rate=15): 58 | size=max([int(np.sqrt(self.size/3)/rate),1]) 59 | size=(size if size%2==1 else size+1) 60 | return (size,size) 61 | 62 | def get_rect(self): 63 | ''' 64 | 获得定位方框 65 | ''' 66 | ys,xs=self.landmark[:,1],self.landmark[:,0] 67 | self.top,self.bottom,self.left,self.right=np.min(ys),np.max(ys),np.min(xs),np.max(xs) 68 | 69 | def get_patch(self,im): 70 | ''' 71 | 截取局部切片 72 | ''' 73 | shape=im.shape 74 | return im[np.max([self.top-self.move,0]):np.min([self.bottom+self.move,shape[0]]),np.max([self.left-self.move,0]):np.min([self.right+self.move,shape[1]])] 75 | 76 | def _draw_convex_hull(self,im, points, color): 77 | ''' 78 | 勾画多凸边形 79 | ''' 80 | points = cv2.convexHull(points) 81 | cv2.fillConvexPoly(im, points, color=color) 82 | 83 | def get_mask_re(self,ksize=None): 84 | ''' 85 | 获得局部相对坐标遮罩 86 | ''' 87 | if ksize==None: 88 | ksize=self.ksize 89 | landmark_re=self.landmark.copy() 90 | landmark_re[:,1]-=np.max([self.top-self.move,0]) 91 | landmark_re[:,0]-=np.max([self.left-self.move,0]) 92 | mask = np.zeros(self.patch_bgr.shape[:2], dtype=np.float64) 93 | 94 | self._draw_convex_hull(mask, 95 | landmark_re, 96 | color=1) 97 | 98 | mask = np.array([mask, mask, mask]).transpose((1, 2, 0)) 99 | 100 | mask = (cv2.GaussianBlur(mask, ksize, 0) > 0) * 1.0 101 | return cv2.GaussianBlur(mask, ksize, 0)[:] 102 | 103 | def get_mask_abs(self,ksize=None): 104 | ''' 105 | 获得全局绝对坐标遮罩 106 | ''' 107 | if ksize==None: 108 | ksize=self.ksize 109 | mask = np.zeros(self.im_bgr.shape, dtype=np.float64) 110 | patch=self.get_patch(mask) 111 | patch[:]=self.patch_mask[:] 112 | return mask 113 | 114 | def whitening(self,rate=0.15,confirm=True): 115 | ''' 116 | 提亮美白 117 | arguments: 118 | rate:float,-1~1,new_V=min(255,V*(1+rate)) 119 | confirm:wether confirm this option 120 | ''' 121 | if confirm: 122 | self.confirm() 123 | self.patch_hsv[:,:,-1]=np.minimum(self.patch_hsv[:,:,-1]+self.patch_hsv[:,:,-1]*self.patch_mask[:,:,-1]*rate,255).astype('uint8') 124 | self.im_bgr[:]=cv2.cvtColor(self.im_hsv, cv2.COLOR_HSV2BGR)[:] 125 | self.update_temp() 126 | else: 127 | self.patch_hsv_temp[:]=cv2.cvtColor(self.patch_bgr_temp, cv2.COLOR_BGR2HSV)[:] 128 | self.patch_hsv_temp[:,:,-1]=np.minimum(self.patch_hsv_temp[:,:,-1]+self.patch_hsv_temp[:,:,-1]*self.patch_mask[:,:,-1]*rate,255).astype('uint8') 129 | self.patch_bgr_temp[:]=cv2.cvtColor(self.patch_hsv_temp, cv2.COLOR_HSV2BGR)[:] 130 | 131 | def brightening(self,rate=0.3,confirm=True): 132 | ''' 133 | 提升鲜艳度 134 | arguments: 135 | rate:float,-1~1,new_S=min(255,S*(1+rate)) 136 | confirm:wether confirm this option 137 | ''' 138 | patch_mask=self.get_mask_re((1,1)) 139 | if confirm: 140 | self.confirm() 141 | patch_new=self.patch_hsv[:,:,1]*patch_mask[:,:,1]*rate 142 | patch_new=cv2.GaussianBlur(patch_new,(3,3),0) 143 | self.patch_hsv[:,:,1]=np.minimum(self.patch_hsv[:,:,1]+patch_new,255).astype('uint8') 144 | self.im_bgr[:]=cv2.cvtColor(self.im_hsv, cv2.COLOR_HSV2BGR)[:] 145 | self.update_temp() 146 | else: 147 | self.patch_hsv_temp[:]=cv2.cvtColor(self.patch_bgr_temp, cv2.COLOR_BGR2HSV)[:] 148 | patch_new=self.patch_hsv_temp[:,:,1]*patch_mask[:,:,1]*rate 149 | patch_new=cv2.GaussianBlur(patch_new,(3,3),0) 150 | self.patch_hsv_temp[:,:,1]=np.minimum(self.patch_hsv[:,:,1]+patch_new,255).astype('uint8') 151 | self.patch_bgr_temp[:]=cv2.cvtColor(self.patch_hsv_temp, cv2.COLOR_HSV2BGR)[:] 152 | 153 | def smooth(self,rate=0.6,ksize=None,confirm=True): 154 | ''' 155 | 磨皮 156 | arguments: 157 | rate:float,0~1,im=rate*new+(1-rate)*src 158 | confirm:wether confirm this option 159 | ''' 160 | if ksize==None: 161 | ksize=self.get_ksize(80) 162 | index=self.patch_mask>0 163 | if confirm: 164 | self.confirm() 165 | patch_new=cv2.GaussianBlur(cv2.bilateralFilter(self.patch_bgr,3,*ksize),ksize,0) 166 | self.patch_bgr[index]=np.minimum(rate*patch_new[index]+(1-rate)*self.patch_bgr[index],255).astype('uint8') 167 | self.im_hsv[:]=cv2.cvtColor(self.im_bgr, cv2.COLOR_BGR2HSV)[:] 168 | self.update_temp() 169 | else: 170 | patch_new=cv2.GaussianBlur(cv2.bilateralFilter(self.patch_bgr_temp,3,*ksize),ksize,0) 171 | self.patch_bgr_temp[index]=np.minimum(rate*patch_new[index]+(1-rate)*self.patch_bgr_temp[index],255).astype('uint8') 172 | self.patch_hsv_temp[:]=cv2.cvtColor(self.patch_bgr_temp, cv2.COLOR_BGR2HSV)[:] 173 | 174 | def sharpen(self,rate=0.3,confirm=True): 175 | ''' 176 | 锐化 177 | ''' 178 | patch_mask=self.get_mask_re((3,3)) 179 | kernel = np.zeros( (9,9), np.float32) 180 | kernel[4,4] = 2.0 #Identity, times two! 181 | #Create a box filter: 182 | boxFilter = np.ones( (9,9), np.float32) / 81.0 183 | 184 | #Subtract the two: 185 | kernel = kernel - boxFilter 186 | index=patch_mask>0 187 | if confirm: 188 | self.confirm() 189 | sharp=cv2.filter2D(self.patch_bgr,-1,kernel) 190 | self.patch_bgr[index]=np.minimum(((1-rate)*self.patch_bgr)[index]+sharp[index]*rate,255).astype('uint8') 191 | self.update_temp() 192 | else: 193 | sharp=cv2.filter2D(self.patch_bgr_temp,-1,kernel) 194 | self.patch_bgr_temp[:]=np.minimum(self.patch_bgr_temp+self.patch_mask*sharp*rate,255).astype('uint8') 195 | self.patch_hsv_temp[:]=cv2.cvtColor(self.patch_bgr_temp, cv2.COLOR_BGR2HSV)[:] 196 | 197 | class Forehead(Organ): 198 | def __init__(self,im_bgr,im_hsv,temp_bgr,temp_hsv,landmark,mask_organs,name,ksize=None): 199 | self.mask_organs=mask_organs 200 | super(Forehead,self).__init__(im_bgr,im_hsv,temp_bgr,temp_hsv,landmark,name,ksize) 201 | 202 | def get_mask_re(self,ksize=None): 203 | ''' 204 | 获得局部相对坐标遮罩 205 | ''' 206 | if ksize==None: 207 | ksize=self.ksize 208 | landmark_re=self.landmark.copy() 209 | landmark_re[:,1]-=np.max([self.top-self.move,0]) 210 | landmark_re[:,0]-=np.max([self.left-self.move,0]) 211 | mask = np.zeros(self.patch_bgr.shape[:2], dtype=np.float64) 212 | 213 | self._draw_convex_hull(mask, 214 | landmark_re, 215 | color=1) 216 | 217 | mask = np.array([mask, mask, mask]).transpose((1, 2, 0)) 218 | 219 | mask = (cv2.GaussianBlur(mask, ksize, 0) > 0) * 1.0 220 | patch_organs=self.get_patch(self.mask_organs) 221 | mask= cv2.GaussianBlur(mask, ksize, 0)[:] 222 | mask[patch_organs>0]=(1-patch_organs[patch_organs>0]) 223 | return mask 224 | 225 | class Face(Organ): 226 | ''' 227 | 脸类 228 | arguments: 229 | im_bgr:uint8 array, inference of BGR image 230 | im_hsv:uint8 array, inference of HSV image 231 | temp_bgr/hsv:global temp image 232 | landmarks:list, landmark groups 233 | index:int, index of face in the image 234 | ''' 235 | def __init__(self,im_bgr,img_hsv,temp_bgr,temp_hsv,landmarks,index): 236 | self.index=index 237 | #五官名称 238 | self.organs_name=['jaw','mouth','nose','left eye','right eye','left brow','right brow'] 239 | 240 | #五官等标记点 241 | self.organs_points=[list(range(0, 17)),list(range(48, 61)),list(range(27, 35)),list(range(42, 48)),list(range(36, 42)),list(range(22, 27)),list(range(17, 22))] 242 | 243 | #实例化脸对象和五官对象 244 | self.organs={name:Organ(im_bgr,img_hsv,temp_bgr,temp_hsv,landmarks[points],name) for name,points in zip(self.organs_name,self.organs_points)} 245 | 246 | #获得额头坐标,实例化额头 247 | mask_nose=self.organs['nose'].get_mask_abs() 248 | mask_organs=(self.organs['mouth'].get_mask_abs()+mask_nose+self.organs['left eye'].get_mask_abs()+self.organs['right eye'].get_mask_abs()+self.organs['left brow'].get_mask_abs()+self.organs['right brow'].get_mask_abs()) 249 | forehead_landmark=self.get_forehead_landmark(im_bgr,landmarks,mask_organs,mask_nose) 250 | self.organs['forehead']=Forehead(im_bgr,img_hsv,temp_bgr,temp_hsv,forehead_landmark,mask_organs,'forehead') 251 | mask_organs+=self.organs['forehead'].get_mask_abs() 252 | 253 | # 人脸的完整标记点 254 | self.FACE_POINTS = np.concatenate([landmarks,forehead_landmark]) 255 | super(Face,self).__init__(im_bgr,img_hsv,temp_bgr,temp_hsv,self.FACE_POINTS,'face') 256 | 257 | mask_face=self.get_mask_abs()-mask_organs 258 | self.patch_mask=self.get_patch(mask_face) 259 | pass 260 | 261 | 262 | def get_forehead_landmark(self,im_bgr,face_landmark,mask_organs,mask_nose): 263 | ''' 264 | 计算额头坐标 265 | ''' 266 | #画椭圆 267 | radius=(np.linalg.norm(face_landmark[0]-face_landmark[16])/2).astype('int32') 268 | center_abs=tuple(((face_landmark[0]+face_landmark[16])/2).astype('int32')) 269 | 270 | angle=np.degrees(np.arctan((lambda l:l[1]/l[0])(face_landmark[16]-face_landmark[0]))).astype('int32') 271 | mask=np.zeros(mask_organs.shape[:2], dtype=np.float64) 272 | cv2.ellipse(mask,center_abs,(radius,radius),angle,180,360,1,-1) 273 | #剔除与五官重合部分 274 | mask[mask_organs[:,:,0]>0]=0 275 | #根据鼻子的肤色判断真正的额头面积 276 | index_bool=[] 277 | for ch in range(3): 278 | mean,std=np.mean(im_bgr[:,:,ch][mask_nose[:,:,ch]>0]),np.std(im_bgr[:,:,ch][mask_nose[:,:,ch]>0]) 279 | up,down=mean+0.5*std,mean-0.5*std 280 | index_bool.append((im_bgr[:,:,ch]up)) 281 | index_zero=((mask>0)&index_bool[0]&index_bool[1]&index_bool[2]) 282 | mask[index_zero]=0 283 | index_abs=np.array(np.where(mask>0)[::-1]).transpose() 284 | landmark=cv2.convexHull(index_abs).squeeze() 285 | return landmark 286 | 287 | class Makeup(): 288 | ''' 289 | 化妆器 290 | ''' 291 | def __init__(self,predictor_path="./data/shape_predictor_68_face_landmarks.dat"): 292 | self.photo_path=[] 293 | self.PREDICTOR_PATH = predictor_path 294 | self.faces={} 295 | 296 | #人脸定位、特征提取器,来自dlib 297 | self.detector = dlib.get_frontal_face_detector() 298 | self.predictor = dlib.shape_predictor(self.PREDICTOR_PATH) 299 | 300 | def get_faces(self,im_bgr,im_hsv,temp_bgr,temp_hsv,name,n=1): 301 | ''' 302 | 人脸定位和特征提取,定位到两张及以上脸或者没有人脸将抛出异常 303 | im: 304 | 照片的numpy数组 305 | fname: 306 | 照片名字的字符串 307 | 返回值: 308 | 人脸特征(x,y)坐标的矩阵 309 | ''' 310 | rects = self.detector(im_bgr, 1) 311 | 312 | if len(rects) <1: 313 | raise NoFace('Too many faces in '+name) 314 | return {name:[Face(im_bgr,im_hsv,temp_bgr,temp_hsv,np.array([[p.x, p.y] for p in self.predictor(im_bgr, rect).parts()]),i) for i,rect in enumerate(rects)]} 315 | 316 | def read_im(self,fname,scale=1): 317 | ''' 318 | 读取图片 319 | ''' 320 | im = cv2.imdecode(np.fromfile(fname,dtype=np.uint8),-1) 321 | if type(im)==type(None): 322 | print(fname) 323 | raise ValueError('Opencv error reading image "{}" , got None'.format(fname)) 324 | return im 325 | 326 | def read_and_mark(self,fname): 327 | im_bgr=self.read_im(fname) 328 | im_hsv=cv2.cvtColor(im_bgr, cv2.COLOR_BGR2HSV) 329 | temp_bgr,temp_hsv=im_bgr.copy(),im_hsv.copy() 330 | return im_bgr,temp_bgr,self.get_faces(im_bgr,im_hsv,temp_bgr,temp_hsv,fname) 331 | 332 | if __name__=='__main__': 333 | path='./heads/5.jpg' 334 | mu=Makeup() 335 | im,temp_bgr,faces=mu.read_and_mark(path) 336 | imc=im.copy() 337 | cv2.imshow('ori',imc) 338 | for face in faces[path]: 339 | face.whitening() 340 | face.smooth(0.7) 341 | face.organs['forehead'].whitening() 342 | face.organs['forehead'].smooth(0.7) 343 | face.organs['mouth'].brightening() 344 | face.organs['mouth'].smooth(0.7) 345 | face.organs['mouth'].whitening() 346 | face.organs['left eye'].whitening() 347 | face.organs['right eye'].whitening() 348 | face.organs['left eye'].sharpen() 349 | face.organs['right eye'].sharpen() 350 | face.organs['left eye'].smooth() 351 | face.organs['right eye'].smooth() 352 | face.organs['left brow'].whitening() 353 | face.organs['right brow'].whitening() 354 | face.organs['left brow'].sharpen() 355 | face.organs['right brow'].sharpen() 356 | face.organs['nose'].whitening() 357 | face.organs['nose'].smooth(0.7) 358 | face.organs['nose'].sharpen() 359 | face.sharpen() 360 | cv2.imshow('new',im.copy()) 361 | cv2.waitKey() 362 | print('Quiting') 363 | 364 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MakupGUI.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 837 10 | 838 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 0 23 | 0 24 | 25 | 26 | 27 | true 28 | 29 | 30 | 31 | 32 | 0 33 | 0 34 | 813 35 | 532 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 美白 48 | 49 | 50 | 51 | 52 | 53 | 54 | Qt::Horizontal 55 | 56 | 57 | 58 | 59 | 60 | 61 | Qt::Horizontal 62 | 63 | 64 | 65 | 40 66 | 20 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 磨皮 75 | 76 | 77 | 78 | 79 | 80 | 81 | Qt::Horizontal 82 | 83 | 84 | 85 | 86 | 87 | 88 | Qt::Horizontal 89 | 90 | 91 | 92 | 40 93 | 20 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 亮眼 102 | 103 | 104 | 105 | 106 | 107 | 108 | Qt::Horizontal 109 | 110 | 111 | 112 | 113 | 114 | 115 | Qt::Horizontal 116 | 117 | 118 | 119 | 40 120 | 20 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 红唇 129 | 130 | 131 | 132 | 133 | 134 | 135 | Qt::Horizontal 136 | 137 | 138 | 139 | 140 | 141 | 142 | Qt::Horizontal 143 | 144 | 145 | 146 | 40 147 | 20 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 打开文件 156 | 157 | 158 | 159 | 160 | 161 | 162 | 确认更改 163 | 164 | 165 | 166 | 167 | 168 | 169 | 撤销更改 170 | 171 | 172 | 173 | 174 | 175 | 176 | 还原 177 | 178 | 179 | 180 | 181 | 182 | 183 | 查看对比 184 | 185 | 186 | 187 | 188 | 189 | 190 | 保存 191 | 192 | 193 | 194 | 195 | 196 | 197 | 保存对比图 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AIMakeup 2 | AI make up based on face-detection 3 | # Example 4 | ![example](./example.gif) 5 | -------------------------------------------------------------------------------- /Ui_MakupGUI.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'C:\pyprojects\AIMakeup\MakupGUI.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.9 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | import sys,os 10 | import numpy as np 11 | import cv2 12 | from PyQt5 import QtCore, QtWidgets 13 | from PyQt5.QtGui import QImage,QIcon,QPixmap 14 | from PyQt5.QtWidgets import QFileDialog,QMessageBox 15 | from AIMakeup import Makeup,Face,Organ,NoFace 16 | 17 | class Ui_MainWindow(object): 18 | def __init__(self, MainWindow): 19 | self.window=MainWindow 20 | self._setupUi() 21 | #控件分组 22 | self.bg_edit=[self.bt_brightening,self.bt_whitening,self.bt_sharpen,self.bt_smooth] 23 | self.bg_op=[self.bt_confirm,self.bt_cancel,self.bt_reset] 24 | self.bg_result=[self.bt_view_compare,self.bt_save,self.bt_save_compare] 25 | self.sls=[self.sl_brightening,self.sl_sharpen,self.sl_whitening,self.sl_smooth] 26 | #用于显示图片的标签 27 | self.label=QtWidgets.QLabel(self.window) 28 | self.sa.setWidget(self.label) 29 | #批量设置状态 30 | self._set_statu(self.bg_edit,False) 31 | self._set_statu(self.bg_op,False) 32 | self._set_statu(self.bg_result,False) 33 | self._set_statu(self.sls,False) 34 | #导入dlib模型文件 35 | if os.path.exists("./data/shape_predictor_68_face_landmarks.dat"): 36 | self.path_predictor=os.path.abspath("./data/shape_predictor_68_face_landmarks.dat") 37 | else: 38 | QMessageBox.warning(self.centralWidget,'警告','默认的dlib模型文件路径不存在,请指定文件位置。\ 39 | \n或从http://dlib.net/files/shape_predictor_68_face_landmarks.dat.bz2下载') 40 | self.path_predictor,_=QFileDialog.getOpenFileName(self.centralWidget,'选择dlib模型文件','./','Data Files(*.dat)') 41 | #实例化化妆器 42 | self.mu=Makeup(self.path_predictor) 43 | 44 | self.path_img='' 45 | self._set_connect() 46 | def _set_connect(self): 47 | ''' 48 | 设置程序逻辑 49 | ''' 50 | self.bt_open.clicked.connect(self._open_img) 51 | for op in ['sharpen','whitening','smooth','brightening','cancel','confirm','reset','save','save_compare','view_compare']: 52 | self.__getattribute__('bt_'+op).clicked.connect(self.__getattribute__('_'+op)) 53 | 54 | def _open_img(self): 55 | ''' 56 | 打开图片 57 | ''' 58 | self.path_img,_=QFileDialog.getOpenFileName(self.centralWidget,'打开图片文件','./','Image Files(*.png *.jpg *.bmp)') 59 | if self.path_img and os.path.exists(self.path_img): 60 | print(self.path_img) 61 | self.im_bgr,self.temp_bgr,self.faces=self.mu.read_and_mark(self.path_img) 62 | self.im_ori,self.previous_bgr=self.im_bgr.copy(),self.im_bgr.copy() 63 | self._set_statu(self.bg_edit,True) 64 | self._set_statu(self.bg_op,True) 65 | self._set_statu(self.bg_result,True) 66 | self._set_statu(self.sls,True) 67 | self._set_img() 68 | else: 69 | QMessageBox.warning(self.centralWidget,'无效路径','无效路径,请重新选择!') 70 | 71 | def _cv2qimg(self,cvImg): 72 | ''' 73 | 将opencv的图片转换为QImage 74 | ''' 75 | height, width, channel = cvImg.shape 76 | bytesPerLine = 3 * width 77 | return QImage(cv2.cvtColor(cvImg,cv2.COLOR_BGR2RGB).data, width, height, bytesPerLine, QImage.Format_RGB888) 78 | 79 | def _set_img(self): 80 | ''' 81 | 显示pixmap 82 | ''' 83 | self.label.setPixmap(QPixmap.fromImage(self._cv2qimg(self.temp_bgr))) 84 | 85 | def _set_statu(self,group,value): 86 | ''' 87 | 批量设置状态 88 | ''' 89 | [item.setEnabled(value) for item in group] 90 | 91 | def _confirm(self): 92 | ''' 93 | 确认操作 94 | ''' 95 | self.im_bgr[:]=self.temp_bgr[:] 96 | 97 | def _cancel(self): 98 | ''' 99 | 还原到上一步 100 | ''' 101 | self.temp_bgr[:]=self.previous_bgr[:] 102 | self._set_img() 103 | 104 | def _reset(self): 105 | ''' 106 | 重置为原始图片 107 | ''' 108 | self.temp_bgr[:]=self.im_ori[:] 109 | self._set_img() 110 | 111 | def _mapfaces(self,fun,value): 112 | ''' 113 | 对每张脸进行迭代操作 114 | ''' 115 | self.previous_bgr[:]=self.temp_bgr[:] 116 | for face in self.faces[self.path_img]: 117 | fun(face,value) 118 | self._set_img() 119 | 120 | def _sharpen(self): 121 | value=min(1,max(self.sl_sharpen.value()/200,0)) 122 | print(value) 123 | def fun(face,value): 124 | face.organs['left eye'].sharpen(value,confirm=False) 125 | face.organs['right eye'].sharpen(value,confirm=False) 126 | self._mapfaces(fun,value) 127 | 128 | def _whitening(self): 129 | value=min(1,max(self.sl_whitening.value()/200,0)) 130 | print(value) 131 | def fun(face,v): 132 | face.organs['left eye'].whitening(value,confirm=False) 133 | face.organs['right eye'].whitening(value,confirm=False) 134 | face.organs['left brow'].whitening(value,confirm=False) 135 | face.organs['right brow'].whitening(value,confirm=False) 136 | face.organs['nose'].whitening(value,confirm=False) 137 | face.organs['forehead'].whitening(value,confirm=False) 138 | face.organs['mouth'].whitening(value,confirm=False) 139 | face.whitening(value,confirm=False) 140 | self._mapfaces(fun,value) 141 | 142 | def _brightening(self): 143 | value=min(1,max(self.sl_brightening.value()/200,0)) 144 | print(value) 145 | def fun(face,value): 146 | face.organs['mouth'].brightening(value,confirm=False) 147 | self._mapfaces(fun,value) 148 | 149 | def _smooth(self): 150 | value=min(1,max(self.sl_smooth.value()/100,0)) 151 | def fun(face,value): 152 | face.smooth(value,confirm=False) 153 | face.organs['left eye'].smooth(value*2/3,confirm=False) 154 | face.organs['right eye'].smooth(value*2/3,confirm=False) 155 | face.organs['left brow'].smooth(value*2/3,confirm=False) 156 | face.organs['right brow'].smooth(value*2/3,confirm=False) 157 | face.organs['nose'].smooth(value*2/3,confirm=False) 158 | face.organs['forehead'].smooth(value*3/2,confirm=False) 159 | face.organs['mouth'].smooth(value,confirm=False) 160 | self._mapfaces(fun,value) 161 | 162 | def _save(self): 163 | output_path,_=QFileDialog.getSaveFileName(self.centralWidget,'选择保存位置','./','Image Files(*.png *.jpg *.bmp)') 164 | if output_path: 165 | self.save(output_path,self.im_bgr) 166 | else: 167 | QMessageBox.warning(self.centralWidget,'无效路径','无效路径,请重新选择!') 168 | 169 | def _save_compare(self): 170 | output_path,_=QFileDialog.getSaveFileName(self.centralWidget,'选择保存位置','./','Image Files(*.png *.jpg *.bmp)') 171 | if output_path: 172 | self.save(output_path,np.concatenate([self.im_ori,self.im_bgr],1)) 173 | else: 174 | QMessageBox.warning(self.centralWidget,'无效路径','无效路径,请重新选择!') 175 | 176 | def _view_compare(self): 177 | cv2.imshow('Compare',np.concatenate([self.im_ori,self.im_bgr],1)) 178 | cv2.waitKey() 179 | 180 | def _setupUi(self): 181 | self.window.setObjectName("MainWindow") 182 | self.window.resize(837, 838) 183 | self.centralWidget = QtWidgets.QWidget(self.window) 184 | self.centralWidget.setObjectName("centralWidget") 185 | self.verticalLayout = QtWidgets.QVBoxLayout(self.centralWidget) 186 | self.verticalLayout.setObjectName("verticalLayout") 187 | self.sa = QtWidgets.QScrollArea(self.centralWidget) 188 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) 189 | sizePolicy.setHorizontalStretch(0) 190 | sizePolicy.setVerticalStretch(0) 191 | sizePolicy.setHeightForWidth(self.sa.sizePolicy().hasHeightForWidth()) 192 | self.sa.setSizePolicy(sizePolicy) 193 | self.sa.setWidgetResizable(True) 194 | self.sa.setObjectName("sa") 195 | self.scrollAreaWidgetContents = QtWidgets.QWidget() 196 | self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 813, 532)) 197 | self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") 198 | self.sa.setWidget(self.scrollAreaWidgetContents) 199 | self.verticalLayout.addWidget(self.sa) 200 | self.gridLayout = QtWidgets.QGridLayout() 201 | self.gridLayout.setObjectName("gridLayout") 202 | self.bt_whitening = QtWidgets.QPushButton(self.centralWidget) 203 | self.bt_whitening.setObjectName("bt_whitening") 204 | self.gridLayout.addWidget(self.bt_whitening, 0, 0, 1, 1) 205 | self.sl_whitening = QtWidgets.QSlider(self.centralWidget) 206 | self.sl_whitening.setOrientation(QtCore.Qt.Horizontal) 207 | self.sl_whitening.setObjectName("sl_whitening") 208 | self.gridLayout.addWidget(self.sl_whitening, 0, 1, 1, 1) 209 | spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 210 | self.gridLayout.addItem(spacerItem, 0, 2, 1, 1) 211 | self.bt_smooth = QtWidgets.QPushButton(self.centralWidget) 212 | self.bt_smooth.setObjectName("bt_smooth") 213 | self.gridLayout.addWidget(self.bt_smooth, 1, 0, 1, 1) 214 | self.sl_smooth = QtWidgets.QSlider(self.centralWidget) 215 | self.sl_smooth.setOrientation(QtCore.Qt.Horizontal) 216 | self.sl_smooth.setObjectName("sl_smooth") 217 | self.gridLayout.addWidget(self.sl_smooth, 1, 1, 1, 1) 218 | spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 219 | self.gridLayout.addItem(spacerItem1, 1, 2, 1, 1) 220 | self.bt_sharpen = QtWidgets.QPushButton(self.centralWidget) 221 | self.bt_sharpen.setObjectName("bt_sharpen") 222 | self.gridLayout.addWidget(self.bt_sharpen, 2, 0, 1, 1) 223 | self.sl_sharpen = QtWidgets.QSlider(self.centralWidget) 224 | self.sl_sharpen.setOrientation(QtCore.Qt.Horizontal) 225 | self.sl_sharpen.setObjectName("sl_sharpen") 226 | self.gridLayout.addWidget(self.sl_sharpen, 2, 1, 1, 1) 227 | spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 228 | self.gridLayout.addItem(spacerItem2, 2, 2, 1, 1) 229 | self.bt_brightening = QtWidgets.QPushButton(self.centralWidget) 230 | self.bt_brightening.setObjectName("bt_brightening") 231 | self.gridLayout.addWidget(self.bt_brightening, 3, 0, 1, 1) 232 | self.sl_brightening = QtWidgets.QSlider(self.centralWidget) 233 | self.sl_brightening.setOrientation(QtCore.Qt.Horizontal) 234 | self.sl_brightening.setObjectName("sl_brightening") 235 | self.gridLayout.addWidget(self.sl_brightening, 3, 1, 1, 1) 236 | spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 237 | self.gridLayout.addItem(spacerItem3, 3, 2, 1, 1) 238 | self.bt_open = QtWidgets.QPushButton(self.centralWidget) 239 | self.bt_open.setObjectName("bt_open") 240 | self.gridLayout.addWidget(self.bt_open, 4, 0, 1, 1) 241 | self.bt_confirm = QtWidgets.QPushButton(self.centralWidget) 242 | self.bt_confirm.setObjectName("bt_confirm") 243 | self.gridLayout.addWidget(self.bt_confirm, 5, 0, 1, 1) 244 | self.bt_cancel = QtWidgets.QPushButton(self.centralWidget) 245 | self.bt_cancel.setObjectName("bt_cancel") 246 | self.gridLayout.addWidget(self.bt_cancel, 5, 1, 1, 1) 247 | self.bt_reset = QtWidgets.QPushButton(self.centralWidget) 248 | self.bt_reset.setObjectName("bt_reset") 249 | self.gridLayout.addWidget(self.bt_reset, 5, 2, 1, 1) 250 | self.bt_view_compare = QtWidgets.QPushButton(self.centralWidget) 251 | self.bt_view_compare.setObjectName("bt_view_compare") 252 | self.gridLayout.addWidget(self.bt_view_compare, 6, 0, 1, 1) 253 | self.bt_save = QtWidgets.QPushButton(self.centralWidget) 254 | self.bt_save.setObjectName("bt_save") 255 | self.gridLayout.addWidget(self.bt_save, 7, 1, 1, 1) 256 | self.bt_save_compare = QtWidgets.QPushButton(self.centralWidget) 257 | self.bt_save_compare.setObjectName("bt_save_compare") 258 | self.gridLayout.addWidget(self.bt_save_compare, 7, 2, 1, 1) 259 | self.verticalLayout.addLayout(self.gridLayout) 260 | self.window.setCentralWidget(self.centralWidget) 261 | 262 | self.retranslateUi() 263 | QtCore.QMetaObject.connectSlotsByName(self.window) 264 | 265 | def save(self,output_path,output_im): 266 | ''' 267 | 保存图片 268 | ''' 269 | cv2.imencode('.jpg',output_im)[1].tofile(output_path) 270 | 271 | def retranslateUi(self): 272 | _translate = QtCore.QCoreApplication.translate 273 | self.window.setWindowTitle(_translate("MainWindow", "AI美颜")) 274 | self.bt_whitening.setText(_translate("MainWindow", "美白")) 275 | self.bt_smooth.setText(_translate("MainWindow", "磨皮")) 276 | self.bt_sharpen.setText(_translate("MainWindow", "亮眼")) 277 | self.bt_brightening.setText(_translate("MainWindow", "红唇")) 278 | self.bt_open.setText(_translate("MainWindow", "打开文件")) 279 | self.bt_confirm.setText(_translate("MainWindow", "确认更改")) 280 | self.bt_cancel.setText(_translate("MainWindow", "撤销更改")) 281 | self.bt_reset.setText(_translate("MainWindow", "还原")) 282 | self.bt_view_compare.setText(_translate("MainWindow", "查看对比")) 283 | self.bt_save.setText(_translate("MainWindow", "保存")) 284 | self.bt_save_compare.setText(_translate("MainWindow", "保存对比图")) 285 | 286 | 287 | if __name__ == "__main__": 288 | app = QtWidgets.QApplication(sys.argv) 289 | MainWindow = QtWidgets.QMainWindow() 290 | ui = Ui_MainWindow(MainWindow) 291 | ui.window.show() 292 | sys.exit(app.exec_()) 293 | 294 | -------------------------------------------------------------------------------- /Ui_MakupGUI.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python -*- 2 | 3 | block_cipher = None 4 | 5 | 6 | a = Analysis(['Ui_MakupGUI.py'], 7 | pathex=['c:\\Anaconda3\\Lib\\site-packages\\PyQt5\\Qt\\bin', 'C:\\pyprojects\\AIMakeup'], 8 | binaries=[], 9 | datas=[], 10 | hiddenimports=[], 11 | hookspath=[], 12 | runtime_hooks=[], 13 | excludes=[], 14 | win_no_prefer_redirects=False, 15 | win_private_assemblies=False, 16 | cipher=block_cipher) 17 | pyz = PYZ(a.pure, a.zipped_data, 18 | cipher=block_cipher) 19 | exe = EXE(pyz, 20 | a.scripts, 21 | a.binaries, 22 | a.zipfiles, 23 | a.datas, 24 | name='Ui_MakupGUI', 25 | debug=False, 26 | strip=False, 27 | upx=True, 28 | console=False , icon='makeup.ico') 29 | -------------------------------------------------------------------------------- /example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantumLiu/AIMakeup/cd7f2bbe812191f1ca86adab3370476ee570597f/example.gif -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | pyinstaller -F -w -i makeup.ico -p c:\Anaconda3\Lib\site-packages\PyQt5\Qt\bin Ui_MakupGUI.py -y -------------------------------------------------------------------------------- /raw/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QuantumLiu/AIMakeup/cd7f2bbe812191f1ca86adab3370476ee570597f/raw/10.jpg --------------------------------------------------------------------------------