-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathimage_processor.py
More file actions
109 lines (91 loc) · 3.8 KB
/
Copy pathimage_processor.py
File metadata and controls
109 lines (91 loc) · 3.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# Copyright (C)2014,2015 Philipp Naumann
# Copyright (C)2014,2015 Marcus Soll
#
# This file is part of SPtP.
#
# SPtP is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# SPtP is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with SPtP. If not, see <http://www.gnu.org/licenses/>.
import PIL.Image
class ImageProcessor:
def __init__(self, image):
"""
This class implements simple image processing methods.
:param image: The image to process
:type image: PIL.Image, currently only RGB(A).
:raise: ValueError: if the image is not an instance of PIL.Image.Image
:return: None
"""
if image is None:
self.image = None
return
if not isinstance(image, PIL.Image.Image):
raise ValueError('Not a PIL.Image.Image')
self.image = image
self.top, self.bottom = self._split_image()
def _split_image(self):
"""
Splits the image vertically in half:
:return: top, bottom as PIL.Image.Image tuple
:rtype: (PIL.Image.Image, PIL.Image.Image)
"""
if self.image is None:
return None, None
w, h = self.image.size
top = self.image.crop((0, 0, w, h // 2))
bottom = self.image.crop((0, h // 2, w, h))
return top, bottom
def aggregate_histogram(self, minimum_values=(200, 200, 200)):
"""
Computes the quotients of
sum(count(pixels with color rgb(r, ?, ?))) for r >= minimum_values[0],
sum(count(pixels with color rgb(?, g, ?))) for g >= minimum_values[1],
sum(count(pixels with color rgb(?, ?, b))) for b >= minimum_values[2]
and the total number of pixels (i. e. "how red/green/blue an image is") using the image's histogram.
:param minimum_values: (minimum red value, minimum green value, minimum blue value). Default: (200, 200, 200), empirically chosen
:type minimum_values: (int,int,int)
:raise ValueError: if the image is not RGB(A)
:return: Red, green and blue ratios as tuple
:rtype: (float,float,float)
"""
if self.image is None:
return None, None, None
if not self.image.mode in ('RGB', 'RGBA'):
raise ValueError('RGB(A) only')
histogram = self.image.histogram()
assert len(histogram) == 3 * 256 or len(histogram) == 4 * 256
w, h = self.image.size
p = w * h
r = histogram[0:256]
g = histogram[256:256 * 2]
b = histogram[256 * 2:256 * 3]
return sum(r[minimum_values[0]:]) / p, sum(g[minimum_values[1]:]) / p, sum(b[minimum_values[2]:]) / p
def outside(self):
"""
Simplistically classifies the image as "outside" if its top half is "mostly blue" and/or its bottom half is "mostly green"!
:return: True if "outside", False if not "outside" or image is None
:rtype: bool
"""
if self.image is None:
return False
outside = False
# Is top half "mostly blue"?
top_processor = ImageProcessor(self.top)
r, g, b = top_processor.aggregate_histogram()
if b > r and b > g:
outside = True
# Is bottom half "mostly green"?
bottom_processor = ImageProcessor(self.bottom)
r, g, b = bottom_processor.aggregate_histogram()
if g > r and g > b:
outside = True
return outside