1
1
import cv2
2
+ import os
2
3
from collections import defaultdict
3
4
from math import log , sqrt
4
5
import numpy as np
@@ -26,19 +27,9 @@ def crop_image(im, settings):
26
27
scale_by = settings .crop_height / im .height
27
28
28
29
im = im .resize ((int (im .width * scale_by ), int (im .height * scale_by )))
30
+ im_debug = im .copy ()
29
31
30
- if im .width == settings .crop_width and im .height == settings .crop_height :
31
- if settings .annotate_image :
32
- d = ImageDraw .Draw (im )
33
- rect = [0 , 0 , im .width , im .height ]
34
- rect [2 ] -= 1
35
- rect [3 ] -= 1
36
- d .rectangle (rect , outline = GREEN )
37
- if settings .destop_view_image :
38
- im .show ()
39
- return im
40
-
41
- focus = focal_point (im , settings )
32
+ focus = focal_point (im_debug , settings )
42
33
43
34
# take the focal point and turn it into crop coordinates that try to center over the focal
44
35
# point but then get adjusted back into the frame
@@ -62,89 +53,143 @@ def crop_image(im, settings):
62
53
63
54
crop = [x1 , y1 , x2 , y2 ]
64
55
56
+ results = []
57
+
58
+ results .append (im .crop (tuple (crop )))
59
+
65
60
if settings .annotate_image :
66
- d = ImageDraw .Draw (im )
61
+ d = ImageDraw .Draw (im_debug )
67
62
rect = list (crop )
68
63
rect [2 ] -= 1
69
64
rect [3 ] -= 1
70
65
d .rectangle (rect , outline = GREEN )
66
+ results .append (im_debug )
71
67
if settings .destop_view_image :
72
- im .show ()
68
+ im_debug .show ()
73
69
74
- return im . crop ( tuple ( crop ))
70
+ return results
75
71
76
72
def focal_point (im , settings ):
77
73
corner_points = image_corner_points (im , settings )
78
74
entropy_points = image_entropy_points (im , settings )
79
75
face_points = image_face_points (im , settings )
80
76
81
- total_points = len (corner_points ) + len (entropy_points ) + len (face_points )
82
-
83
- corner_weight = settings .corner_points_weight
84
- entropy_weight = settings .entropy_points_weight
85
- face_weight = settings .face_points_weight
86
-
87
- weight_pref_total = corner_weight + entropy_weight + face_weight
88
-
89
- # weight things
90
77
pois = []
91
- if weight_pref_total == 0 or total_points == 0 :
92
- return pois
93
78
94
- pois .extend (
95
- [ PointOfInterest ( p .x , p .y , weight = p .weight * ( (corner_weight / weight_pref_total ) / (len (corner_points )/ total_points ) )) for p in corner_points ]
96
- )
97
- pois .extend (
98
- [ PointOfInterest ( p .x , p .y , weight = p .weight * ( (entropy_weight / weight_pref_total ) / (len (entropy_points )/ total_points ) )) for p in entropy_points ]
99
- )
100
- pois .extend (
101
- [ PointOfInterest ( p .x , p .y , weight = p .weight * ( (face_weight / weight_pref_total ) / (len (face_points )/ total_points ) )) for p in face_points ]
102
- )
79
+ weight_pref_total = 0
80
+ if len (corner_points ) > 0 :
81
+ weight_pref_total += settings .corner_points_weight
82
+ if len (entropy_points ) > 0 :
83
+ weight_pref_total += settings .entropy_points_weight
84
+ if len (face_points ) > 0 :
85
+ weight_pref_total += settings .face_points_weight
86
+
87
+ corner_centroid = None
88
+ if len (corner_points ) > 0 :
89
+ corner_centroid = centroid (corner_points )
90
+ corner_centroid .weight = settings .corner_points_weight / weight_pref_total
91
+ pois .append (corner_centroid )
92
+
93
+ entropy_centroid = None
94
+ if len (entropy_points ) > 0 :
95
+ entropy_centroid = centroid (entropy_points )
96
+ entropy_centroid .weight = settings .entropy_points_weight / weight_pref_total
97
+ pois .append (entropy_centroid )
98
+
99
+ face_centroid = None
100
+ if len (face_points ) > 0 :
101
+ face_centroid = centroid (face_points )
102
+ face_centroid .weight = settings .face_points_weight / weight_pref_total
103
+ pois .append (face_centroid )
103
104
104
105
average_point = poi_average (pois , settings )
105
106
106
107
if settings .annotate_image :
107
108
d = ImageDraw .Draw (im )
108
- for f in face_points :
109
- d .rectangle (f .bounding (f .size ), outline = RED )
110
- for f in entropy_points :
111
- d .rectangle (f .bounding (30 ), outline = BLUE )
112
- for poi in pois :
113
- w = max (4 , 4 * 0.5 * sqrt (poi .weight ))
114
- d .ellipse (poi .bounding (w ), fill = BLUE )
115
- d .ellipse (average_point .bounding (25 ), outline = GREEN )
109
+ max_size = min (im .width , im .height ) * 0.07
110
+ if corner_centroid is not None :
111
+ color = BLUE
112
+ box = corner_centroid .bounding (max_size * corner_centroid .weight )
113
+ d .text ((box [0 ], box [1 ]- 15 ), "Edge: %.02f" % corner_centroid .weight , fill = color )
114
+ d .ellipse (box , outline = color )
115
+ if len (corner_points ) > 1 :
116
+ for f in corner_points :
117
+ d .rectangle (f .bounding (4 ), outline = color )
118
+ if entropy_centroid is not None :
119
+ color = "#ff0"
120
+ box = entropy_centroid .bounding (max_size * entropy_centroid .weight )
121
+ d .text ((box [0 ], box [1 ]- 15 ), "Entropy: %.02f" % entropy_centroid .weight , fill = color )
122
+ d .ellipse (box , outline = color )
123
+ if len (entropy_points ) > 1 :
124
+ for f in entropy_points :
125
+ d .rectangle (f .bounding (4 ), outline = color )
126
+ if face_centroid is not None :
127
+ color = RED
128
+ box = face_centroid .bounding (max_size * face_centroid .weight )
129
+ d .text ((box [0 ], box [1 ]- 15 ), "Face: %.02f" % face_centroid .weight , fill = color )
130
+ d .ellipse (box , outline = color )
131
+ if len (face_points ) > 1 :
132
+ for f in face_points :
133
+ d .rectangle (f .bounding (4 ), outline = color )
134
+
135
+ d .ellipse (average_point .bounding (max_size ), outline = GREEN )
116
136
117
137
return average_point
118
138
119
139
120
140
def image_face_points (im , settings ):
121
- np_im = np .array (im )
122
- gray = cv2 .cvtColor (np_im , cv2 .COLOR_BGR2GRAY )
123
-
124
- tries = [
125
- [ f'{ cv2 .data .haarcascades } haarcascade_eye.xml' , 0.01 ],
126
- [ f'{ cv2 .data .haarcascades } haarcascade_frontalface_default.xml' , 0.05 ],
127
- [ f'{ cv2 .data .haarcascades } haarcascade_profileface.xml' , 0.05 ],
128
- [ f'{ cv2 .data .haarcascades } haarcascade_frontalface_alt.xml' , 0.05 ],
129
- [ f'{ cv2 .data .haarcascades } haarcascade_frontalface_alt2.xml' , 0.05 ],
130
- [ f'{ cv2 .data .haarcascades } haarcascade_frontalface_alt_tree.xml' , 0.05 ],
131
- [ f'{ cv2 .data .haarcascades } haarcascade_eye_tree_eyeglasses.xml' , 0.05 ],
132
- [ f'{ cv2 .data .haarcascades } haarcascade_upperbody.xml' , 0.05 ]
133
- ]
134
-
135
- for t in tries :
136
- # print(t[0])
137
- classifier = cv2 .CascadeClassifier (t [0 ])
138
- minsize = int (min (im .width , im .height ) * t [1 ]) # at least N percent of the smallest side
139
- try :
140
- faces = classifier .detectMultiScale (gray , scaleFactor = 1.1 ,
141
- minNeighbors = 7 , minSize = (minsize , minsize ), flags = cv2 .CASCADE_SCALE_IMAGE )
142
- except :
143
- continue
144
-
145
- if len (faces ) > 0 :
146
- rects = [[f [0 ], f [1 ], f [0 ] + f [2 ], f [1 ] + f [3 ]] for f in faces ]
147
- return [PointOfInterest ((r [0 ] + r [2 ]) // 2 , (r [1 ] + r [3 ]) // 2 , size = abs (r [0 ]- r [2 ])) for r in rects ]
141
+ if settings .dnn_model_path is not None :
142
+ detector = cv2 .FaceDetectorYN .create (
143
+ settings .dnn_model_path ,
144
+ "" ,
145
+ (im .width , im .height ),
146
+ 0.8 , # score threshold
147
+ 0.3 , # nms threshold
148
+ 5000 # keep top k before nms
149
+ )
150
+ faces = detector .detect (np .array (im ))
151
+ results = []
152
+ if faces [1 ] is not None :
153
+ for face in faces [1 ]:
154
+ x = face [0 ]
155
+ y = face [1 ]
156
+ w = face [2 ]
157
+ h = face [3 ]
158
+ results .append (
159
+ PointOfInterest (
160
+ int (x + (w * 0.5 )), # face focus left/right is center
161
+ int (y + (h * 0 )), # face focus up/down is close to the top of the head
162
+ size = w ,
163
+ weight = 1 / len (faces [1 ])
164
+ )
165
+ )
166
+ return results
167
+ else :
168
+ np_im = np .array (im )
169
+ gray = cv2 .cvtColor (np_im , cv2 .COLOR_BGR2GRAY )
170
+
171
+ tries = [
172
+ [ f'{ cv2 .data .haarcascades } haarcascade_eye.xml' , 0.01 ],
173
+ [ f'{ cv2 .data .haarcascades } haarcascade_frontalface_default.xml' , 0.05 ],
174
+ [ f'{ cv2 .data .haarcascades } haarcascade_profileface.xml' , 0.05 ],
175
+ [ f'{ cv2 .data .haarcascades } haarcascade_frontalface_alt.xml' , 0.05 ],
176
+ [ f'{ cv2 .data .haarcascades } haarcascade_frontalface_alt2.xml' , 0.05 ],
177
+ [ f'{ cv2 .data .haarcascades } haarcascade_frontalface_alt_tree.xml' , 0.05 ],
178
+ [ f'{ cv2 .data .haarcascades } haarcascade_eye_tree_eyeglasses.xml' , 0.05 ],
179
+ [ f'{ cv2 .data .haarcascades } haarcascade_upperbody.xml' , 0.05 ]
180
+ ]
181
+ for t in tries :
182
+ classifier = cv2 .CascadeClassifier (t [0 ])
183
+ minsize = int (min (im .width , im .height ) * t [1 ]) # at least N percent of the smallest side
184
+ try :
185
+ faces = classifier .detectMultiScale (gray , scaleFactor = 1.1 ,
186
+ minNeighbors = 7 , minSize = (minsize , minsize ), flags = cv2 .CASCADE_SCALE_IMAGE )
187
+ except :
188
+ continue
189
+
190
+ if len (faces ) > 0 :
191
+ rects = [[f [0 ], f [1 ], f [0 ] + f [2 ], f [1 ] + f [3 ]] for f in faces ]
192
+ return [PointOfInterest ((r [0 ] + r [2 ]) // 2 , (r [1 ] + r [3 ]) // 2 , size = abs (r [0 ]- r [2 ]), weight = 1 / len (rects )) for r in rects ]
148
193
return []
149
194
150
195
@@ -161,7 +206,7 @@ def image_corner_points(im, settings):
161
206
np_im ,
162
207
maxCorners = 100 ,
163
208
qualityLevel = 0.04 ,
164
- minDistance = min (grayscale .width , grayscale .height )* 0.07 ,
209
+ minDistance = min (grayscale .width , grayscale .height )* 0.03 ,
165
210
useHarrisDetector = False ,
166
211
)
167
212
@@ -171,7 +216,7 @@ def image_corner_points(im, settings):
171
216
focal_points = []
172
217
for point in points :
173
218
x , y = point .ravel ()
174
- focal_points .append (PointOfInterest (x , y , size = 4 ))
219
+ focal_points .append (PointOfInterest (x , y , size = 4 , weight = 1 / len ( points ) ))
175
220
176
221
return focal_points
177
222
@@ -205,17 +250,22 @@ def image_entropy_points(im, settings):
205
250
x_mid = int (crop_best [0 ] + settings .crop_width / 2 )
206
251
y_mid = int (crop_best [1 ] + settings .crop_height / 2 )
207
252
208
- return [PointOfInterest (x_mid , y_mid , size = 25 )]
253
+ return [PointOfInterest (x_mid , y_mid , size = 25 , weight = 1.0 )]
209
254
210
255
211
256
def image_entropy (im ):
212
257
# greyscale image entropy
213
- # band = np.asarray(im.convert("L"))
214
- band = np .asarray (im .convert ("1" ), dtype = np .uint8 )
258
+ band = np .asarray (im .convert ("L" ))
259
+ # band = np.asarray(im.convert("1"), dtype=np.uint8)
215
260
hist , _ = np .histogram (band , bins = range (0 , 256 ))
216
261
hist = hist [hist > 0 ]
217
262
return - np .log2 (hist / hist .sum ()).sum ()
218
263
264
+ def centroid (pois ):
265
+ x = [poi .x for poi in pois ]
266
+ y = [poi .y for poi in pois ]
267
+ return PointOfInterest (sum (x )/ len (pois ), sum (y )/ len (pois ))
268
+
219
269
220
270
def poi_average (pois , settings ):
221
271
weight = 0.0
@@ -260,11 +310,12 @@ def bounding(self, size):
260
310
261
311
262
312
class Settings :
263
- def __init__ (self , crop_width = 512 , crop_height = 512 , corner_points_weight = 0.5 , entropy_points_weight = 0.5 , face_points_weight = 0.5 , annotate_image = False ):
313
+ def __init__ (self , crop_width = 512 , crop_height = 512 , corner_points_weight = 0.5 , entropy_points_weight = 0.5 , face_points_weight = 0.5 , annotate_image = False , dnn_model_path = None ):
264
314
self .crop_width = crop_width
265
315
self .crop_height = crop_height
266
316
self .corner_points_weight = corner_points_weight
267
317
self .entropy_points_weight = entropy_points_weight
268
- self .face_points_weight = entropy_points_weight
318
+ self .face_points_weight = face_points_weight
269
319
self .annotate_image = annotate_image
270
- self .destop_view_image = False
320
+ self .destop_view_image = False
321
+ self .dnn_model_path = dnn_model_path
0 commit comments