OLD | NEW |
---|---|
(Empty) | |
1 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 """A Module containing useful image tools for use by I Spy.""" | |
craigdh
2013/06/13 19:28:31
"""Utilities for performing pixel-by-pixel image c
cgrimm
2013/06/14 18:43:58
Done.
| |
6 | |
7 __author__ = 'cgrimm@google.com (Chris Grimm)' | |
craigdh
2013/06/13 19:28:31
We don't do this in chromium.
cgrimm
2013/06/14 18:43:58
Done.
| |
8 | |
9 from itertools import izip | |
craigdh
2013/06/13 19:28:31
Prefer importing the module and then calling modul
cgrimm
2013/06/14 18:43:58
Done.
| |
10 from PIL import Image | |
craigdh
2013/06/13 19:28:31
ditto
cgrimm
2013/06/14 18:43:58
Done.
| |
11 | |
12 | |
13 class Error(Exception): | |
craigdh
2013/06/13 19:28:31
This adds little additional meaning. Just use Exce
cgrimm
2013/06/14 18:43:58
Done.
| |
14 pass | |
15 | |
16 | |
17 def AreTheSameSize(*images): | |
craigdh
2013/06/13 19:28:31
Just pass the list by value. The contents of the l
cgrimm
2013/06/14 18:43:58
Done.
| |
18 """Returns if a set of images are the same size. | |
19 | |
20 Args: | |
21 *images: a list of images to compare | |
22 Returns: | |
23 boolean | |
24 Raises: | |
25 Error: One image or fewer is passed in. | |
26 """ | |
27 if len(images) > 1: | |
28 i1 = images[0] | |
29 for i2 in images: | |
30 if i1.size == i2.size: | |
craigdh
2013/06/13 19:28:31
No need for the pass, just do:
if i1.size != i2.s
cgrimm
2013/06/14 18:43:58
Done.
| |
31 pass | |
32 else: | |
33 return False | |
34 return True | |
craigdh
2013/06/13 19:28:31
Actually, you can probably do this whole function
cgrimm
2013/06/14 18:43:58
Done.
| |
35 else: | |
36 raise Error('No images passed in') | |
37 | |
38 | |
39 def GetColorDist(px1, px2): | |
craigdh
2013/06/13 19:28:31
Functions local to the module should be prefixed w
cgrimm
2013/06/14 18:43:58
Done.
| |
40 """Returns the normalized color distance between pixels. | |
41 | |
42 This function gets the color distance between two pixels and | |
43 returns a number between 0 and 1 where 0 is lowest distance | |
44 and 1 is the greatest distance. | |
45 | |
46 Args: | |
47 px1: a 3-tuple (R,G,B) | |
48 px2: a 3-tuple (R,G,B) | |
49 Returns: | |
50 a number between 0 and 1 | |
51 """ | |
52 dist = 0. | |
53 for i in range(3): | |
54 dist += (px1[i]-px2[i])**2 | |
craigdh
2013/06/13 19:28:31
Use sum() for efficiency as this is in the critica
cgrimm
2013/06/14 18:43:58
Done.
| |
55 return dist/(3*255**2) | |
craigdh
2013/06/13 19:28:31
Does python calculate this constant every time? I'
cgrimm
2013/06/14 18:43:58
Done.
| |
56 | |
57 | |
58 def GetColorDistAsColor(px1, px2): | |
59 """Computes Color Distance as a greyscale pixel. | |
60 | |
61 This function gets the color distance between two pixels | |
62 represented as a pixel ranging in color from (0,0,0) to | |
63 (255,255,255). | |
64 | |
65 Args: | |
66 px1: a 3-tuple (R,G,B) | |
67 px2: a 3-tuple (R,G,B) | |
68 Returns: | |
69 a 3-tuple between (0,0,0) and (255,255,255) | |
70 """ | |
71 n = GetColorDist(px1, px2) | |
72 return (int(255*n), int(255*n), int(255*n)) | |
73 | |
74 | |
75 def Brightness(px): | |
76 """gets the brightness of a pixel. | |
craigdh
2013/06/13 19:28:31
g -> G
cgrimm
2013/06/14 18:43:58
Done.
| |
77 | |
78 returns the sum of a given pixel's color channels. | |
craigdh
2013/06/13 19:28:31
r -> R
cgrimm
2013/06/14 18:43:58
Done.
| |
79 | |
80 Args: | |
81 px: a 3-tuple (R,G,B) | |
82 Returns: | |
83 a number between 0 and 3*255 | |
84 """ | |
85 return px[0]+px[1]+px[2] | |
craigdh
2013/06/13 19:28:31
Just sum(px)
cgrimm
2013/06/14 18:43:58
Done.
| |
86 | |
87 | |
88 def MinPixel(px1, px2): | |
89 """gets the pixel with the lower brightness. | |
90 | |
91 Calculates the brightness of each pixel and returns | |
92 the pixel with the lower brightness. | |
93 | |
94 Args: | |
95 px1: a 3-tuple (R,G,B) | |
96 px2: a 3-tuple (R,G,B) | |
97 Returns: | |
98 a 3-tuple (R,G,B) | |
99 """ | |
100 average_px1 = Brightness(px1) | |
craigdh
2013/06/13 19:28:31
no need for the intermediate variables here.
cgrimm
2013/06/14 18:43:58
Done.
| |
101 average_px2 = Brightness(px2) | |
102 if average_px1 < average_px2: | |
103 return px1 | |
104 else: | |
105 return px2 | |
106 | |
107 | |
108 def MaxPixel(px1, px2): | |
109 """gets the pixel with the greater brightness. | |
110 | |
111 Calculates the brightness of each pixel and returns | |
112 the pixel with the greater brightness. | |
113 | |
114 Args: | |
115 px1: a 3-tuple (R,G,B) | |
116 px2: a 3-tuple (R,G,B) | |
117 Returns: | |
118 a 3-tuple (R,G,B) | |
119 """ | |
120 average_px1 = Brightness(px1) | |
craigdh
2013/06/13 19:28:31
ditto
cgrimm
2013/06/14 18:43:58
Done.
| |
121 average_px2 = Brightness(px2) | |
122 if average_px1 < average_px2: | |
123 return px2 | |
124 else: | |
125 return px1 | |
126 | |
127 | |
128 def CreateMask(is_hard, cutoff, *images): | |
craigdh
2013/06/13 19:28:31
No need for the *, as mentioned earlier.
cgrimm
2013/06/14 18:43:58
Done.
| |
129 """Computes a mask for a set of images. | |
130 | |
131 Returns an image that is computed from the images | |
132 passed in. The mask's values are computed by calculating | |
133 total differences between the image, and storing them | |
134 such that (255,255,255) represents great difference, | |
135 and (0,0,0) represents no difference. If is_hard is | |
136 True, the resulting mask is put through a | |
137 thresholding pass where pixel values below the | |
138 cutoff become (0,0,0) and ones above become (255,255,255). | |
139 | |
140 Args: | |
141 is_hard: boolean, whether or not to threshold the mask | |
craigdh
2013/06/13 19:28:31
is_hard -> threshold
Also, I would pass images fi
cgrimm
2013/06/14 18:43:58
Done.
| |
142 cutoff: number, the value to threshold the mask at | |
143 *images: the images to compute the mask from | |
144 Returns: | |
145 an image that is a mask of the passed in images. | |
146 Raises: | |
147 Error: if the images passed in are not of the same size | |
148 """ | |
149 if AreTheSameSize(*images): | |
craigdh
2013/06/13 19:28:31
Instead of indenting the whole function, just do t
cgrimm
2013/06/14 18:43:58
Done.
| |
150 mask = Image.new('RGB', images[0].size) | |
151 mask_data = list(mask.getdata()) | |
craigdh
2013/06/13 19:28:31
The data structure returned is already iterable, i
cgrimm
2013/06/14 18:43:58
Done.
| |
152 data_set = [list(x.getdata()) for x in images] | |
craigdh
2013/06/13 19:28:31
data_set is unnecessary. Just pass images[0].getda
cgrimm
2013/06/14 18:43:58
Done.
| |
153 i1 = data_set[0] | |
craigdh
2013/06/13 19:28:31
more descriptive variable names than i1, i2
cgrimm
2013/06/14 18:43:58
Done.
| |
154 for i2 in data_set[1:]: | |
155 mask_data = ComputeLargestDifference(mask_data, i1, i2) | |
156 | |
157 if is_hard: | |
158 mask_data = [ | |
159 (255, 255, 255) if Brightness(x) > cutoff | |
160 else (0, 0, 0) | |
161 for x in mask_data | |
162 ] | |
craigdh
2013/06/13 19:28:31
Instead of constructing a list, just create an ite
cgrimm
2013/06/14 18:43:58
putdata requires a sequence not an iterator.
| |
163 | |
164 mask.putdata(mask_data) | |
165 return mask | |
166 else: | |
167 raise Error('All Images must be the same size') | |
168 | |
169 | |
170 def ComputeLargestDifference(mask_data, image_data1, image_data2): | |
171 """Modifies a mask based upon a comparision of two images. | |
172 | |
173 A helper function used to generate masks. The mask, as | |
174 it exists when the function is called, is compared to two | |
175 images on a pixel-by-pixel basis. For each pixel, the | |
176 ColorDistance between the two images is computed as a Color, | |
177 and compared against the color of the mask at the same | |
178 pixel. The brightest of these pixels is chosen for pixel | |
179 in the images/mask, and a new set of pixels is returned. | |
180 | |
181 Args: | |
182 mask_data: a list of pixels of the mask | |
183 image_data1: a list of pixels of an image | |
184 image_data2: a list of pixels of another image | |
185 Returns: | |
186 a list of pixels representing a modified version of the mask. | |
187 """ | |
188 return [ | |
craigdh
2013/06/13 19:28:31
Should be able to create an iterator instead of a
cgrimm
2013/06/14 18:43:58
Done.
| |
189 MaxPixel(m, GetColorDistAsColor(i1, i2)) | |
190 for m, i1, i2 in izip(mask_data, image_data1, image_data2) | |
191 ] | |
craigdh
2013/06/13 19:28:31
Also, this indentation doesn't look like chromium
cgrimm
2013/06/14 18:43:58
Done.
| |
192 | |
193 | |
194 def ThresholdColorDiff(px1, px2, cutoff): | |
195 """Thresholds the color distance of two pixels. | |
196 | |
197 Computes a Threshold of the color distance of two pixels, | |
198 if the normalized color distance of px1 and px2 is greater than | |
199 cutoff, returns 1. otherwise returns 0. | |
200 | |
201 Args: | |
202 px1: a 3-tuple (R,G,B) | |
203 px2: a 3-tuple (R,G,B) | |
204 cutoff: a number used as the threshold limit | |
205 Returns: | |
206 either 1. or 0. depending on the distance of px1 and px2. | |
207 """ | |
208 diff = GetColorDist(px1, px2) | |
209 if diff > cutoff: | |
210 return 1. | |
211 else: | |
212 return 0. | |
213 | |
214 | |
215 def MaskToValue(px): | |
216 """Converts a black or white pixel to 1. or 0. | |
217 | |
218 Args: | |
219 px: a 3-tuple (R,G,B) | |
220 Returns: | |
221 1. if the pixel is (0,0,0), 0. if the pixel is (255,255,255) | |
222 Raises: | |
223 Error: if the pixel is not (0,0,0) or (255,255,255) | |
224 """ | |
225 if px == (0, 0, 0): | |
226 return 1. | |
227 if px == (255, 255, 255): | |
228 return 0. | |
229 else: | |
230 raise Error('Mask may only contain black or white pixels') | |
231 | |
232 | |
233 def Similarity(image1, image2, mask=None): | |
234 """computes the similarity of two images. | |
235 | |
236 Computes the similarity of two images taking into account | |
237 an optional mask. the similarity is returned as a number | |
238 between 0 and 1, where 1 is the same image, and 0 is a | |
craigdh
2013/06/13 19:28:31
We're doing pixel-by-pixel comparisons, so this wo
cgrimm
2013/06/14 18:43:58
Done.
| |
239 dramatically different image. | |
240 | |
241 Args: | |
242 image1: the first Image to be compared | |
243 image2: the second Image to be compared | |
244 mask: an optional mask to occlude parts of the images | |
245 from calculation | |
246 Returns: | |
247 a number representing the similarity of the images. | |
248 Raises: | |
249 Error: if the images to be compared and mask are not the same size. | |
250 """ | |
251 if mask: | |
252 if AreTheSameSize(image1, image2, mask): | |
253 return 1. - sum([ | |
254 MaskToValue(m)*ThresholdColorDiff(px1, px2, 0.001) | |
255 for m, px1, px2 in izip(mask.getdata(), | |
256 image1.getdata(), | |
257 image2.getdata()) | |
258 ]) / (image1.size[0]*image1.size[1]) | |
259 else: | |
260 raise Error('images and mask must be the same size') | |
261 else: | |
262 if AreTheSameSize(image1, image2): | |
263 return 1. - sum([ | |
264 ThresholdColorDiff(px1, px2, 0.001) | |
265 for px1, px2 in izip(image1.getdata(), image2.getdata()) | |
266 ]) / (image1.size[0]*image1.size[1]) | |
267 else: | |
268 raise Error('images must be the same size') | |
269 | |
270 | |
271 def SameImage(image1, image2, certainty, mask=None): | |
272 """Returns a boolean representing if the images are the Same. | |
273 | |
274 Returns a boolean relating to whether or not two images | |
275 are similar enough to be considered the same. Essentially | |
276 wraps the Similarity function and adds a certainty | |
277 parameter: a number between 0 and 1 that represents the | |
craigdh
2013/06/13 19:28:31
As above, take a difference in pixels for the cuto
cgrimm
2013/06/14 18:43:58
Done.
| |
278 cutoff in Similarity for Sameness. | |
279 | |
280 Args: | |
281 image1: an Image to compare | |
282 image2: an Image to compare | |
283 certainty: a number that is the cutoff for image sameness | |
284 mask: an optional Image that occludes parts of the images from | |
285 same-ness calculation | |
286 Returns: | |
287 a boolean representing if the images are the same. | |
288 Raises: | |
289 Error: if the images (and mask) are different sizes. | |
290 """ | |
291 similar = Similarity(image1, image2, mask) | |
292 if similar >= certainty: | |
craigdh
2013/06/13 19:28:31
just:
return similar >= certainty
cgrimm
2013/06/14 18:43:58
Done.
| |
293 return True | |
294 else: | |
295 return False | |
OLD | NEW |