Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import os
- os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'
- ''' import libraries '''
- import ssl
- import face_alignment
- import numpy as np
- import matplotlib.image as mpimg
- from skimage import io
- from sklearn.cluster import OPTICS
- ssl._create_default_https_context = ssl._create_unverified_context
- class PhotoModeration:
- def __init__(
- self,
- image_path: str,
- ) -> None:
- self.image_path = image_path
- self.face_model = face_alignment.FaceAlignment(face_alignment.LandmarksType.TWO_D, flip_input=False, device='cpu')
- self.image = io.imread(image_path, pilmode='RGB')
- ''' compute width and height of the uploaded photo '''
- self.W = self.image.shape[1]
- self.H = self.image.shape[0]
- self.error = None
- # self.final_image = None
- self.num_landmarks = None
- self.real_width_ratio = None
- self.real_height_ratio = None
- self.X_face_center = None
- self.Y_face_center = None
- self.X_img_center = int(self.image.shape[1]/2)
- self.Y_img_center = int(self.image.shape[0]/2)
- self.const_face_width_ratio = 0.2
- def find_non_crossed_intervals(self, landmarks):
- ''' find uncrossed landmarks by x-axis '''
- ''' compute distence between center of image and center of 1 landmark '''
- x_arr0 = landmarks[0][:,0]
- y_arr0 = landmarks[0][:,1]
- x_center_arr0 = np.mean(x_arr0)
- y_center_arr0 = np.mean(y_arr0)
- ''' bound of unit width of crossed min max face width '''
- min_x0, max_x0 = min(x_arr0), max(x_arr0)
- ind_2_and_more_faces = 0
- dist0 = np.sqrt( (x_center_arr0 - self.X_img_center)**2 + (y_center_arr0 - self.Y_img_center)**2 )
- ''' id of landmark which is the nearest to photo center '''
- target_nearest_landmark_ind = 0
- ''' compute distence between center of image and center of other landmarks '''
- for i in range(1, len(landmarks)):
- x_arr1 = landmarks[i][:,0]
- y_arr1 = landmarks[i][:,1]
- x_center_arr1 = np.mean(x_arr1)
- y_center_arr1 = np.mean(y_arr1)
- dist1 = np.sqrt( (x_center_arr1 - self.X_img_center)**2 + (y_center_arr1 - self.Y_img_center)**2 )
- if dist1 < dist0:
- target_nearest_landmark_ind = i
- dist0 = dist1
- min_x1, max_x1 = min(x_arr1), max(x_arr1)
- ''' if width of min max faces is crossed along x-axis recompute min max of unit width '''
- if min_x0 < min_x1 and min_x1 < max_x0 and max_x1 > max_x0:
- min_x0, max_x0 = min_x0, max_x1
- elif min_x0 < min_x1 and min_x1 < max_x0 and max_x1 < max_x0:
- min_x0, max_x0 = min_x0, max_x0
- elif min_x1 < min_x0 and min_x0 < max_x1 and max_x0 > max_x1:
- min_x0, max_x0 = min_x1, max_x0
- elif min_x1 < min_x0 and min_x0 < max_x1 and max_x0 < max_x1:
- min_x0, max_x0 = min_x1, max_x1
- else:
- ''' if width of min max faces is uncrossed along x-axis make ind_2_and_more_faces + 1 '''
- ind_2_and_more_faces += 1
- ''' return ind_2_and_more_faces and id ladmark which is the nearest to the center '''
- return ind_2_and_more_faces, target_nearest_landmark_ind
- def if_landmark_only_one(self, landmarks):
- ''' compute X_face_center and Y_face_center over found landmark '''
- x_array = landmarks[:,0]
- y_array = landmarks[:,1]
- self.X_face_center = np.mean(x_array)
- self.Y_face_center = np.mean(y_array)
- # ''' compute width and height of the uploaded photo '''
- # W = self.image.shape[1]
- # H = self.image.shape[0]
- ''' compute the ratio of X_face_center to W '''
- x_center_to_w = self.X_face_center/self.W
- ''' compute the ratio of Y_face_center to H '''
- y_center_to_H = 1 - self.Y_face_center/self.H
- ''' set ellipse parameters, ellipse must include face center '''
- ''' parameters were selected empirically based on 7000 photos '''
- u = 0.4984#np.mean(x_center_to_w) #x-position of the center
- v = 0.5783#np.mean([1-i for i in y_center_to_H]) #y-position of the center
- a =.15#radius on the x-axis
- b =.23#radius on the y-axis
- ''' if center of face is outside the min max values of ellipse return error 2 - The detected face is not centered'''
- if x_center_to_w <= u - a or x_center_to_w >= u + a or y_center_to_H <= v - b or y_center_to_H >= v + b:
- self.error = 'The detected face is not centered'
- else:
- y0 = v + np.sqrt( (1 - (x_center_to_w - u)**2/a**2)*b**2 )
- y1 = v - np.sqrt( (1 - (x_center_to_w - u)**2/a**2)*b**2 )
- x0 = u + np.sqrt( (1 - (y_center_to_H - v)**2/b**2)*a**2 )
- x1 = u - np.sqrt( (1 - (y_center_to_H - v)**2/b**2)*a**2 )
- ''' if center of landmark is located outside ellipse return error 2 - The detected face is not centered '''
- if y_center_to_H < y1 or y_center_to_H > y0 or x_center_to_w < x1 or x_center_to_w > x0:
- self.error = 'The detected face is not centered'
- ''' if center of landmark is located inside ellipse '''
- else:
- ''' compute the ratio landmark width to photo width '''
- self.real_width_ratio = abs(max(x_array) - min(x_array))/self.W
- ''' compute the ratio landmark height to photo height '''
- self.real_height_ratio = abs(max(y_array) - min(y_array))/self.H
- ''' if real_width_ratio < const_face_width_ratio and real_height_ratio <= real_width_ratio error 4 - The detected face is too small on the photo '''
- if self.real_width_ratio < self.const_face_width_ratio:
- self.error = 'The detected face is too small on the photo'
- elif self.real_width_ratio > 0.6:
- if self.real_height_ratio <= self.real_width_ratio and self.real_height_ratio > 0.56*self.real_width_ratio:
- ''' if real_width_ratio > 0.6 and real_height_ratio <= real_width_ratio an real_height_ratio > 0.56*real_width_ratio error 5 - The face is too large on the photo, the photo size is standard '''
- self.error = 'The face is too large on the photo, the photo size is standard'
- ''' if real_width_ratio > 0.6 and real_height_ratio <= 0.56*real_width_ratio error 6 - The face is too large on the photo, the photo is elongated vertically '''
- elif self.real_height_ratio <= 0.56*self.real_width_ratio:
- self.error = 'The face is too large on the photo, the photo is elongated vertically'
- elif self.real_width_ratio >= self.const_face_width_ratio and self.real_width_ratio <= 0.6:
- ''' if real_width_ratio >= const_face_width_ratio and real_width_ratio <= 0.6 and real_height_ratio < 0.56*real_width_ratio error 7 - The face is standard size on the photo, the photo is elongated vertically '''
- if self.real_height_ratio < 0.56*self.real_width_ratio:
- self.error = 'The face is standard size on the photo, the photo is elongated vertically'
- ''' if real_width_ratio >= const_face_width_ratio and real_width_ratio <= 0.6 and real_height_ratio >= 0.56*real_width_ratio
- photo size is standard and face size is standard '''
- elif self.real_height_ratio >= 0.56*self.real_width_ratio:
- ''' search dark glasses '''
- ''' select landmark dots which are located upper face center along y axis '''
- indexes_of_up_landmarks = [i for i, x in enumerate(y_array) if x < self.Y_face_center]
- x_up_landmarks = landmarks[:,0][indexes_of_up_landmarks]
- y_up_landmarks = landmarks[:,1][indexes_of_up_landmarks]
- landmarks_eye = np.vstack([x_up_landmarks, y_up_landmarks]).T
- ''' split up landmark dots into 2 clusters, main idea: 1 cluster - 1 eye '''
- clustering = OPTICS(min_samples = 5).fit(landmarks_eye)
- ''' dark ratio around each eye '''
- cluster_domain_dark_ratio = []
- min_x, max_x = int(min(x_array)), int(max(x_array))
- face_scale_x = np.abs(max_x - min_x)
- for cluster in range(0, len(set(clustering.labels_))):
- ind_of_labels = [i for i, x in enumerate(clustering.labels_) if x == cluster]
- if len(ind_of_labels) > 0:
- sub_x = landmarks_eye[:,0][ind_of_labels]
- sub_y = landmarks_eye[:,1][ind_of_labels]
- k = 0.2
- h = 0.15
- min_sub_x = int(np.mean(sub_x) - face_scale_x*k)
- max_sub_x = int(np.mean(sub_x) + face_scale_x*k)
- min_sub_y = int(np.mean(sub_y) - face_scale_x*h)
- max_sub_y = int(np.mean(sub_y) + face_scale_x*h)
- normalized_img = self.image/self.image.max()
- cropped_image_glass = normalized_img[min_sub_y:max_sub_y, min_sub_x:max_sub_x]
- color_bound = 0.3
- cropped_image_glass[cropped_image_glass[:,:,0] > color_bound] = 1
- cropped_image_glass[cropped_image_glass[:,:,1] > color_bound] = 1
- cropped_image_glass[cropped_image_glass[:,:,2] > color_bound] = 1
- #compute_dark_ratio
- unique_x, counts_x = np.unique(cropped_image_glass[:,:,0], return_counts = True)
- unique_y, counts_y = np.unique(cropped_image_glass[:,:,1], return_counts = True)
- unique_z, counts_z = np.unique(cropped_image_glass[:,:,2], return_counts = True)
- if 1 in unique_x or 1 in unique_y or 1 in unique_z:
- x_count = dict(zip(unique_x, counts_x))[1]
- y_count = dict(zip(unique_y, counts_y))[1]
- z_count = dict(zip(unique_z, counts_z))[1]
- dark_ratio = (1 - (x_count + y_count + z_count)/(3*cropped_image_glass.shape[0]*cropped_image_glass.shape[1]))
- cluster_domain_dark_ratio.append(dark_ratio)
- ''' min dark ratio >= 0.42 error 8 - The area around the eyes is dark or you have dark glasses '''
- if min(cluster_domain_dark_ratio) >= 0.42:
- self.error = 'The area around the eyes is dark or you have dark glasses'
- ''' min dark ratio < 0.42 the area around the eyes is not dark or you don't have dark glasses'''
- # else:
- # const_x = 0.44
- # const_y = 0.32
- # delta_x = (max(x_array) - min(x_array))/const_x/2
- # delta_y = (max(y_array) - min(y_array))/const_y/2
- # left_bound_x = int(self.X_face_center - delta_x)
- # if left_bound_x < 0:
- # left_bound_x = 0
- # up_bound_y = int(self.Y_face_center - delta_y)
- # if up_bound_y < 0:
- # up_bound_y = 0
- # ''' crop photo 3 to 4 ratio around landmark '''
- # cropped_image = self.image[up_bound_y:int(self.Y_face_center + delta_y), left_bound_x:int(self.X_face_center + delta_x), :]#.astype(int)
- # ''' if width of cropped photo < 200 px error 9 - Low quality of photo (cropprd 3 to 4 face landmark) '''
- # if cropped_image.shape[0] < 200:
- # self.error = 'Low quality of photo (cropprd 3 to 4 face landmark)'
- # else:
- # ''' if width of cropped photo > 200 px return cropped final image '''
- # self.image = mpimg.imread(self.image_path)
- # self.final_image = self.image[up_bound_y:int(self.Y_face_center + delta_y), left_bound_x:int(self.X_face_center + delta_x), :]#.astype(int)
- return self.error#, self.final_image
- def moderation_photo(self):
- ''' detect file format '''
- if str(self.image_path.split('.')[-1]).lower() not in ['png', 'jpg', 'jpeg', 'heic', 'jfif']:
- self.error = 'File format error. The format must be jpg, jpeg, png or heic'
- ''' if the photo is oriented horizontally return error 3 - The height of the photos is less than the width '''
- elif self.W > self.H:
- self.error = 'The height of the photos is less than the width'
- else:
- ''' if width or height of photo < 200 px error 9 - One of photo side < 200 px '''
- if self.image.shape[0] < 200 or self.image.shape[1] < 200:
- self.error = 'One of photo side < 200 px'
- else:
- '''check if min(width, hight) > 3000 px'''
- min_side_size = sorted([i for i in self.image.shape])[1]
- if min_side_size > 3000:
- width = self.image.shape[0]
- hight = self.image.shape[1]
- cut_y = 0.2
- cut_x = 0.2
- ''' cut out 80% of width and hight '''
- image_cropped = self.image[int((cut_y-0.1)*width):-int((cut_y+0.1)*width), int(cut_x*hight):-int(cut_x*hight)]
- ''' search landmarks by using face_alignment '''
- landmarks = self.face_model.get_landmarks(image_cropped)
- ''' if len(landmarks) > 0 transform searched landmarks over initial sizes of photo with higher resolution '''
- if landmarks != None:
- for i in range(0, len(landmarks)):
- landmarks[i][:,0] = landmarks[i][:,0] + int(cut_x*hight)
- landmarks[i][:,1] = landmarks[i][:,1] + int((cut_y-0.1)*width)
- ''' if min(width, hight) >= 3000 px'''
- else:
- ''' search landmarks by using face_alignment '''
- landmarks = self.face_model.get_landmarks(self.image)
- ''' if there is no landmarks, return error 0 - No faces were found on the photo '''
- if landmarks == None:
- self.error = 'No faces were found on the photo'
- ''' if there is 1 landmark only run if_landmark_only_one() '''
- elif len(landmarks) == 1:
- # self.error, self.final_image = self.if_landmark_only_one(landmarks[0])
- self.error = self.if_landmark_only_one(landmarks[0])
- ''' if there is more than 1 landmark '''
- elif len(landmarks) > 1:
- ''' remove landmarks which are 10 times smaller in width than the widest landmark '''
- times_smaller = 10
- widths_of_landmarks = [max(landmarks[r][:,0]) - min(landmarks[r][:,0]) for r in range(0, len(landmarks))]
- max_width_landmarks = max(widths_of_landmarks)
- bound = max_width_landmarks/times_smaller
- index_of_big_landmarks = [(t) for t, p in enumerate(widths_of_landmarks) if p > bound]
- landmarks = [landmarks[s] for s in index_of_big_landmarks]
- ''' check if w/h in [a,b] where w - face width, h - face hight. w and h are computed based on found landmarks '''
- a = 0.86
- b = 1.25
- good_landmarks_indexes = []
- for ind, ld in enumerate(landmarks):
- w = abs(max(ld[:,0]) - min(ld[:,0]))
- h = abs(max(ld[:,1]) - min(ld[:,1]))
- if w/h >= a and w/h <= b:
- good_landmarks_indexes.append(ind)
- ''' if number of landmarks in [a,b] = 0 return error 0 - No faces were found on the photo '''
- if len(good_landmarks_indexes) == 0:
- self.error = 'No faces were found on the photo'
- ''' if number of landmarks in [a,b] = 1 run if_landmark_only_one() '''
- elif len(good_landmarks_indexes) == 1:
- # self.error, self.final_image = self.if_landmark_only_one(landmarks[ind])
- self.error = self.if_landmark_only_one(landmarks[ind])
- ''' if number of landmarks in [a,b] > 1 '''
- elif len(good_landmarks_indexes) > 1:
- ''' choose landmarks in [a,b] '''
- landmarks = [landmarks[ind] for ind in good_landmarks_indexes]
- ''' run find_non_crossed_intervals() '''
- indicator_2_and_more_faces, target_average_landmark_ind = self.find_non_crossed_intervals(landmarks)
- ''' if there is any uncrossed interval return error 1 - Several people were found on the photo '''
- if indicator_2_and_more_faces >= 1:
- self.num_landmarks = len(landmarks)
- self.error = 'Several people were found on the photo'
- ''' if there is all crossed interval run if_landmark_only_one() '''
- elif indicator_2_and_more_faces == 0:
- # self.error, self.final_image = self.if_landmark_only_one(landmarks[target_average_landmark_ind])
- self.error = self.if_landmark_only_one(landmarks[target_average_landmark_ind])
- return self.error#, self.final_image
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement