Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(137)

Side by Side Diff: chrome/test/functional/ispy/image_tools.py

Issue 16855010: Python Tools for Pixel-by-Pixel Image Comparison (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: spaces between operators, various nitpicks fixed Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | chrome/test/functional/ispy/image_tools_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 """Utilities for performing pixel-by-pixel image comparison."""
6
7
8 import itertools
9 import math
10 import PIL
11
12
13 def _AreTheSameSize(images):
14 """Returns whether a set of images are the same size.
15
16 Args:
17 images: a list of images to compare
18
19 Returns:
20 boolean
21
22 Raises:
23 Exception: One image or fewer is passed in.
24 """
25 if len(images) > 1:
26 return not any(images[0].size != img.size for img in images[1:])
27 else:
28 raise Exception('No images passed in')
29
30
31 def _GetColorDist(px1, px2):
32 """Returns the normalized color distance between pixels.
33
34 This function gets the color distance between two pixels and
35 returns a number between 0 and 1 where 0 is lowest distance
36 and 1 is the greatest distance.
37
38 Args:
39 px1: a 3-tuple (R,G,B)
40 px2: a 3-tuple (R,G,B)
41
42 Returns:
43 a number between 0 and 1
44 """
45 return sum(
46 (ch1 - ch2) ** 2
47 for ch1, ch2 in itertools.izip(px1[0:3], px2[0:3])
48 ) / 195075. # 3*255**2
49
50
51 def _GetColorDistAsColor(px1, px2):
52 """Computes Color Distance as a greyscale pixel.
53
54 This function gets the color distance between two pixels
55 represented as a pixel ranging in color from (0,0,0) to
56 (255,255,255).
57
58 Args:
59 px1: a 3-tuple (R,G,B)
60 px2: a 3-tuple (R,G,B)
61
62 Returns:
63 a 3-tuple between (0,0,0) and (255,255,255)
64 """
65 n = _GetColorDist(px1, px2)
66 return (int(math.ceil(255 * n)),
67 int(math.ceil(255 * n)),
68 int(math.ceil(255 * n)))
69
70
71 def _Brightness(px):
72 """Gets the brightness of a pixel.
73
74 Returns the sum of a given pixel's color channels.
75
76 Args:
77 px: a 3-tuple (R,G,B)
78
79 Returns:
80 a number between 0 and 3*255
81 """
82 return sum(px)
83
84
85 def _MinPixel(px1, px2):
86 """Gets the pixel with the lower brightness.
87
88 Calculates the brightness of each pixel and returns
89 the pixel with the lower brightness.
90
91 Args:
92 px1: a 3-tuple (R,G,B)
93 px2: a 3-tuple (R,G,B)
94
95 Returns:
96 a 3-tuple (R,G,B)
97 """
98 if _Brightness(px1) < _Brightness(px2):
99 return px1
100 else:
101 return px2
102
103
104 def _MaxPixel(px1, px2):
105 """gets the pixel with the greater brightness.
106
107 Calculates the brightness of each pixel and returns
108 the pixel with the greater brightness.
109
110 Args:
111 px1: a 3-tuple (R,G,B)
112 px2: a 3-tuple (R,G,B)
113
114 Returns:
115 a 3-tuple (R,G,B)
116 """
117 if _Brightness(px1) < _Brightness(px2):
118 return px2
119 else:
120 return px1
121
122
123 def CreateMask(images, threshold=True, cutoff=0):
124 """Computes a mask for a set of images.
125
126 Returns an image that is computed from the images
127 passed in. The mask's values are computed by calculating
128 total differences between the image, and storing them
129 such that (255,255,255) represents great difference,
130 and (0,0,0) represents no difference. If is_hard is
131 True, the resulting mask is put through a
132 thresholding pass where pixel values below the
133 cutoff become (0,0,0) and ones above become (255,255,255).
134
135 Args:
136 images: the images to compute the mask from
137 threshold: boolean, whether or not to threshold the mask
138 cutoff: number, the value to threshold the mask at
139
140 Returns:
141 an image that is a mask of the passed in images.
142
143 Raises:
144 Exception: if the images passed in are not of the same size
145 """
146 if not _AreTheSameSize(images):
147 raise Exception('All images must be the same size')
148 mask = PIL.Image.new('RGB', images[0].size)
149 mask_data = mask.getdata()
150 image_data = images[0].getdata()
151 for other_image in images[1:]:
152 mask_data = _ComputeLargestDifference(mask_data, image_data,
153 other_image.getdata())
154
155 if threshold:
156 mask.putdata([
157 (255, 255, 255) if _Brightness(px) > cutoff
158 else (0, 0, 0)
159 for px in mask_data
160 ])
161 else:
162 mask.putdata(list(mask_data))
163 return mask
164
165
166 def _ComputeLargestDifference(mask_data, image_data1, image_data2):
167 """Modifies a mask based upon a comparision of two images.
168
169 A helper function used to generate masks. The mask, as
170 it exists when the function is called, is compared to two
171 images on a pixel-by-pixel basis. For each pixel, the
172 ColorDistance between the two images is computed as a Color,
173 and compared against the color of the mask at the same
174 pixel. The brightest of these pixels is chosen for pixel
175 in the images/mask, and a new set of pixels is returned.
176
177 Args:
178 mask_data: a list of pixels of the mask
179 image_data1: a list of pixels of an image
180 image_data2: a list of pixels of another image
181
182 Returns:
183 a list of pixels representing a modified version of the mask.
184 """
185 return (
186 _MaxPixel(m, _GetColorDistAsColor(i1, i2))
187 for m, i1, i2 in itertools.izip(mask_data, image_data1, image_data2)
188 )
189
190
191 def _ThresholdColorDiff(px1, px2, cutoff):
192 """Thresholds the color distance of two pixels.
193
194 Computes a Threshold of the color distance of two pixels,
195 if the normalized color distance of px1 and px2 is greater than
196 cutoff, returns 1. otherwise returns 0.
197
198 Args:
199 px1: a 3-tuple (R,G,B)
200 px2: a 3-tuple (R,G,B)
201 cutoff: a number used as the threshold limit
202
203 Returns:
204 either 1. or 0. depending on the distance of px1 and px2.
205 """
206 diff = _GetColorDist(px1, px2)
207 if diff > cutoff:
208 return 1
209 else:
210 return 0
211
212
213 def _MaskToValue(px):
214 """Converts a black or white pixel to 1. or 0.
215
216 Args:
217 px: a 3-tuple (R,G,B)
218
219 Returns:
220 1. if the pixel is (0,0,0), 0. if the pixel is (255,255,255)
221
222 Raises:
223 Exception: if the pixel is not (0,0,0) or (255,255,255)
224 """
225 if px[0:3] == (0, 0, 0):
226 return 1.
227 if px[0:3] == (255, 255, 255):
228 return 0.
229 else:
230 raise Exception('Mask may only contain black or white pixels')
231
232
233 def TotalDifferentPixels(image1, image2, mask=None):
234 """Computes the number of different pixels between two images.
235
236 Args:
237 image1: the first Image to be compared
238 image2: the second Image to be compared
239 mask: an optional mask to occlude parts of the images
240 from calculation
241
242 Returns:
243 the number of differing pixels between the images.
244
245 Raises:
246 Exception: if the images to be compared and mask are not the same size.
247 """
248 if mask:
249 if _AreTheSameSize([image1, image2, mask]):
250 return sum(
251 _MaskToValue(m) * _ThresholdColorDiff(px1, px2, 0)
252 for m, px1, px2 in itertools.izip(mask.getdata(),
253 image1.getdata(),
254 image2.getdata())
255 )
256 else:
257 raise Exception('images and mask must be the same size')
258 else:
259 if _AreTheSameSize([image1, image2]):
260 return sum(
261 _ThresholdColorDiff(px1, px2, 0)
262 for px1, px2 in itertools.izip(
263 image1.getdata(),
264 image2.getdata()
265 )
266 )
267 else:
268 raise Exception('images and mask must be the same size')
269
270
271 def SameImage(image1, image2, max_different_pixels=0, mask=None):
272 """Returns a boolean representing whether the images are the same.
273
274 Returns a boolean indicating whether two images
275 are similar enough to be considered the same. Essentially
276 wraps the Similarity function and adds a max_different_pixels
277 cutoff for Sameness.
frankf 2013/06/17 21:48:31 I don't thinkg Sameness has defined meaning. "Simi
cgrimm 2013/06/17 23:41:18 Done.
278
279 Args:
280 image1: an Image to compare
frankf 2013/06/17 21:48:31 Document how this is encoded
cgrimm 2013/06/17 23:41:18 Done.
281 image2: an Image to compare
282 max_different_pixels: a number that is the cutoff for image sameness
frankf 2013/06/17 21:48:31 Once you add this threshold, then this brute force
cgrimm 2013/06/17 23:41:18 resolved
283 mask: an optional Image that occludes parts of the images from
284 same-ness calculation
285
286 Returns:
287 a boolean representing if the images are the same.
frankf 2013/06/17 21:48:31 "True if the images are similar, false otherwise"
cgrimm 2013/06/17 23:41:18 Done.
288
289 Raises:
290 Error: if the images (and mask) are different sizes.
frankf 2013/06/17 21:48:31 Is Error a type of exception?
cgrimm 2013/06/17 23:41:18 This is a remnant from when I had a subclass of Ex
291 """
292
293 different_pixels = TotalDifferentPixels(image1, image2, mask)
294 return different_pixels <= max_different_pixels
OLDNEW
« no previous file with comments | « no previous file | chrome/test/functional/ispy/image_tools_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698