Advertisement
Guest User

Untitled

a guest
Apr 12th, 2018
55
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.19 KB | None | 0 0
  1. # -*- coding: utf-8 -*-
  2.  
  3. from __future__ import print_function
  4.  
  5. import argparse
  6. import cv2
  7. import numpy as np
  8. import os
  9. import shutil
  10. import sys
  11.  
  12. from .__version__ import __version__
  13.  
  14. FIXEXP = True # Flag to fix underexposition
  15. MINFACE = 8 # Minimum face size ratio; too low and we get false positives
  16. INCREMENT = 0.06
  17. GAMMA_THRES = 0.001
  18. GAMMA = 0.90
  19. FACE_RATIO = 6 # Face / padding ratio
  20. QUESTION_OVERWRITE = "Overwrite image files?"
  21. FILETYPES = ['.jpg', '.jpeg', '.bmp', '.dib', '.jp2',
  22. '.png', '.webp', '.pbm', '.pgm', '.ppm',
  23. '.sr', '.ras', '.tiff', '.tif']
  24. INPUT_FILETYPES = FILETYPES + [s.upper() for s in FILETYPES]
  25.  
  26. # Load XML Resource
  27. cascFile = 'haarcascade_frontalface_default.xml'
  28. d = os.path.dirname(sys.modules['autocrop'].__file__)
  29. cascPath = os.path.join(d, cascFile)
  30.  
  31.  
  32. # Define simple gamma correction fn
  33. def gamma(img, correction):
  34. img = cv2.pow(img/255.0, correction)
  35. return np.uint8(img*255)
  36.  
  37.  
  38. def crop(image, fwidth=500, fheight=500, fsize=None):
  39. """Given a ndarray image with a face, returns cropped array.
  40.  
  41. Arguments:
  42. - image, the numpy array of the image to be processed.
  43. - fwidth, the final width (px) of the cropped img. Default: 500
  44. - fheight, the final height (px) of the cropped img. Default: 500
  45. Returns:
  46. - image, a cropped numpy array
  47.  
  48. ndarray, int, int -> ndarray
  49. """
  50. # Some grayscale color profiles can throw errors, catch them
  51. try:
  52. gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  53. except cv2.error:
  54. gray = image
  55.  
  56. # Set fwidth and fheight if an fsize flag is set
  57. if fsize is not None:
  58. fwidth = fsize
  59. fheight = fsize
  60.  
  61. # Scale the image
  62. height, width = (image.shape[:2])
  63. minface = int(np.sqrt(height**2 + width**2) / MINFACE)
  64.  
  65. # Create the haar cascade
  66. faceCascade = cv2.CascadeClassifier(cascPath)
  67.  
  68. # ====== Detect faces in the image ======
  69. faces = faceCascade.detectMultiScale(
  70. gray,
  71. scaleFactor=1.1,
  72. minNeighbors=5,
  73. minSize=(minface, minface),
  74. flags=cv2.CASCADE_FIND_BIGGEST_OBJECT | cv2.CASCADE_DO_ROUGH_SEARCH,
  75. )
  76.  
  77. # Handle no faces
  78. if len(faces) == 0:
  79. return None
  80.  
  81. # Make padding from biggest face found
  82. x, y, w, h = faces[-1]
  83. pad = h / FACE_RATIO
  84.  
  85. # Make sure padding is contained within picture
  86. # decreases pad by 6% increments to fit crop into image.
  87. # Can lead to very small faces.
  88. while True:
  89. if (y-2*pad < 0 or y+h+pad > height or
  90. int(x-1.5*pad) < 0 or x+w+int(1.5*pad) > width):
  91. pad = (1 - INCREMENT) * pad
  92. else:
  93. break
  94.  
  95. # Crop the image from the original
  96. y1 = int(y - round(pad) - (fheight/8))
  97. if y1 < 0:
  98. y1 = 0
  99.  
  100. y2 = int(y + h + round(pad))
  101. if y1 == 0:
  102. y2 = fheight
  103.  
  104. x1 = int(x - round(pad) - (fwidth/12))
  105. if x1 < 0:
  106. x1 = 0
  107.  
  108. x2 = int(x + w + round(pad))
  109. if x1 == 0:
  110. x2 = fwidth
  111.  
  112. print("x1: {} | x2: {} \ny1: {} | y2: {}".format(x1,x2,y1,y2))
  113.  
  114. while fwidth > x2 - x1:
  115. step = 0
  116. if step == 0:
  117. x2 = x2 + 1
  118. step = 1
  119. elif step == 1:
  120. x1 = x1 - 1
  121. step = 0
  122.  
  123. while fheight > y2 - y1:
  124. step = 0
  125. if step == 0:
  126. y2 = y2 + 1
  127. step = 1
  128. elif step == 1:
  129. y1 = y1 - 1
  130. step = 0
  131.  
  132. while x2 - x1 > fwidth:
  133. step = 0
  134. if step == 0:
  135. x2 = x2 - 1
  136. step = 1
  137. elif step == 1:
  138. x1 = x1 - 1
  139. step = 0
  140.  
  141. while y2 - y1 > fheight:
  142. step = 0
  143. if step == 0:
  144. y2 = y2 - 1
  145. step = 1
  146. elif step == 1:
  147. y1 = y1 - 1
  148. step = 0
  149.  
  150. while x2 > width:
  151. x2 = x2 - 1
  152. x1 = x1 - 1
  153. if x2 == width:
  154. x2 = (x2 - round((x1 / 2)))
  155. x1 = x1 - round((x1 / 2))
  156.  
  157. while y2 > height:
  158. y2 = y2 - 1
  159. y1 = y1 - 1
  160.  
  161.  
  162. print("w: {} | h: {}".format(width, height))
  163. print("x: {} y: {} w: {} h: {}".format(x,y,w,h))
  164. print("changed_x1: {} | changed_x2: {} \nchanged_y1: {} | changed_y2: {}\npad: {}".format(x1,x2,y1,y2,pad))
  165. image = image[y1:y2, x1:x2]
  166.  
  167. # Resize the damn thing
  168. # image = cv2.resize(image, (fwidth, fheight), interpolation=cv2.INTER_AREA)
  169.  
  170. # ====== Dealing with underexposition ======
  171. if FIXEXP:
  172. # Check if under-exposed
  173. uexp = cv2.calcHist([gray], [0], None, [256], [0, 256])
  174. if sum(uexp[-26:]) < GAMMA_THRES * sum(uexp):
  175. image = gamma(image, GAMMA)
  176. return image
  177.  
  178.  
  179. def main(input_d, output_d, fheight=500, fwidth=500, fsize=None):
  180. """Crops folder of images to the desired height and width if a face is found
  181.  
  182. If input_d == output_d or output_d is None, overwrites all files
  183. where the biggest face was found.
  184.  
  185. Args:
  186. input_d (str): Directory to crop images from.
  187. output_d (str): Directory where cropped images are placed.
  188. fheight (int): Height (px) to which to crop the image.
  189. Default: 500px
  190. fwidth (int): Width (px) to which to crop the image.
  191. Default: 500px
  192.  
  193. Side Effects:
  194. Creates image files in output directory.
  195.  
  196. str, str, (int), (int) -> None
  197. """
  198. errors = 0
  199. files = [os.path.join(input_d, f) for f in os.listdir(input_d)
  200. if any(f.endswith(t) for t in INPUT_FILETYPES)]
  201.  
  202. # Set fwidth and fheight if an fsize flag is set
  203. if fsize is not None:
  204. fwidth = fsize
  205. fheight = fsize
  206.  
  207. # Guard against calling the function directly
  208. assert len(files) > 0
  209.  
  210. if output_d is not None:
  211. filenames = [os.path.basename(f) for f in files]
  212. target_files = [os.path.join(output_d, fn) for fn in filenames]
  213. for i, o in zip(files, target_files):
  214. shutil.copyfile(i, o)
  215. files = target_files
  216. else:
  217. output_d = input_d
  218.  
  219. for f in files:
  220. filename = os.path.basename(f)
  221.  
  222. # Perform the actual crop
  223. input_img = cv2.imread(f)
  224. image = crop(input_img, fwidth, fheight, fsize)
  225.  
  226. # Make sure there actually was a face in there
  227. if isinstance(image, type(None)):
  228. print('No faces can be detected in {}.'.format(filename))
  229. errors += 1
  230. continue
  231.  
  232. # Write cropfile
  233. output_filename = os.path.join(output_d, filename)
  234. cv2.imwrite(output_filename, image)
  235.  
  236. # Stop and print status
  237. print(' {} files have been cropped'.format(len(files) - errors))
  238.  
  239.  
  240. def input_path(p):
  241. """Returns absolute path, only if input is a valid directory"""
  242. no_folder = 'Input folder does not exist'
  243. no_images = 'Input folder does not contain any image files'
  244. p = os.path.abspath(p)
  245. if not os.path.isdir(p):
  246. raise argparse.ArgumentTypeError(no_folder)
  247. filetypes = set(os.path.splitext(f)[-1] for f in os.listdir(p))
  248. if not any(t in INPUT_FILETYPES for t in filetypes):
  249. raise argparse.ArgumentTypeError(no_images)
  250. else:
  251. return p
  252.  
  253.  
  254. def output_path(p):
  255. """Returns absolute path, if input is a valid directory name.
  256. If directory doesn't exist, creates it."""
  257. p = os.path.abspath(p)
  258. if not os.path.isdir(p):
  259. os.makedirs(p)
  260. return p
  261.  
  262.  
  263. def size(i):
  264. """Returns valid only if input is a positive integer under 1e5"""
  265. error = 'Invalid pixel size'
  266. try:
  267. i = int(i)
  268. except TypeError:
  269. raise argparse.ArgumentTypeError(error)
  270. if i > 0 and i < 1e5:
  271. return i
  272. else:
  273. raise argparse.ArgumentTypeError(error)
  274.  
  275.  
  276. def compat_input(s=''):
  277. """Compatibility function to permit testing for Python 2 and 3"""
  278. try:
  279. return raw_input(s)
  280. except NameError:
  281. return input(s)
  282.  
  283.  
  284. def confirmation(question, default=True):
  285. """Ask a yes/no question via standard input and return the answer.
  286.  
  287. If invalid input is given, the user will be asked until
  288. they acutally give valid input.
  289.  
  290. Args:
  291. question(str):
  292. A question that is presented to the user.
  293. default(bool|None):
  294. The default value when enter is pressed with no value.
  295. When None, there is no default value and the query
  296. will loop.
  297. Returns:
  298. A bool indicating whether user has entered yes or no.
  299.  
  300. Side Effects:
  301. Blocks program execution until valid input(y/n) is given.
  302. """
  303. yes_list = ["yes", "y"]
  304. no_list = ["no", "n"]
  305.  
  306. default_dict = { # default => prompt default string
  307. None: "[y/n]",
  308. True: "[Y]/n",
  309. False: "y/[N]",
  310. }
  311.  
  312. default_str = default_dict[default]
  313. prompt_str = "%s %s " % (question, default_str)
  314.  
  315. while True:
  316. choice = compat_input(prompt_str).lower()
  317.  
  318. if not choice and default is not None:
  319. return default
  320. if choice in yes_list:
  321. return True
  322. if choice in no_list:
  323. return False
  324.  
  325. notification_str = "Please respond with 'y' or 'n'"
  326. print(notification_str)
  327.  
  328.  
  329. def parse_args(args):
  330. help_d = {
  331. 'desc': 'Automatically crops faces from batches of pictures',
  332. 'input': '''Folder where images to crop are located.
  333. Default: current working directory''',
  334. 'output': '''Folder where cropped images will be placed.
  335. Default: current working directory''',
  336. 'width': 'Width of cropped files in px. Default=500',
  337. 'height': 'Height of cropped files in px. Default=500',
  338. 'size': '''Sets the width and height in px.
  339. Overrides width and height arguments if set.''',
  340. 'y': 'Bypass any confirmation prompts',
  341. }
  342.  
  343. parser = argparse.ArgumentParser(description=help_d['desc'])
  344. parser.add_argument('-o', '--output', '-p', '--path', type=output_path,
  345. default=None, help=help_d['output'])
  346. parser.add_argument('-i', '--input', default='.', type=input_path,
  347. help=help_d['input'])
  348. parser.add_argument('-w', '--width', type=size,
  349. default=500, help=help_d['width'])
  350. parser.add_argument('-H', '--height',
  351. type=size, default=500, help=help_d['height'])
  352. parser.add_argument('-s', '--size',
  353. type=size, default=None, help=help_d['size'])
  354. parser.add_argument('-v', '--version', action='version',
  355. version='%(prog)s version {}'.format(__version__))
  356. parser.add_argument('--no-confirm', action='store_true', help=help_d['y'])
  357. return parser.parse_args()
  358.  
  359.  
  360. def cli():
  361. args = parse_args(sys.argv[1:])
  362. if not args.no_confirm:
  363. if args.output is None or args.input == args.output:
  364. if not confirmation(QUESTION_OVERWRITE):
  365. sys.exit()
  366. if args.input == args.output:
  367. args.output = None
  368. print('Processing images in folder:', args.input)
  369. main(args.input, args.output, args.height, args.width, args.size)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement