├── .gitignore ├── README.rst ├── __init__.py ├── example.png ├── features ├── __init__.py ├── blur.py ├── composition.py ├── contrast.py ├── faces.py └── noise.py ├── grid.py ├── histogram.py ├── image.py ├── logger.py ├── main.py ├── papers.py ├── test.py ├── training_data.py └── windows.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Image Quality Analysis Final Project 2 | ==================================== 3 | 4 | This is my final project code for Computational Vision. 5 | **I really wish I could improve the quality of code at some point.** 6 | This was created in approximately two weeks, which includes figuring 7 | out how to compile and learn OpenCV. 8 | 9 | .. image:: example.png 10 | 11 | Prerequisites 12 | ------------- 13 | 14 | You'll need OpenCV and Numpy to run this. 15 | 16 | Configuration 17 | -------------- 18 | 19 | The main entry point is main.py. 20 | 21 | Not all the options are available for configuration on the command line, 22 | so you'll have to manually edit the main.py code (which is messy, I know). 23 | 24 | Near the top of main.py are the features we're using:: 25 | 26 | measurements = ( 27 | contrast, 28 | noise, 29 | blur, 30 | composition, 31 | #faces, # => under composition 32 | ) 33 | 34 | Uncomment or comment the features you wish the analyzer to use. Blur is 35 | the longest to compute on average and composition is incomplete. 36 | 37 | If you want to change/add some training data...:: 38 | 39 | california_night = TrainingData('good/Another Beautiful California Night.jpg', 40 | measures=measurements, kind='good', 41 | ) 42 | 43 | These (and other lines like it) list some test images. Currently, they are 44 | not included with the project yet:: 45 | 46 | 47 | li_dir = load_images_from('li') 48 | chow_dir = load_images_from('chow') 49 | china_dir = load_images_from('/Users/jeff/Desktop/china-day6/full') 50 | 51 | This contains a collection of images to use. You can create your own here. 52 | But you'll need to modify the switch case below it:: 53 | 54 | if options.imageset: 55 | if options.imageset == 'li': 56 | process(li_dir) 57 | return 0 58 | elif options.imageset == 'chow': 59 | process(chow_dir) 60 | return 0 61 | elif options.imageset == 'china': 62 | process(china_dir) 63 | return 0 64 | elif options.imageset == 'custom': 65 | process() 66 | return 0 67 | 68 | Inside the process() function, there is a max_size argument:: 69 | 70 | max_size = None#(800, 600) 71 | 72 | Setting it to a 2-tuple restricts the width or height to the given value 73 | if the image is greater than the given size. This only applies to image sets. 74 | 75 | Similarly, there's an identical one for single images in the main():: 76 | 77 | size = None #(320,240,'crop') # (0.5, 0.5, 'resize-p') 78 | if size is None: 79 | im = tdata.load() 80 | elif size[-1] == 'crop': 81 | im = image.random_cropped_region(tdata.load(), size[:2]) 82 | elif size[-1] == 'resize': 83 | im = tdata.load(size[:2]) 84 | elif size[-1] == 'resize-p': 85 | im = image.resize(tdata.load(), by_percent=size[:2]) 86 | else: 87 | raise TypeError, "Invalid image sizing type." 88 | 89 | The size argument takes a 3-tuple, the first two are sizes, and the last 90 | is the method to reduce the size. 'resize-p' resizes by percent (in 91 | decimal form), while 'crop' crops a random region in the image. 'resize' is 92 | identical to max_size argument. 93 | 94 | Usage 95 | --------- 96 | 97 | To use, run main.py on an image set:: 98 | 99 | python main.py --imageset=custom 100 | 101 | The results are placed in output.html. If you want to run on only one image, 102 | use the file argument:: 103 | 104 | python main.py --file=/path/to/file.jpg 105 | 106 | You can also pass the debug flag to see intermediary image processing 107 | steps:: 108 | 109 | python main.py --file=/path/to/file.jpg -d # Don't run this! 110 | 111 | But **this creates a ALOT of debugging windows**. You'll want to see the 112 | debugging information for one feature, use the --type argument:: 113 | 114 | python main.py --file="/path/to/file.jpg" -d --type=blur 115 | 116 | And that is all for now. -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffh/CV-Image-Quality-Analysis/323e9bf4ad1ba25c28853bc85691eadcbae35464/__init__.py -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffh/CV-Image-Quality-Analysis/323e9bf4ad1ba25c28853bc85691eadcbae35464/example.png -------------------------------------------------------------------------------- /features/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffh/CV-Image-Quality-Analysis/323e9bf4ad1ba25c28853bc85691eadcbae35464/features/__init__.py -------------------------------------------------------------------------------- /features/blur.py: -------------------------------------------------------------------------------- 1 | import image 2 | import cv 3 | import random 4 | import noise 5 | import math 6 | from numpy import array 7 | 8 | # saturation is all ZERO when image is grayscale! 9 | def get_focus_points(im,debug=False): 10 | edges = image.dilate(image.auto_edges(im)) 11 | #d = 1 12 | #sobel = image.sobel(im, xorder=d, yorder=d) 13 | sobel = image.laplace(im) 14 | hsv = image.rgb2hsv(im) 15 | 16 | focused = image.And(sobel, edges) 17 | if im.nChannels == 3: 18 | hue, saturation, value = image.split(hsv) 19 | saturation = image.dilate(saturation) 20 | focused = image.And(focused, saturation) 21 | focused = image.threshold(image.dilate(focused), threshold=32) 22 | 23 | if debug: 24 | image.show(edges, "1. Edges") 25 | image.show(sobel, "2. Sobel") 26 | if im.nChannels == 3: 27 | image.show(saturation, "3. Saturation") 28 | return focused 29 | 30 | def convert_to_points(im): 31 | points = [] 32 | size = cv.GetSize(im) 33 | for x in xrange(size[0]): 34 | for y in xrange(size[1]): 35 | if im[y, x] > 0: 36 | points.append((x, y)) 37 | return points 38 | 39 | """ 40 | # too slow, too inefficient 41 | from hcluster import pdist, linkage, to_tree, dendrogram, centroid 42 | def form_groups(points, threshold=5, min_size=0.025): 43 | "Clusters points based on a given threshold. If the cluster's dist > threshold, it is split." 44 | print "Threshold =", threshold 45 | points = array(points) 46 | #Y = pdist(points) 47 | print "centroids" 48 | Z = centroid(points) 49 | print "dendrogram" 50 | dendrogram(Z) 51 | print "to_tree" 52 | R = to_tree(Z) 53 | print "get_count" 54 | total = R.get_count() 55 | clusters = [R] 56 | seen = set() 57 | min_size = max(int(total * min_size), 4) 58 | 59 | while 1: 60 | if clusters[0] in seen: 61 | break 62 | node = clusters.pop(0) 63 | diff = abs(node.get_left().get_count() - node.get_right().get_count()) 64 | should_split = not node.is_leaf() and node.dist > threshold 65 | should_split = should_split and total > min_size and \ 66 | node.get_left().get_count() > min_size and \ 67 | node.get_right().get_count() > min_size 68 | if not should_split: 69 | clusters.append(node) 70 | seen = seen.union([node]) 71 | else: 72 | clusters.extend((node.get_left(), node.get_right())) 73 | print 'clusters', len(clusters) 74 | def get_ids(c, accum=None): 75 | if c.is_leaf(): 76 | return [c.id] 77 | l = [] 78 | l.extend(get_ids(c.get_left())) 79 | l.extend(get_ids(c.get_right())) 80 | return l 81 | return [get_ids(c) for c in clusters] 82 | """ 83 | from scipy.cluster.vq import whiten, kmeans2, vq 84 | def form_groups(points, estimated_size=10, iter=1): 85 | if len(points) < 1: 86 | return [] 87 | points = array(points) 88 | centroids, variance = kmeans2(points, estimated_size, iter=iter, minit='points') 89 | group_indicies, dist = vq(points, centroids) 90 | group = {} 91 | for i,index in enumerate(group_indicies): 92 | if index not in group: 93 | group[index] = [] 94 | group[index].append(points[i]) 95 | return group.values() 96 | 97 | def draw_groups(groups, im=None): 98 | 99 | hulls = [] 100 | for group in groups: 101 | ch = ConvexHull([(g[0], g[1]) for g in group])#map(lambda x: (x[0], x[1]), group)) 102 | #ch = ConvexHull(map(lambda x: points[x], group)) 103 | hulls.append(ch) 104 | 105 | ppp = ch.points_per_pixel() 106 | #if ch.area() >= min_area: # not a 3x3 region 107 | # densities.append(ppp) 108 | a = int(ppp * 255) 109 | if im: 110 | ch.draw_filled_hull(im, rgb=(a,a,a))#r(255),r(255),r(255))) 111 | 112 | 113 | #if debug and ch.area() < min_area: 114 | # ch.draw_centroid(focused_regions, rgb=(255,0,0)) 115 | 116 | #ch.draw_hull(focused_regions, rgb=(r(255),r(255),r(255))) 117 | 118 | #ch.draw_points(focused_regions, rgb=(r(255),r(255),r(255))) 119 | return hulls 120 | 121 | class Contours(object): 122 | def __init__(self, im): 123 | self.im = im 124 | self.storage = cv.CreateMemStorage(0) 125 | self.contours = cv.FindContours(im, self.storage, cv.CV_RETR_TREE, 126 | cv.CV_CHAIN_APPROX_SIMPLE) 127 | 128 | def approx_poly(self): 129 | self.contours = cv.ApproxPoly(self.contours, self.storage, 130 | cv.CV_POLY_APPROX_DP, 3, 1) 131 | return self 132 | 133 | def draw(self, im, levels=3, thickness=3, external_rgb=(255,0,0), internal_rgb=(0,255,0)): 134 | # draw contours in red and green 135 | cv.DrawContours (im, self.contours, 136 | cv.RGB(*external_rgb), cv.RGB(*internal_rgb), 137 | levels, thickness, cv.CV_AA) 138 | 139 | def __iter__(self): 140 | start = contour = self.contours 141 | while contour: 142 | yield contour 143 | contour = contour.h_next() 144 | self.contours = start 145 | 146 | class ConvexHull(object): 147 | def __init__(self, points): 148 | # compute the convex hull 149 | self.boundary_points = points[:] 150 | self.points = points 151 | self.storage = cv.CreateMemStorage(0) 152 | self.hull = cv.ConvexHull2(self.points, self.storage, cv.CV_CLOCKWISE, 1) 153 | self._centroid = None 154 | 155 | def __contains__(self, point): 156 | return cv.PointPolygonTest(self.boundary_points, point, 0) >= 0 157 | 158 | def draw_points(self, im, rgb=(255,0,0)): 159 | for pt in self.points: 160 | cv.Circle (im, pt, 2, cv.RGB(*rgb), 161 | cv.CV_FILLED, cv.CV_AA, 0) 162 | 163 | def draw_filled_hull(self, im, rgb=(255,255,255)): 164 | cv.FillPoly(im, [self.hull], cv.RGB(*rgb), cv.CV_AA) 165 | 166 | def draw_hull(self, im, rgb=(0,255,0)): 167 | cv.PolyLine(im, [self.hull], 1, cv.RGB(*rgb), 1, cv.CV_AA) 168 | 169 | def draw_centroid(self, im, rgb=(0,0,255)): 170 | cv.Circle (im, self.centroid(), 2, cv.RGB(*rgb), 171 | cv.CV_FILLED, cv.CV_AA, 0) 172 | 173 | def area(self): 174 | return max(abs(cv.ContourArea(self.hull)), 0.00001) 175 | 176 | def points_per_pixel(self): 177 | return len(self.points) / self.area() 178 | 179 | def centroid(self): 180 | if self._centroid is None: 181 | sum = [0, 0] 182 | for p in self.points: 183 | sum[0] += p[0] 184 | sum[1] += p[1] 185 | s = float(len(self.points)) 186 | self._centroid = int(sum[0] / s), int(sum[1] / s) 187 | return self._centroid 188 | 189 | def pixel_remove(im): 190 | p = im[1,1] 191 | if p == 0: 192 | return 0 193 | c = 0 194 | if im[0,0] > 0 and im[2,2] > 0: 195 | c += 1 196 | if im[1,0] > 0 and im[1,2] > 0: 197 | c += 1 198 | if im[2,0] > 0 and im[0,2] > 0: 199 | c += 1 200 | if im[0,1] > 0 and im[2,1] > 0: 201 | c += 1 202 | if c > 2: 203 | return 0 204 | return c > 0 205 | 206 | def remove_useless_points(im): 207 | return image.neighbor_map(im, pixel_remove) 208 | 209 | # ri_threshold = 0.2 is too weak for large images 210 | def boolean(score): 211 | # the focus regions should cover 15%+ of the screen 212 | # the density of the regions should be > 20% point/pixel 213 | region_image_ratio, density, density_std, saturation_score, light_score = score 214 | if density > 0.7: 215 | return False 216 | if light_score > 0.5: 217 | return True 218 | return (region_image_ratio <= 0.1 and \ 219 | (density - density_std < 0.2)) 220 | # TODO: saturation_score needs more tweaking 221 | #or saturation_score > 0.8 222 | #return score < d_threshold 223 | 224 | requires_result_from = [] # noise perhaps??? 225 | # was using Agglomerative Clustering, but too slow for points > ~5000 226 | def measure(im, debug=False): 227 | size = cv.GetSize(im) 228 | npixels = size[0] * size[1] 229 | #print 'np', npixels 230 | 231 | 232 | focused = get_focus_points(im, debug) 233 | points = convert_to_points(focused) 234 | 235 | if debug: 236 | print "\t"+str(len(points)), '/', npixels, '=', len(points) / float(npixels) 237 | print "\tlen(points) =", len(points) 238 | image.show(focused, "4. Focused Points") 239 | 240 | saturation_score = 0 241 | if not image.is_grayscale(im): 242 | edges = image.auto_edges(im) 243 | _, saturation, _ = image.split(image.rgb2hsv(im)) 244 | if debug: 245 | image.show(saturation, "5. Saturation") 246 | #saturation = image.laplace(image.gaussian(saturation, 3)) 247 | saturation = image.invert(saturation) 248 | mask = image.invert(image.threshold(im, threshold=16)) 249 | if debug: 250 | image.show(saturation, "5.3. Laplace of Saturation") 251 | cv.Set(saturation, 0, mask) 252 | cv.Set(saturation, 0, focused) 253 | if debug: 254 | image.show(mask, "5.6. Mask(focused AND invert(threshold(im, 16)))") 255 | image.show(saturation, "6. Set(<5.3>, 0, <5.6>)") 256 | 257 | saturation_score = cv.Sum(saturation)[0] / float(npixels * 255) 258 | print "\tSaturation Score:", saturation_score 259 | 260 | # light exposure 261 | h,s,v = image.split(image.rgb2hsv(im)) 262 | if debug: 263 | image.show(h, "7. Hue") 264 | image.show(s, "7. Saturation") 265 | image.show(v, "7. Value") 266 | diff = cv.CloneImage(v) 267 | cv.Set(diff, 0, image.threshold(s, threshold=16)) 268 | diff = image.dilate(diff, iterations=10) 269 | if debug: 270 | thres_s = image.threshold(s, threshold=16) 271 | image.show(thres_s, "8.3. Mask(threshold(<7.Saturation>, 16))") 272 | image.show(diff, "8.6. Dilate(Set(<7.Value>, 0, <8.3>), 10)") 273 | 274 | cdiff = cv.CountNonZero(diff) 275 | if cdiff > 0 and cdiff / float(npixels) > 0.01: 276 | test = cv.CloneImage(v) 277 | cv.Set(test, 0, image.invert(diff)) 278 | s = cv.Sum(test)[0] / float(cdiff * 255) 279 | if debug: 280 | print '\tLight Exposure Score:', s 281 | else: 282 | s = 0 283 | 284 | if image.is_grayscale(im): 285 | return focused, (1, 1, 1, saturation_score, s) 286 | 287 | # we want to short circuit ASAP to avoid doing KMeans 50% of the image's pixels 288 | if len(points) > npixels/2: 289 | return focused, (1, 1, 1, saturation_score, s) 290 | 291 | # we're so blurry we don't have any points! 292 | if len(points) < 1: 293 | return focused, (0, 0, 0, saturation_score, s) 294 | 295 | if debug: 296 | im2 = cv.CloneImage(im) 297 | focused_regions = image.new_from(im) 298 | cv.Set(focused_regions, 0) 299 | 300 | r = lambda x: random.randrange(1, x) 301 | groups = form_groups(points, 302 | estimated_size=min(max(int(len(points) / 1000), 2), 15)) 303 | #groups = form_groups(points, threshold=max(cv.GetSize(im))/16) 304 | #print 'groups', len(groups) 305 | hulls = draw_groups(groups, focused_regions) 306 | focused_regions = image.threshold(focused_regions, threshold=32, type=cv.CV_THRESH_TOZERO) 307 | min_area = npixels * 0.0005 308 | densities = [h.points_per_pixel() for h in hulls if h.area() >= min_area] 309 | 310 | if debug: 311 | #image.show(focused, "Focused Points") 312 | image.show(focused_regions, "9. Focused Regions from <4>") 313 | cv.Sub(im2, image.gray2rgb(image.invert(image.threshold(focused_regions, threshold=1))), im2) 314 | image.show(im2, "10. threshold(<9>)") 315 | 316 | 317 | focused_regions = image.rgb2gray(focused_regions) 318 | 319 | densities = array(densities) 320 | c = cv.CountNonZero(focused_regions) 321 | c /= float(npixels) 322 | 323 | score = (c, densities.mean(), densities.std(), saturation_score, s) 324 | 325 | return focused, score -------------------------------------------------------------------------------- /features/composition.py: -------------------------------------------------------------------------------- 1 | import image 2 | import cv 3 | from features.blur import form_groups, convert_to_points, ConvexHull, draw_groups, Contours 4 | from histogram import GrayscaleHist 5 | from numpy import array 6 | from contrast import average 7 | from features import faces 8 | from grid import Grid 9 | 10 | def measure_focused_roi(im, roi, area, focus_points, debug=False): 11 | g = Grid(cv.GetSize(im)) 12 | canvas = image.new_from(im) 13 | cv.Set(canvas, 0) 14 | focus_in_roi = image.And(focus_points, roi) 15 | if debug: 16 | image.show(focus_in_roi, "ROI + Focused Points") 17 | 18 | densities = [] 19 | points = convert_to_points(focus_in_roi) 20 | groups = form_groups(points, estimated_size=24, iter=5) 21 | for group in groups: 22 | ch = ConvexHull(map(lambda x: (x[0], x[1]), group)) 23 | 24 | ppp = ch.points_per_pixel() 25 | a = int(ppp * 255) 26 | ch.draw_filled_hull(canvas, rgb=(a,a,a)) 27 | if debug: 28 | image.show(canvas, "Focused Regions in ROI") 29 | 30 | quadrants = g.split_in_four(canvas) 31 | sums = [] 32 | for i,quad in enumerate(quadrants): 33 | sums.append(cv.Sum(quad)[0] / float(area/4)) 34 | arr = array(sums) 35 | print arr.mean(), arr.std() 36 | diff = max(sums) - min(sums) 37 | 38 | return diff, arr.std() 39 | 40 | def error_from_uniform(mean, stddev): 41 | return abs(mean - 128)/128.0, abs(stddev - 64)/64.0 42 | 43 | def measure_color_roi(im, roi, area, focused_regions, debug=False): 44 | im = cv.CloneImage(im) 45 | g = Grid(cv.GetSize(im)) 46 | 47 | 48 | """ 49 | contours = Contours(image.threshold(focused_regions, threshold=1)).approx_poly() 50 | if debug: 51 | test = image.new_from(im) 52 | cv.Set(test, 0) 53 | for c in contours: 54 | i = 1 55 | while c: 56 | cv.FillPoly(test, [[c[x] for x in range(len(c))]], cv.RGB(0,64*i,0)) 57 | c = c.v_next() 58 | i += 1 59 | #contours.draw(test, levels=9) 60 | image.show(test, "Test") 61 | """ 62 | #mask = image.And(image.threshold(focused_regions, threshold=1), roi) 63 | # 64 | #canvas = image.new_from(im, nChannels=1) 65 | #cv.Set(canvas, 0) 66 | #if cv.CountNonZero(mask) <= 1: 67 | # return 0, 0 68 | #contours = Contours(image.dilate(mask)).approx_poly() 69 | #for c in contours: 70 | # i = 1 71 | # while c: 72 | # cv.FillPoly(canvas, [[c[x] for x in range(len(c))]], 255) 73 | # c = c.v_next() 74 | # i += 1 75 | #mask = image.Or(mask, canvas) 76 | #if debug: 77 | # image.show(mask, "MASK") 78 | # 79 | #cv.Set(im, 0, image.invert(mask)) 80 | cv.Set(im, 0, image.invert(roi)) 81 | 82 | #area = cv.CountNonZero(image.threshold(im, threshold=1)) 83 | 84 | if debug: 85 | image.show(g.draw(im,thickness=2), "Image + ROI + Focus point mask") 86 | 87 | scores = [] 88 | im = image.rgb2gray(im) 89 | #canvas = image.And(plane, roi) 90 | quadrants = g.split_in_four(im) 91 | hist = [] 92 | for q,quad in enumerate(quadrants): 93 | #scores.append(cv.Sum(quad)[0] / float(area/4)) 94 | h = GrayscaleHist(value_range=(1,255)).use_image(quad) 95 | #image.show(h.to_img(), ['gray', 'red','green','blue'][i] + ' in ' + str(q)) 96 | hist.append(h.to_array()) 97 | scores = [] 98 | excluded_points = set([(2, 1), (3, 0)]) 99 | for i,h1 in enumerate(hist): 100 | for j,h2 in enumerate(hist): 101 | if i <= j or (i,j) in excluded_points: 102 | continue 103 | h = abs(h2-h1) 104 | ht = GrayscaleHist(value_range=(0,255)).use_array_as_hist(h) 105 | scores.append((h[5:].mean(), h[5:].std())) 106 | means = max([x[0] for x in scores]) 107 | stddevs = max([x[1] for x in scores]) 108 | return means/255.0, stddevs/255.0 109 | 110 | def measure_saturation(im, debug=False): 111 | _, sat, _ = image.split(image.rgb2hsv(im)) 112 | arr = image.cv2array(sat) 113 | return arr.mean(), arr.std() 114 | 115 | def measure_contrast(im, debug=False): 116 | h = GrayscaleHist(bins=64).use_image(image.rgb2gray(im)) 117 | return h.stddev() 118 | 119 | def boolean((focused, contrast, saturation, face_score)): 120 | # focus = maximum different between one of four quads interms of # of focus pixels 121 | #focus, (means, stddev), face_score = score 122 | #return focus < 0.1 or (means > 0.05 or stddev > 0.025) #or faces.boolean(face_score) 123 | focused_mean_diff, focused_std = focused 124 | sat_mean, sat_std = saturation 125 | if not faces.boolean(face_score): 126 | return False 127 | if contrast > 48: 128 | return False 129 | return focused_mean_diff - focused_std * 2 < 0.2 or sat_mean + sat_std <= 96 130 | 131 | requires_result_from = ['blur'] 132 | def measure(im, blur, noise=None, debug=False): 133 | focus_points = blur[0] 134 | #is_noisy = noise[2] 135 | 136 | size = cv.GetSize(im) 137 | npixels = size[0] * size[1] 138 | 139 | #if focused_regions is None: 140 | # focused_regions = image.new_from(im) 141 | # cv.Set(focused_regions, 0) 142 | # groups = form_groups(focus_points, 143 | # estimated_size=min(max(int(len(npixels) / 1000), 2), 15)) 144 | # #groups = form_groups(points, threshold=max(cv.GetSize(im))/16) 145 | # #print 'groups', len(groups) 146 | # draw_groups(groups, focused_regions) 147 | 148 | im2 = cv.CloneImage(im) 149 | g = Grid(cv.GetSize(im2)) 150 | if debug: 151 | image.show(g.draw(im2), "Image with Grid + ROI") 152 | 153 | roi = image.new_from(im, nChannels=1) 154 | cv.Set(roi, 0) 155 | #g.draw_lines(roi, thickness=int(max(min((size[0] + size[1]) * 1/100.0, 255), 1))) 156 | g.draw_regions(roi) 157 | area = cv.Sum(roi)[0] 158 | 159 | (_, face_rects), face_score = faces.measure(im) 160 | face_block = image.new_from(im, nChannels=1) 161 | cv.Set(face_block, 0) 162 | for r in face_rects: 163 | r.draw(face_block, color=cv.RGB(255,255,255), thickness=cv.CV_FILLED) 164 | 165 | if debug: 166 | face_roi = cv.CloneImage(im) 167 | cv.Set(face_roi, 0, image.invert(roi)) 168 | cv.Set(face_roi, 0, image.invert(image.threshold(face_block, threshold=1))) 169 | 170 | image.show(face_block, "Faces in Binary") 171 | image.show(g.draw(face_roi), "Face + ROI") 172 | 173 | return (im, ( 174 | measure_focused_roi(im, roi, area, focus_points, debug), 175 | #measure_color_roi(im, roi, area, focus_points, debug), 176 | measure_contrast(im, debug), 177 | measure_saturation(im, debug), 178 | faces.measure(im, debug)[1], 179 | )) -------------------------------------------------------------------------------- /features/contrast.py: -------------------------------------------------------------------------------- 1 | import image 2 | import cv 3 | from windows import GrayRGBWindow 4 | from grid import Grid 5 | 6 | from histogram import GrayscaleHist 7 | 8 | def average(seq): 9 | if len(seq) == 0: 10 | return 0 11 | s = sum(seq) 12 | return s / float(len(seq)) 13 | 14 | def boolean(score, threshold=32): # restricted to the life, universe, and everything 15 | (mean, stddev) = score 16 | return (mean < threshold) and stddev < 64 17 | #return mean - stddev/2 <= 0 or mean < 32 18 | 19 | def score_hist(hist): 20 | #a1, a2 = average(hist.count(slice(None, 32))), average(hist.count(slice(32, None))) 21 | #return min(a1, a2) / max(a1, a2) 22 | return hist.mean(), hist.stddev() 23 | 24 | requires_result_from = [] 25 | def measure(im, debug=False): 26 | gray = image.rgb2gray(im) 27 | _,s,v = image.split(image.rgb2hsv(im)) 28 | h = GrayscaleHist(bins=64).use_image(v) 29 | s = GrayscaleHist(bins=64).use_image(s) 30 | scores = [score_hist(h)] 31 | 32 | if debug: 33 | image.show(v, "1. Value") 34 | image.show(h.to_img(), "2. Value Histogram") 35 | image.show(s.to_img(), "2. Saturation Histogram") 36 | print score_hist(s) 37 | 38 | return ( 39 | None, #h.to_img(), 40 | scores[0], 41 | ) -------------------------------------------------------------------------------- /features/faces.py: -------------------------------------------------------------------------------- 1 | import image 2 | import cv 3 | import os 4 | 5 | DIR = os.path.join(*('data/haarcascades'.split('/'))) 6 | face_cascades = [os.path.join(DIR,x) for x in ( 7 | 'haarcascade_frontalface_alt.xml', 8 | 'haarcascade_frontalface_alt2.xml', 9 | 'haarcascade_frontalface_alt_tree.xml', 10 | 'haarcascade_frontalface_default.xml', 11 | 'haarcascade_profileface.xml', 12 | )] 13 | 14 | eye_cascades = [os.path.join(DIR,x) for x in ( 15 | 'haarcascade_eye.xml', 16 | 'haarcascade_eye_tree_eyeglasses.xml', 17 | 'haarcascade_mcs_eyepair_big.xml', 18 | 'haarcascade_mcs_eyepair_small.xml', 19 | 'haarcascade_mcs_lefteye.xml', 20 | 'haarcascade_lefteye_2splits.xml', 21 | 'haarcascade_mcs_righteye.xml', 22 | 'haarcascade_righteye_2splits.xml', 23 | )] 24 | 25 | mouth_cascades = [os.path.join(DIR,x) for x in ( 26 | 'haarcascade_mcs_mouth.xml', 27 | )] 28 | 29 | body_cascades = [os.path.join(DIR,x) for x in ( 30 | 'haarcascade_fullbody.xml', 31 | 'haarcascade_upperbody.xml', 32 | 'haarcascade_lowerbody.xml', 33 | 'haarcascade_mcs_upperbody.xml', 34 | )] 35 | 36 | def detect_skin(im, debug=False): 37 | hsv = image.rgb2hsv(im) 38 | 39 | if debug: 40 | image.show(hsv, 'hsv') 41 | h,s,v = image.split(hsv) 42 | 43 | if cv.CountNonZero(h) == cv.CountNonZero(s) == 0: 44 | white = image.new_from(im) 45 | cv.Set(white, 255) 46 | return white 47 | 48 | if debug: 49 | image.show(h, "Hue") 50 | image.show(s,"sat1") 51 | 52 | h_rng = 0, 46 53 | s_rng = 48, 178 54 | 55 | h = image.threshold(image.gaussian(h, 5), threshold=h_rng[1], type=cv.CV_THRESH_TOZERO_INV) 56 | h = image.threshold(h, threshold=h_rng[0], type=cv.CV_THRESH_TOZERO) 57 | h = image.threshold(h, threshold=1) 58 | 59 | s = image.threshold(image.gaussian(s, 5), threshold=s_rng[1], type=cv.CV_THRESH_TOZERO_INV) 60 | s = image.threshold(s, threshold=s_rng[0], type=cv.CV_THRESH_TOZERO) 61 | if debug: 62 | image.show(s,"sat2") 63 | s = image.threshold(s, threshold=1) 64 | 65 | v = image.dilate(image.erode(image.And(s, h))) 66 | 67 | 68 | #im = image.hsv2rgb(image.merge(h,s,v)) 69 | if debug: 70 | image.show(v, "Human") 71 | return image.threshold(v, threshold=1) 72 | 73 | im_scale = 0.5 74 | 75 | def detect(im, cascade, haar_scale=1.2, min_neighbors=2, min_size=(20,20)): 76 | haar_flags = 0 77 | 78 | gray = image.rgb2gray(im) 79 | small = image.resize(gray, by_percent=im_scale) 80 | cv.EqualizeHist(small, small) 81 | objs = cv.HaarDetectObjects(small, cascade, cv.CreateMemStorage(0), 82 | haar_scale, min_neighbors, cv.CV_HAAR_DO_CANNY_PRUNING, min_size) 83 | return [Rect(r,n,cv.GetSize(im)) for r,n in objs] 84 | 85 | def draw_objects(im, objs, color=cv.RGB(255,0,0), thickness=1): 86 | for ((x, y, w, h), n) in objs: 87 | #print n 88 | if n < 4: continue 89 | pt1 = tuple(map(int, (x / im_scale, y / im_scale))) 90 | pt2 = tuple(map(int,((x+w) / im_scale, (y+h) / im_scale))) 91 | cv.Rectangle(im, pt1, pt2, color, thickness=thickness) 92 | return im 93 | 94 | class Rect(object): 95 | def __init__(self, (x, y, w, h), n=None, im_size=None): 96 | self.x, self.y, self.w, self.h = x, y, w, h 97 | self.n = n 98 | self.im_size = im_size 99 | 100 | def __contains__(self, rect): 101 | x1, x2 = self.x, self.x+self.w 102 | y1, y2 = self.y, self.y+self.h 103 | return x1 <= rect.x <= x2 and y1 <= rect.y <= y2 and \ 104 | x1 <= rect.x + rect.w <= x2 and y1 <= rect.y + rect.h <= y2 105 | 106 | def draw(self, im, color=(255,255,255), thickness=4): 107 | im_size = cv.GetSize(im) 108 | im2_size = self.im_size 109 | size_ratio = ( 110 | (im_size[0] / float(im2_size[0])) / im_scale, 111 | (im_size[1] / float(im2_size[1])) / im_scale, 112 | ) 113 | 114 | pt1 = int(self.x * size_ratio[0]), int(self.y * size_ratio[1]) 115 | pt2 = int((self.x+self.w) * size_ratio[0]), int((self.y+self.h) * size_ratio[1]) 116 | cv.Rectangle(im, pt1, pt2, color, thickness=thickness) 117 | return im 118 | 119 | def intersects_with(self, rect): 120 | return self.x <= rect.x <= self.x+self.w or self.y <= rect.y <= self.y+self.h or \ 121 | rect.x <= self.x <= rect.x+rect.w or rect.y <= self.y <= rect.y+rect.h 122 | 123 | def area(self): 124 | return self.w * self.h 125 | 126 | def __cmp__(self, rect): 127 | return cmp(self.area(), rect.area()) 128 | 129 | def __eq__(self, rect): 130 | return (self.x, self.y, self.w, self.h, self.n) == \ 131 | (rect.x, rect.y, rect.w, rect.h, rect.n) 132 | 133 | def __hash__(self): 134 | return hash((self.x, self.y, self.w, self.h)) 135 | 136 | def __repr__(self): 137 | return "Rect((%d, %d, %d, %d), %d)" % ( 138 | self.x, self.y, self.w, self.h, self.n, 139 | ) 140 | 141 | def intersected_area(self, rect): 142 | if not self.intersects_with(rect): 143 | return 0 144 | width = min(self.x + self.w, rect.x + rect.w) - max(self.x, rect.x) 145 | height = min(self.y + self.h, rect.y + rect.h) - max(self.y, rect.y) 146 | return width * height 147 | 148 | def to_tuple(self): 149 | return ((self.x, self.y, self.w, self.h), self.n) 150 | 151 | def __iter__(self): 152 | return iter(((self.x, self.y, self.w, self.h), self.n)) 153 | 154 | def filter_overlap(rects, overlap_ratio=0.75): 155 | "Filters overlapping objects as one. (Picks a random overlapped object as it)." 156 | to_remove = [] 157 | for i,r1 in enumerate(rects): 158 | for j,r2 in enumerate(rects): 159 | if i <= j: continue 160 | a = min(r1.area(), r2.area()) 161 | if r1.intersected_area(r2) > a * overlap_ratio: 162 | to_remove.append(max(r1, r2)) 163 | to_remove = set(to_remove) 164 | filtered_rects = [r for r in rects if r not in to_remove] 165 | counts = {} 166 | for r in filtered_rects: 167 | for r2 in rects: 168 | if r == r2: continue 169 | a = min(r1.area(), r2.area()) 170 | if r1.intersected_area(r2) > a * overlap_ratio: 171 | counts[r] = counts.get(r, 0) + 1 172 | return counts 173 | 174 | def boolean(faces): 175 | return faces < 1 176 | 177 | #cascade = cv.Load(face_cascades[-2]) 178 | 179 | requires_result_from = [] 180 | def measure(im, debug=False): 181 | im2 = image.max_size(im, (800, 600)) 182 | 183 | b,g,r = image.split(im2) 184 | #cv.EqualizeHist(r,r) 185 | ##cv.EqualizeHist(g,g) 186 | ##cv.EqualizeHist(b,b) 187 | im2 = image.merge(b,g,r) 188 | 189 | eyes = 0 190 | #objs = [] 191 | #for cascade in eye_cascades: 192 | # print cascade 193 | # cascade = cv.Load(cascade) 194 | # objs = filter_overlap(detect(im, cascade)) 195 | # draw_objects(im, objs, color=cv.RGB(0,255,0)) 196 | # eyes += len(objs) 197 | #faces = 0 198 | if debug: 199 | im3 = cv.CloneImage(im2) 200 | faces = [] 201 | for cascade in face_cascades:#(face_cascades[0],face_cascades[-1]): 202 | cascade = cv.Load(cascade) 203 | detected_faces = detect(im2, cascade) 204 | faces += detected_faces 205 | if debug: 206 | for i,rect in enumerate(faces): 207 | rect.draw(im3, color=cv.RGB(255,16*i,16*i)) 208 | if debug: 209 | image.show(im3, "Faces + Repeats") 210 | faces = filter_overlap(faces) 211 | #print (objs[1], objs[6]) 212 | #draw_objects(im2, map(tuple, faces.keys())) 213 | for rect, count in faces.iteritems(): 214 | rect.draw(im2, color=cv.RGB(255,0,0)) 215 | #print (objs[3],objs[13]) 216 | #draw_objects(im, filter_overlap((objs[3],objs[13]))) 217 | 218 | #objs = [] 219 | #for cascade in body_cascades: 220 | # print cascade 221 | # cascade = cv.Load(cascade) 222 | # objs += detect(im, cascade) 223 | #draw_objects(im, filter_overlap(objs), color=cv.RGB(0,0,255)) 224 | 225 | #objs = [] 226 | #for cascade in mouth_cascades: 227 | # print cascade 228 | # cascade = cv.Load(cascade) 229 | # objs += detect(im, cascade) 230 | #draw_objects(im, filter_overlap(objs), color=cv.RGB(255,0,255)) 231 | 232 | score = 0 233 | for face_rect, count in faces.iteritems(): 234 | score += count * 0.25 + 0.15 235 | print faces 236 | 237 | if debug: 238 | image.show(im2, "Faces") 239 | return (im2, faces), score -------------------------------------------------------------------------------- /features/noise.py: -------------------------------------------------------------------------------- 1 | import image 2 | import cv 3 | from numpy import array 4 | from grid import Grid 5 | 6 | class Noise(object): 7 | def __init__(self, mean, stddev): 8 | self.mean = mean 9 | self.stddev = stddev 10 | 11 | def __call__(self, im): 12 | size = cv.GetSize(im) 13 | #print size 14 | pixel = im[int(size[0]/2), int(size[1]/2)] 15 | mean, stddev = [x[0] for x in cv.AvgSdv(im)] 16 | #if (value < self.mean-self.stddev or value >= self.mean+self.stddev): 17 | #if stddev > self.stddev: 18 | if stddev > self.stddev: 19 | return 255 20 | return 0 21 | 22 | def count_neighbors(im): 23 | size = cv.GetSize(im) 24 | mx, my = int(size[0]/2), int(size[1]/2) 25 | pixel = im[mx, my] 26 | if pixel == 0: 27 | return 0 28 | c = cv.CountNonZero(im) 29 | return c 30 | 31 | def noise_per_grid(images): 32 | size = cv.GetSize(images[0]) 33 | total = float(size[0] * size[1]) 34 | scores = [] 35 | for im in images: 36 | scores.append(cv.CountNonZero(im) / total) 37 | return scores 38 | 39 | def boolean(score, threshold=0.13, diff_threshold=0.05): 40 | diff = score[1]-score[0] 41 | diff_mean, diff_std = score[2], score[3] 42 | uv_score, avg_std = score[4] 43 | 44 | #return uv_score > 0.90 or uv_score < 0.1 45 | 46 | #return (diff_std > 25) 47 | if (score[0] > threshold and score[1] > threshold and \ 48 | (abs(diff) > diff_threshold or diff < 0)): 49 | return uv_score > 0.9 and avg_std > 60 50 | return False 51 | 52 | requires_result_from = [] 53 | """ 54 | Too "clever", too slow 55 | 56 | def measure(im, debug=False): 57 | #im = image.random_cropped_region(im, (640, 480)) 58 | #l = image.laplace(im) 59 | l = image.sobel(im, xorder=2, yorder=2) 60 | #l = image.dilate(l) 61 | size = cv.GetSize(l) 62 | mean, stddev = map(lambda x: x[0], cv.AvgSdv(l)) 63 | n = Noise(mean, stddev) 64 | l = image.neighbor_map(l, n, nbr_size=3) 65 | edges = image.threshold(image.auto_edges(im, percentage=0.1), threshold=1) 66 | cv.Set(l, 0, edges) 67 | 68 | l = image.neighbor_map(l, count_neighbors, nbr_size=3) 69 | score = cv.Sum(l)[0] / float((size[0]-2) * (size[1]-2) * 9) 70 | 71 | if debug: 72 | image.show(edges, "Edges") 73 | image.show(image.threshold(l, threshold=1), "Final") 74 | image.show(image.sub(im, image.gray2rgb(l)), "Final Sub") 75 | 76 | return image.threshold(l, threshold=1), score 77 | """ 78 | 79 | 80 | def measure(im, debug=False): 81 | gray = image.rgb2gray(im) 82 | size = cv.GetSize(im) 83 | total = float(size[0] * size[1]) 84 | l = image.sub(gray, image.gaussian(gray, 5)) 85 | l2 = image.sub(gray, image.gaussian(gray, 9)) 86 | edges = image.dilate(image.auto_edges(im, percentage=0.2)) 87 | if debug: 88 | image.show(image.threshold(l, threshold=1), "Before Edge Removal (kernel=5)") 89 | image.show(image.threshold(l2, threshold=1), "Before Edge Removal (kernel=9)") 90 | cv.Set(l, 0, image.threshold(edges, threshold=1)) 91 | cv.Set(l2, 0, image.threshold(edges, threshold=1)) 92 | 93 | l = image.threshold(l, threshold=1) 94 | l2 = image.threshold(l2, threshold=1) 95 | 96 | 97 | 98 | if debug: 99 | image.show(image.threshold(edges, threshold=1), "Edges") 100 | image.show(l, "After Edge Removal (kernel=5)") 101 | image.show(l2, "After Edge Removal (kernel=9)") 102 | 103 | noise2 = image.new_from(gray) 104 | cv.EqualizeHist(gray, noise2) 105 | cv.AbsDiff(noise2, gray, noise2) 106 | cv.Set(noise2, 0, image.threshold(image.sobel(im, xorder=2, yorder=2), threshold=4)) 107 | diff = image.cv2array(noise2) 108 | if debug: 109 | image.show(noise2, "DIFF") 110 | print "M", diff.mean(), "S", diff.std() 111 | diff_stat = (diff.mean(), diff.std()) 112 | percent_noise = cv.CountNonZero(noise2) / total 113 | if debug: 114 | image.show(noise2, "NOISE2") 115 | 116 | 117 | 118 | # magical, I don't understand how this works 119 | _, sat, _ = image.split(image.rgb2hsv(im)) 120 | edges = image.auto_edges(im) 121 | l,u,v = tuple(map(image.equalize_hist, image.split(image.rgb2luv(im)))) 122 | u,v = tuple(map(image.gaussian, (u,v))) 123 | if debug: 124 | image.show(l, "1. L") 125 | image.show(u, "1. U") 126 | image.show(v, "1. V") 127 | la,ua,va,uva = tuple(map(image.cv2array, (l,u,v, image.And(l,u,v)))) 128 | test = image.new_from(gray) 129 | test2 = image.new_from(gray) 130 | cv.Xor(u,v,test) 131 | if debug: 132 | image.show(test, "2. U Xor V") 133 | cv.Set(test, 0, image.dilate(edges)) 134 | #cv.Set(test, 0, image.invert(image.threshold(sat, threshold=8))) 135 | uv_score = cv.CountNonZero(test) / total 136 | if debug: 137 | image.show(test, "3. U Xor V - dilate(Edges) - invert(threshold(Saturation))") 138 | 139 | g = Grid(size) 140 | images = map(image.cv2array, g.split_into(test, 6)) 141 | arr = image.cv2array(test) 142 | avg_mean, avg_std = arr.mean(), arr.std() 143 | 144 | 145 | #ms = [(a.mean(), a.std()) for a in images] 146 | #min_mean = min_std = 255 147 | #max_mean = max_std = 0 148 | #for m,s in ms: 149 | # min_mean = min(min_mean, m) 150 | # min_std = min(min_std, s) 151 | # max_mean = max(max_mean, m) 152 | # max_std = max(max_std, s) 153 | #if debug: 154 | # print min_mean, min_std 155 | # print avg_mean, avg_std 156 | # print max_mean, max_std 157 | # 158 | #score = uv_score, min_mean, avg_mean, avg_std, max_mean 159 | uv_score = uv_score, avg_std 160 | 161 | score = cv.CountNonZero(l) / total, cv.CountNonZero(l2) / total, \ 162 | diff_stat[0], diff_stat[1], uv_score 163 | 164 | return l, score 165 | 166 | 167 | def boolean((uv_score, avg_std)): 168 | return (uv_score > 0.9 or uv_score < 0.1) and avg_std >= 60 169 | 170 | 171 | def measure(im, debug=False): 172 | 173 | gray = image.rgb2gray(im) 174 | size = cv.GetSize(im) 175 | total = float(size[0] * size[1]) 176 | edges = image.auto_edges(im) 177 | 178 | _, sat, val = image.split(image.rgb2hsv(im)) 179 | edges = image.auto_edges(im) 180 | l,u,v = tuple(map(image.equalize_hist, image.split(image.rgb2luv(im)))) 181 | u,v = tuple(map(image.gaussian, (u,v))) 182 | if debug: 183 | image.show(l, "1. L") 184 | image.show(u, "1. U") 185 | image.show(v, "1. V") 186 | la,ua,va,uva = tuple(map(image.cv2array, (l,u,v, image.And(l,u,v)))) 187 | test = image.new_from(gray) 188 | test2 = image.new_from(gray) 189 | cv.Xor(u,v,test) 190 | #cv.AbsDiff(u,v, test2) 191 | if debug: 192 | #cv.Threshold(test, test, 32, 255, cv.CV_THRESH_BINARY) 193 | image.show(test, "2. U Xor V") 194 | #image.show(test2, "TEST 2") 195 | #test = image.dilate(test) 196 | cv.Set(test, 0, image.dilate(edges)) 197 | #cv.Set(test, 0, image.invert(image.threshold(sat, threshold=8))) 198 | uv_score = cv.CountNonZero(test) / total 199 | if debug: 200 | image.show(test, "3. U Xor V - dilate(Edges) - invert(threshold(Saturation))") 201 | 202 | arr = image.cv2array(test) 203 | avg_mean, avg_std = arr.mean(), arr.std() 204 | 205 | score = uv_score, avg_std 206 | 207 | return test, score 208 | 209 | ### NEW METHOD 210 | 211 | def boolean((mean, std, over)): 212 | # over <= 3 213 | n = 16 # grid size 214 | return over <= 0.05*(n*n) and mean > 70 215 | 216 | def measure(im, debug=False): 217 | gray = image.rgb2gray(im) 218 | size = cv.GetSize(im) 219 | total = float(size[0] * size[1]) 220 | edges = image.auto_edges(im) 221 | 222 | hue, sat, val = tuple(map(image.equalize_hist, image.split(image.rgb2hsv(im)) )) 223 | l,u,v = tuple(map(image.equalize_hist, image.split(image.rgb2luv(im)))) 224 | 225 | values = [] 226 | if debug: 227 | image.show(l, "L") 228 | image.show(val, "Value") 229 | sat = image.threshold(val,255-32)#image.And(val, sat) 230 | if debug: 231 | image.show(sat, "Thresh") 232 | #cv.And(val, l, val) 233 | cv.Sub(l, sat, l) 234 | cv.Set(l, 0, image.dilate(edges, iterations=3)) 235 | if debug: 236 | image.show(l, "L - Value") 237 | val = l 238 | g = Grid(cv.GetSize(val)) 239 | images = g.split_into(val, 16) 240 | arr = image.cv2array(val) 241 | avgmean, avgstd = arr.mean(), arr.std() 242 | for i in images: 243 | a = image.cv2array(i) 244 | mean, std = abs(a.mean() - avgmean), max(a.std(), 0) 245 | values.append((mean+std)) 246 | 247 | if debug: 248 | print values 249 | print "AVG", avgmean, avgstd 250 | image.show(val, "Result") 251 | 252 | return val, (avgmean, avgstd, len([v for v in values if v > avgstd*2])) -------------------------------------------------------------------------------- /grid.py: -------------------------------------------------------------------------------- 1 | import cv 2 | import image 3 | 4 | class Grid(object): 5 | def __init__(self, size): 6 | super(Grid, self).__init__() 7 | self.size = size 8 | 9 | def split_into(self, im, count): 10 | if type(count) not in (list, tuple): 11 | count = (count, count) 12 | box_w, box_h = self.size[0]/count[0], self.size[1]/count[1] 13 | images = [] 14 | for x in range(count[0]): 15 | for y in range(count[1]): 16 | box = (box_w * x, box_h * y, box_w-1, box_h-1) 17 | images.append(image.crop(im, box)) 18 | return images 19 | 20 | def draw_regions(self, im, color=cv.RGB(255,255,255), thickness=cv.CV_FILLED): 21 | t1, t2 = (self.size[0]/3, self.size[1]/3) 22 | b1, b2 = t1/3, t2/3 23 | 24 | cv.Rectangle(im, (t1-b1, t2-b2), (t1+b1, t2+b2), color, thickness) 25 | cv.Rectangle(im, (t1*2-b1, t2-b2), (t1*2+b1, t2+b2), color, thickness) 26 | cv.Rectangle(im, (t1-b1, t2*2-b2), (t1+b1, t2*2+b2), color, thickness) 27 | cv.Rectangle(im, (t1*2-b1, t2*2-b2), (t1*2+b1, t2*2+b2), color, thickness) 28 | return im 29 | 30 | def draw_lines(self, im, color=cv.RGB(255,255,255), thickness=5): 31 | t1, t2 = (self.size[0]/3, self.size[1]/3) 32 | b1, b2 = t1/3, t2/3 33 | cv.Line(im, (t1, 0), (t1, self.size[1]), color, thickness=thickness) 34 | cv.Line(im, (t1*2, 0), (t1*2, self.size[1]), color, thickness=thickness) 35 | cv.Line(im, (0, t2), (self.size[0], t2), color, thickness=thickness) 36 | cv.Line(im, (0, t2*2), (self.size[0], t2*2), color, thickness=thickness) 37 | 38 | return im 39 | 40 | def draw(self, im, color=cv.RGB(255,0,0), thickness=10): 41 | self.draw_lines(im, color, abs(thickness)) 42 | self.draw_regions(im, color, thickness) 43 | 44 | return im 45 | 46 | def split_in_four(self, im): 47 | half = self.size[0]/2, self.size[1]/2 48 | top_left = image.crop(im, (0, 0, half[0], half[1])) 49 | top_right = image.crop(im, (half[0], 0, half[0], half[1])) 50 | bottom_left = image.crop(im, (0, half[1], half[0], half[1])) 51 | bottom_right = image.crop(im, (half[0], half[1], half[0], half[1])) 52 | return top_left, top_right, bottom_left, bottom_right -------------------------------------------------------------------------------- /histogram.py: -------------------------------------------------------------------------------- 1 | import image 2 | import cv 3 | import numpy 4 | 5 | class GrayscaleHist(object): 6 | def __init__(self, bins=32, value_range=(0,255), scale=10): 7 | self.bins = bins 8 | self.value_range = value_range 9 | self.scale = scale 10 | 11 | self.kind = 'opencv' 12 | 13 | def use_image(self, im): 14 | self.kind = 'opencv' 15 | self.hist = cv.CreateHist([self.bins], cv.CV_HIST_ARRAY, [self.value_range], 1) 16 | cv.CalcHist([im], self.hist) 17 | self.min_value, self.max_value, _, _ = cv.GetMinMaxHistValue(self.hist) 18 | return self 19 | 20 | def use_array(self, arr): 21 | self.kind = 'numpy' 22 | if not isinstance(arr, numpy.ndarray): 23 | arr = numpy.array(list(arr)) 24 | self.hist, _ = numpy.histogram(arr, self.bins, self.value_range) 25 | self.min_value, self.max_value = min(self.hist), max(self.hist) 26 | return self 27 | 28 | def use_array_as_hist(self, arr): 29 | self.kind = 'numpy' 30 | if not isinstance(arr, numpy.ndarray): 31 | arr = numpy.array(list(arr)) 32 | self.hist = arr 33 | self.bin = len(self.hist) 34 | self.min_value, self.max_value = min(self.hist), max(self.hist) 35 | return self 36 | 37 | def mean(self): 38 | if self.kind == 'numpy': 39 | return self.hist.mean() 40 | return numpy.array(list(self)).mean() 41 | 42 | def stddev(self): 43 | if self.kind == 'numpy': 44 | return self.hist.std() 45 | return numpy.array(list(self)).std() 46 | 47 | def count(self, value): 48 | if isinstance(value, slice): 49 | return [self.count(i) for i in range(*value.indices(len(self)))] 50 | if self.kind == 'numpy': 51 | return self.hist[value] 52 | return cv.QueryHistValue_1D(self.hist, value) 53 | 54 | def intensity_at_bin(self, value): 55 | if isinstance(value, slice): 56 | return [self.intensity_at_bin(i) for i in range(*value.indices(len(self)))] 57 | 58 | if self.value_range: 59 | max_value = float(self.value_range[1]) 60 | else: 61 | max_value = float(self.max_value) 62 | 63 | return round(self.count(value) * max_value / float(max(self.max_value, 0.0001))) 64 | 65 | def index(self, intensity): 66 | l = list(self) 67 | return l.index(intensity) 68 | 69 | def __getitem__(self, bin_num): 70 | if isinstance(bin_num, slice): 71 | return [self[i] for i in range(*bin_num.indices(len(self)))] 72 | return self.intensity_at_bin(bin_num) 73 | 74 | def __len__(self): 75 | return self.bins 76 | 77 | def __iter__(self): 78 | for bin_num in range(self.bins): 79 | yield self.intensity_at_bin(bin_num) 80 | 81 | def to_array(self): 82 | return numpy.array(list(self)) 83 | 84 | def draw(self, im, color=cv.RGB(0,0,0), offset=(0,0)): 85 | height = cv.GetSize(im)[1] 86 | 87 | pad = 2 88 | 89 | if self.value_range: 90 | max_value = float(self.value_range[1]) 91 | else: 92 | max_value = float(self.max_value) 93 | 94 | for b in range(self.bins): 95 | value = self.intensity_at_bin(b) 96 | 97 | cv.Rectangle(im, 98 | (offset[0] + b*self.scale + pad, 99 | offset[1] + height-int(value/max_value * height)), 100 | (offset[0] + int((b+1)*self.scale - pad), 101 | offset[1] + height), 102 | color, cv.CV_FILLED) 103 | return im 104 | 105 | def to_img(self, height=200, color=cv.RGB(0,0,0), offset=(0,0), bg=(255,255,255)): 106 | im = cv.CreateImage((self.bins * self.scale, height), 8, 3) 107 | cv.Set(im, bg) 108 | return self.draw(im, color, offset=(0,0)) -------------------------------------------------------------------------------- /image.py: -------------------------------------------------------------------------------- 1 | import cv 2 | import os 3 | import math 4 | import random 5 | import numpy 6 | 7 | def rgba(r,g,b,a): 8 | v = cv.RGB(r,g,b) 9 | return v[:3] + (a,) 10 | 11 | def is_grayscale(im): 12 | if im.nChannels == 1: 13 | return True 14 | h,s,v = split(rgb2hsv(im)) 15 | if cv.CountNonZero(h) == 0 == cv.CountNonZero(s): 16 | return True 17 | return False 18 | 19 | def new_from(im, depth=None, nChannels=None, size=None): 20 | if depth is None: 21 | depth = im.depth 22 | if nChannels is None: 23 | nChannels = im.nChannels 24 | if size is None: 25 | size = cv.GetSize(im) 26 | 27 | return cv.CreateImage(size, depth, nChannels) 28 | 29 | def crop(im, rect): 30 | cropped = new_from(im, size=rect[2:]) 31 | cv.SetImageROI(im, tuple(rect)) 32 | cv.Copy(im, cropped) 33 | cv.ResetImageROI(im) 34 | return cropped 35 | 36 | def random_cropped_region(im, size): 37 | isize = cv.GetSize(im) 38 | s = (min(size[0], isize[0]), min(size[1], isize[1])) 39 | if s == isize: 40 | return im 41 | mx, my = isize[0] - s[0], isize[1] - s[1] 42 | x, y = random.randrange(mx), random.randrange(my) 43 | print x,y, isize, size 44 | print (x,y,x+size[0],y+size[1]) 45 | return crop(im, (x,y,size[0],size[1])) 46 | 47 | def op(cvFunc, im1, im2, *images): 48 | new_im = new_from(im1) 49 | cvFunc(im1, im2, new_im) 50 | for im in images: 51 | cvFunc(new_im, im, new_im) 52 | return new_im 53 | 54 | def add(im1, im2, *images): return op(cv.Add, im1, im2, *images) 55 | def sub(im1, im2, *images):return op(cv.Sub, im1, im2, *images) 56 | def multiply(im1, im2, *images): return op(cv.Mul, im1, im2, *images) 57 | def And(im1, im2, *images): return op(cv.And, im1, im2, *images) 58 | def Or(im1, im2, *images): return op(cv.Or, im1, im2, *images) 59 | def Xor(im1, im2, *images): return op(cv.Xor, im1, im2, *images) 60 | def absDiff(im1, im2, *images):return op(cv.AbsDiff, im1, im2, *images) 61 | 62 | def blend(im1, im2, alpha=0.5): 63 | new_im = new_from(im1) 64 | cv.AddWeighted(im1, alpha, im2, 1-alpha, 0.0, new_im) 65 | return new_im 66 | 67 | def gaussian(im, size=9): 68 | if type(size) in (int, float, long): 69 | size = (size, size) 70 | new_im = new_from(im) 71 | cv.Smooth(im, new_im, cv.CV_GAUSSIAN, size[0], size[1]) 72 | return new_im 73 | 74 | def erode(im, element=None, iterations=1): 75 | new_im = new_from(im) 76 | cv.Erode(im, new_im, element, iterations) 77 | return new_im 78 | 79 | def dilate(im, element=None, iterations=1): 80 | new_im = new_from(im) 81 | cv.Dilate(im, new_im, element, iterations) 82 | return new_im 83 | 84 | def resize(im, size=None, by_percent=None, method=cv.CV_INTER_LINEAR): 85 | assert size != None or by_percent != None 86 | if size is not None: 87 | im_size = cv.GetSize(im) 88 | size = list(size) 89 | if size[0] is None: 90 | size[0] = size[1] / float(im_size[1]) * im_size[0] 91 | if size[1] is None: 92 | size[1] = size[0] / float(im_size[0]) * im_size[1] 93 | 94 | if by_percent is not None: 95 | size = list(cv.GetSize(im)) 96 | if type(by_percent) in (list, tuple): 97 | size[0] *= by_percent[0] 98 | size[1] *= by_percent[1] 99 | else: 100 | size[0] *= by_percent 101 | size[1] *= by_percent 102 | size = (int(size[0]), int(size[1])) 103 | resized_im = new_from(im, size=size) 104 | cv.Resize(im, resized_im, method) 105 | return resized_im 106 | 107 | def max_size(im, size, method=cv.CV_INTER_LINEAR): 108 | im_size = cv.GetSize(im) 109 | if im_size[0] > size[0]: 110 | return resize(im, (size[0],None), method=method) 111 | if im_size[1] > size[1]: 112 | return resize(im, (None, size[1]), method=method) 113 | return im 114 | 115 | def load(path, grayscale=False, max_size=None): 116 | path = os.path.join('data', path) 117 | if not os.path.exists(path): 118 | raise TypeError, "File does not exist: "+repr(os.path.abspath(path)) 119 | 120 | if grayscale: 121 | return cv.LoadImage(path, cv.CV_LOAD_IMAGE_GRAYSCALE) 122 | return cv.LoadImage(path, cv.CV_LOAD_IMAGE_COLOR) 123 | 124 | def invert(im): 125 | inv = new_from(im) 126 | cv.SubRS(im, cv.ScalarAll(int('1' * im.depth, 2)), inv) 127 | return inv 128 | 129 | def threshold(im, threshold=128, type=cv.CV_THRESH_BINARY, max_value=255): 130 | if not is_grayscale(im): 131 | im = rgb2gray(im) 132 | new_im = new_from(im) 133 | cv.Threshold(im, new_im, threshold, max_value, type) 134 | return new_im 135 | 136 | def dft(im, flags,): 137 | new_im = new_from(im) 138 | 139 | def split(im): 140 | if im.nChannels == 3: 141 | imr = new_from(im, nChannels=1) 142 | img = new_from(im, nChannels=1) 143 | imb = new_from(im, nChannels=1) 144 | cv.Split(im, imr, img, imb, None) 145 | return (imr, img, imb) 146 | else: 147 | return (im,) 148 | 149 | def merge(im1, im2, im3, im4=None): 150 | assert 1 == im1.nChannels == im2.nChannels == im3.nChannels 151 | new_im = new_from(im1, nChannels=3) 152 | cv.Merge(im1, im2, im3, im4, new_im) 153 | return new_im 154 | 155 | def equalize_hist(im): 156 | new_im = new_from(im) 157 | cv.EqualizeHist(im, new_im) 158 | return new_im 159 | 160 | def rgb2hsv(im): 161 | hsv = new_from(im) 162 | cv.CvtColor(im, hsv, cv.CV_BGR2HSV) 163 | return hsv 164 | 165 | def hsv2rgb(im): 166 | rgb = new_from(im) 167 | cv.CvtColor(im, rgb, cv.CV_HSV2BGR) 168 | return rgb 169 | 170 | def rgb2luv(im): 171 | luv = new_from(im) 172 | cv.CvtColor(im, luv, cv.CV_BGR2Luv) 173 | return luv 174 | 175 | def luv2rgb(im): 176 | rgb = new_from(im) 177 | cv.CvtColor(im, rgb, cv.CV_Luv2BGR) 178 | return rgb 179 | 180 | def rgb2gray(im): 181 | if im.nChannels == 1: 182 | return im 183 | gray = new_from(im, cv.IPL_DEPTH_8U, nChannels=1) 184 | cv.CvtColor(im, gray, cv.CV_RGB2GRAY) 185 | return gray 186 | 187 | def gray2rgb(im): 188 | if im.nChannels==3: 189 | return im 190 | color = new_from(im, cv.IPL_DEPTH_8U, nChannels=3) 191 | cv.CvtColor(im, color, cv.CV_GRAY2RGB) 192 | return color 193 | 194 | def laplace(im): 195 | im = rgb2gray(im) 196 | new_im = new_from(im, depth=cv.IPL_DEPTH_16S) 197 | cv.Laplace(im, new_im) 198 | cv.ConvertScaleAbs(new_im, im) 199 | return im 200 | 201 | def edges(im, threshold1=50, threshold2=150, aperture_size=3): 202 | edges = new_from(im, cv.IPL_DEPTH_8U, 1) 203 | gray = rgb2gray(im) 204 | cv.Canny(gray, edges, threshold1, threshold2, aperture_size) 205 | return edges 206 | 207 | def auto_edges(im, starting_threshold=50, percentage=0.2): 208 | size = cv.GetSize(im) 209 | total = size[0] * size[1] * percentage 210 | e = edges(im, starting_threshold, starting_threshold*3) 211 | while cv.CountNonZero(e) > total: 212 | starting_threshold += 10 213 | e = edges(im, starting_threshold, starting_threshold*3) 214 | return e 215 | 216 | def corners(im, max_corners=100, quality=0.1, min_dist=5, block_size=3, use_harris=False, 217 | mask=None, k=0.04): 218 | eig = new_from(im, depth=cv.IPL_DEPTH_32F, nChannels=1) 219 | tmp = new_from(im, depth=cv.IPL_DEPTH_32F, nChannels=1) 220 | gray = rgb2gray(im) 221 | corners = cv.GoodFeaturesToTrack(gray, eig, tmp, max_corners, quality, min_dist, 222 | mask, block_size, use_harris, k) 223 | #cv.Scale(eig, eig, 100, 0.00) 224 | return corners 225 | 226 | size = [10, 20] 227 | 228 | def show(im, title="Image"): 229 | global size # I know, bad practice... 230 | cv.NamedWindow(title) 231 | im2 = max_size(im, (640, 480)) 232 | cv.ShowImage(title, im2) 233 | cv.MoveWindow(title, size[0], size[1]) 234 | size[0] += 20 235 | size[1] += 20 236 | 237 | def sobel(im, xorder=1, yorder=1): 238 | recommended_size = { 239 | 1: 3, 2: 3, 3: 5, 240 | 4: 5, 5:7, 6: 7, 7:9, 8:9 241 | } 242 | im = rgb2gray(im) 243 | new_im = cv.CreateImage(cv.GetSize(im), cv.IPL_DEPTH_16S, 1) 244 | cv.Sobel(im, new_im, xorder, yorder, 245 | apertureSize=recommended_size[max(xorder, yorder)]) 246 | cv.ConvertScaleAbs(new_im, im) 247 | return im 248 | 249 | def save(im, path): 250 | #root = os.path.join('output') 251 | root = os.path.dirname(path) 252 | try: 253 | os.makedirs(root) 254 | except OSError: 255 | pass 256 | path = os.path.join(root, path) 257 | cv.SaveImage(path, im) 258 | 259 | def set_pixel(im, x, y, rgb): 260 | if type(rgb) in (tuple, list) and len(rgb) == 3: 261 | rgb = (rgb[2], rgb[1], rgb[0]) 262 | cv.Set2D(im, y, x, rgb) 263 | 264 | def neighbor_map(im, func, nbr_size=3): 265 | oim = new_from(im) 266 | size = cv.GetSize(oim) 267 | nbr_size = int(nbr_size / 2) 268 | size = tuple((x - nbr_size for x in size)) 269 | for x in xrange(nbr_size, size[0]): 270 | for y in xrange(nbr_size, size[1]): 271 | value = func(im[y-nbr_size:y+nbr_size+1, x-nbr_size:x+nbr_size+1]) 272 | set_pixel(oim, x, y, value) 273 | return oim 274 | 275 | def draw_points(im, points, radius=2, color=cv.RGB(255,0,0), thickness=1): 276 | for pt in points: 277 | cv.Circle(im, tuple(map(int, pt)), radius, color, thickness) 278 | return im 279 | 280 | 281 | def cv2array(im): 282 | depth2dtype = { 283 | cv.IPL_DEPTH_8U: 'uint8', 284 | cv.IPL_DEPTH_8S: 'int8', 285 | cv.IPL_DEPTH_16U: 'uint16', 286 | cv.IPL_DEPTH_16S: 'int16', 287 | cv.IPL_DEPTH_32S: 'int32', 288 | cv.IPL_DEPTH_32F: 'float32', 289 | cv.IPL_DEPTH_64F: 'float64', 290 | } 291 | 292 | arrdtype=im.depth 293 | a = numpy.fromstring( 294 | im.tostring(), 295 | dtype=depth2dtype[im.depth], 296 | count=im.width*im.height*im.nChannels) 297 | a.shape = (im.height,im.width,im.nChannels) 298 | return a 299 | 300 | 301 | def array2cv(a): 302 | dtype2depth = { 303 | 'uint8': cv.IPL_DEPTH_8U, 304 | 'int8': cv.IPL_DEPTH_8S, 305 | 'uint16': cv.IPL_DEPTH_16U, 306 | 'int16': cv.IPL_DEPTH_16S, 307 | 'int32': cv.IPL_DEPTH_32S, 308 | 'float32': cv.IPL_DEPTH_32F, 309 | 'float64': cv.IPL_DEPTH_64F, 310 | } 311 | try: 312 | nChannels = a.shape[2] 313 | except: 314 | nChannels = 1 315 | cv_im = cv.CreateImageHeader((a.shape[1],a.shape[0]), 316 | dtype2depth[str(a.dtype)], 317 | nChannels) 318 | cv.SetData(cv_im, a.tostring(), 319 | a.dtype.itemsize*nChannels*a.shape[1]) 320 | return cv_im -------------------------------------------------------------------------------- /logger.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | import image 3 | 4 | class FileLogger(object): 5 | def __init__(self, handle=None): 6 | if handle is None: 7 | handle = sys.stdout 8 | self.handle = handle 9 | 10 | def write(self, *message, **kwargs): 11 | self.handle.write(' '.join(map(str, message)) % kwargs) 12 | 13 | def writeln(self, *message, **kwargs): 14 | self.write(*message, **kwargs) 15 | self.handle.write("\n") 16 | 17 | def result(self, image_path, results, expected, compare=True): 18 | self.writeln(image_path + ":") 19 | for name,result in results.iteritems(): 20 | self.writeln("\t%(e)s%(name)s => %(score)r %(b)s (%(sec)s secs)", 21 | name=name, score=result[1], 22 | b="Yes" if result[2] else "No", 23 | e="* " if compare and name in expected else "", 24 | sec=result[3]) 25 | 26 | def close(self): 27 | self.handle.close() 28 | 29 | class HtmlLogger(FileLogger): 30 | def __init__(self, handle=None, resized_dir=None): 31 | super(HtmlLogger, self).__init__(handle) 32 | self.resized_dir = resized_dir 33 | self.write("""
Image | 44 |Metric | 45 |Score | 46 |Result? | 47 |Expected? | 48 |Compute Time | 49 |
---|---|---|---|---|---|
%(imgpath)s | ',
62 | fullpath=fullpath, path=path, imgpath=image_path, len=len(results))
63 | for i,pair in enumerate(results.iteritems()):
64 | name,result = pair
65 | if i != 0:
66 | self.write("|||||
%(name)s | %(score)r | ', 68 | name=name, score=result[1]) 69 | self.write('%(confirmed)s | %(expected)s | ', 70 | cls="bad" if compare and result[2] != (name in expected) else "good", 71 | confirmed="Yes" if result[2] else "", 72 | expected="Yes" if name in expected else "") 73 | self.write('%(timing).2f second(s) | ', 74 | cls="bad" if compare and result[3] > 10 else "", 75 | timing=result[3]) 76 | self.write("