OLD | NEW |
---|---|
(Empty) | |
1 # Copyright (c) 2013 The Chromium Authos. 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 comparision.""" | |
6 | |
7 import itertools | |
8 import PIL | |
9 from PIL import Image | |
10 | |
11 | |
12 def _AreTheSameSize(images): | |
13 """Returns whether a set of images are the size size. | |
14 | |
15 Args: | |
16 images: a list of images to compare. | |
17 | |
18 Returns: | |
19 boolean. | |
20 | |
21 Raises: | |
22 Exception: One image or fewer is passed in. | |
23 """ | |
24 if len(images) > 1: | |
25 return all(images[0].size == img.size for img in images[1:]) | |
26 else: | |
27 raise Exception('No images passed in.') | |
28 | |
29 | |
30 def _GetDifferenceWithMask(image1, image2, mask=None, | |
31 masked_color=(0, 0, 0), | |
32 same_color=(0, 0, 0), | |
33 different_color=(255, 255, 255)): | |
34 """Returns an image representing the difference between the two images. | |
35 | |
36 This function computes the difference between two images taking into | |
37 account a mask if it is provided. The final three arguments represent | |
38 the coloration of the generated image. | |
39 | |
40 Args: | |
41 image1: the first image to compare. | |
42 image2: the second image to compare. | |
43 mask: an optional mask to take into consideration. | |
44 masked_color: the color of a masked section in the resulting image. | |
45 same_color: the color of an unmasked section that is the same. | |
46 between images 1 and 2 in the resulting image. | |
47 different_color: the color of an unmasked section that is different | |
48 between images 1 and 2 in the resulting image. | |
49 | |
50 Returns: | |
51 an image repesenting the difference between the two images. | |
52 | |
53 Raises: | |
54 Exception: if image1, image2, and mask are not the same size. | |
55 """ | |
56 if mask: | |
57 if not _AreTheSameSize([image1, image2, mask]): | |
58 raise Exception('images and mask must be the same size.') | |
59 image_diff = PIL.Image.new('RGB', image1.size, (0, 0, 0)) | |
60 data = list( | |
craigdh
2013/06/18 19:16:11
use [] instead of list(). That will result in this
cgrimm
2013/06/18 20:21:01
Done.
| |
61 masked_color if m == (255, 255, 255) | |
62 else same_color if px1 == px2 | |
63 else different_color | |
64 for m, px1, px2 in itertools.izip(mask.getdata(), | |
65 image1.getdata(), | |
66 image2.getdata()) | |
67 ) | |
68 image_diff.putdata(data) | |
69 return image_diff | |
70 else: | |
71 if not _AreTheSameSize([image1, image2]): | |
72 raise Exception('images must be the same size.') | |
73 image_diff = PIL.Image.new('RGB', image1.size, (0, 0, 0)) | |
74 data = list( | |
craigdh
2013/06/18 19:16:11
ditto
cgrimm
2013/06/18 20:21:01
Done.
| |
75 same_color if px1 == px2 | |
76 else different_color | |
77 for px1, px2 in itertools.izip(image1.getdata(), | |
78 image2.getdata()) | |
79 ) | |
80 image_diff.putdata(data) | |
81 return image_diff | |
82 | |
83 | |
84 def CreateMask(images): | |
85 """Computes a mask for a set of images. | |
86 | |
87 Returns an image that is computed from the images | |
88 passed in. The mask is generated by continually | |
craigdh
2013/06/18 19:16:11
Just say the mask will have a white pixel anywhere
cgrimm
2013/06/18 20:21:01
Done.
| |
89 finding the difference between two images in the list | |
90 with respect to a mask which is initialized as a black | |
91 image, then setting the value of the mask to the result. | |
92 | |
93 Args: | |
94 images: the images to compute the mask from. | |
95 | |
96 Returns: | |
97 an image that is a mask of the passed in images. | |
98 | |
99 Raises: | |
100 Exception: if the images passed in are not of the same size. | |
101 Exception: if fewer than two images are passed in. | |
102 """ | |
103 if len(images) < 2: | |
104 raise Exception('mask must be created from two or more images.') | |
105 mask = Image.new('RGB', images[0].size, (0, 0, 0)) | |
106 image = images[0] | |
107 for other_image in images[1:]: | |
108 mask = _GetDifferenceWithMask( | |
109 image, | |
110 other_image, | |
111 mask, | |
112 masked_color=(255, 255, 255)) | |
113 return mask | |
114 | |
115 | |
116 def VisualizeImageDifferences(image1, image2, mask=None): | |
117 """Returns an image repesenting the unmasked differences between two images. | |
118 | |
119 Iterates through the pixel values of two images and an optional | |
120 mask. If the pixel values are the same, or the pixel is masked, | |
121 (0,0,0) is stored for that pixel. Otherwise, (255,255,255) is stored. | |
122 This ultimately produces an image where unmasked differences between | |
123 the two images are white pixels, and everything else is black. | |
124 | |
125 Args: | |
126 image1: an Image | |
127 image2: another Image of the same size as image1. | |
128 mask: an optional mask that represents parts of the two | |
129 images to ignore when generating the difference image. | |
130 | |
131 Returns: | |
132 a black and white image representing the unmasked difference between | |
133 the two input images. | |
134 | |
135 Raises: | |
136 Exception: if the two images and optional mask are different sizes. | |
137 """ | |
138 return _GetDifferenceWithMask(image1, image2, mask) | |
139 | |
140 | |
141 def TotalDifferentPixels(image1, image2, mask=None): | |
142 """Computes the number of different pixels between two images. | |
143 | |
144 Args: | |
145 image1: the first RGB PIL.Image to be compared. | |
146 image2: the second RGB PIL.Image to be compared. | |
147 mask: an optional mask to occlude parts of the images from calculation. | |
craigdh
2013/06/18 19:16:11
Mention the format of the mask. Is it an image or
cgrimm
2013/06/18 20:21:01
Done.
| |
148 | |
149 Returns: | |
150 the number of differing pixels between the images. | |
151 | |
152 Raises: | |
153 Exception: if the images to be compared and the mask are not the same size. | |
154 """ | |
155 if mask: | |
craigdh
2013/06/18 19:16:11
Entirely different codepaths are being followed de
cgrimm
2013/06/18 20:21:01
Similar problem in _GetDifferenceWithMask, fixed b
| |
156 if _AreTheSameSize([image1, image2, mask]): | |
157 return sum([ | |
craigdh
2013/06/18 19:16:11
remove the []. No need to create a list in memory,
cgrimm
2013/06/18 20:21:01
Done.
| |
158 0 if m == (255, 255, 255) | |
159 else 1 if px1 != px2 | |
160 else 0 | |
161 for px1, px2, m in itertools.izip( | |
162 image1.getdata(), | |
163 image2.getdata(), | |
164 mask.getdata() | |
165 ) | |
166 ]) | |
167 else: | |
168 raise Exception('images and mask must be the same size') | |
169 else: | |
170 if _AreTheSameSize([image1, image2]): | |
171 return sum([ | |
craigdh
2013/06/18 19:16:11
ditto
cgrimm
2013/06/18 20:21:01
Done.
| |
172 1 if px1 != px2 | |
173 else 0 | |
174 for px1, px2 in itertools.izip( | |
175 image1.getdata(), | |
176 image2.getdata() | |
177 ) | |
178 ]) | |
179 else: | |
180 raise Exception('images must be the same size') | |
181 | |
182 | |
183 def SameImage(image1, image2, max_different_pixels=0, mask=None): | |
184 """Returns a boolean representing whether the images are the same. | |
185 | |
186 Returns a boolean indicating whether two images are similar | |
187 enough to be considered the same. Essentially wraps the | |
188 TotalDifferentPixels function and adds a max_different_pixels | |
189 cutoff for whether or not the images are the same. | |
190 | |
191 Args: | |
192 image1: an RGB PIL.Image to compare. | |
193 image2: an RGB PIL.Image to compare. | |
194 max_different_pixels: a number that is the cutoff for whether or not | |
195 the two images are the same. | |
196 mask: an optional Image that occludes parts of the images from | |
197 the calculation. | |
198 | |
199 Returns: | |
200 True if the images are similar, False otherwise. | |
201 | |
202 Raises: | |
203 Exception: if the images (and mask) are different sizes. | |
204 """ | |
205 different_pixels = TotalDifferentPixels(image1, image2, mask) | |
206 return different_pixels <= max_different_pixels | |
OLD | NEW |