Skip to content

Commit 336fd04

Browse files
committed
add image processing, operates in float space which uses too much memory!
1 parent 05dc862 commit 336fd04

File tree

1 file changed

+253
-0
lines changed

1 file changed

+253
-0
lines changed

adafruit_pycamera/imageprocessing.py

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import sys
2+
import struct
3+
import displayio
4+
5+
try:
6+
import numpy as np
7+
except:
8+
import ulab.numpy as np
9+
10+
11+
def _bytes_per_row(source_width: int) -> int:
12+
pixel_bytes = 3 * source_width
13+
padding_bytes = (4 - (pixel_bytes % 4)) % 4
14+
return pixel_bytes + padding_bytes
15+
16+
17+
def _write_bmp_header(output_file: BufferedWriter, filesize: int) -> None:
18+
output_file.write(bytes("BM", "ascii"))
19+
output_file.write(struct.pack("<I", filesize))
20+
output_file.write(b"\00\x00")
21+
output_file.write(b"\00\x00")
22+
output_file.write(struct.pack("<I", 54))
23+
24+
25+
def _write_dib_header(output_file: BufferedWriter, width: int, height: int) -> None:
26+
output_file.write(struct.pack("<I", 40))
27+
output_file.write(struct.pack("<I", width))
28+
output_file.write(struct.pack("<I", height))
29+
output_file.write(struct.pack("<H", 1))
30+
output_file.write(struct.pack("<H", 24))
31+
for _ in range(24):
32+
output_file.write(b"\x00")
33+
34+
35+
def components_to_file_rgb565(output_file, r, g, b):
36+
height, width = r.shape
37+
pixel_bytes = 3 * width
38+
padding_bytes = (4 - (pixel_bytes % 4)) % 4
39+
filesize = 54 + height * (pixel_bytes + padding_bytes)
40+
_write_bmp_header(output_file, filesize)
41+
_write_dib_header(output_file, width, height)
42+
p = b"\0" * padding_bytes
43+
m = memoryview(buffer_from_components_rgb888(r, g, b))
44+
for i in range(0, len(m), pixel_bytes)[::-1]:
45+
output_file.write(m[i : i + pixel_bytes])
46+
output_file.write(p)
47+
48+
49+
def np_convolve_same(a, v):
50+
"""Perform the np.convolve(mode=same) operation
51+
52+
This is not directly supported on ulab, so we have to slice the "full" mode result
53+
"""
54+
if len(a) < len(v):
55+
a, v = v, a
56+
tmp = np.convolve(a, v)
57+
n = len(a)
58+
c = (len(v) - 1) // 2
59+
result = tmp[c : c + n]
60+
return result
61+
62+
63+
FIVE_BITS = 0b11111
64+
SIX_BITS = 0b111111
65+
EIGHT_BITS = 0b11111111
66+
67+
68+
def bitmap_as_array(bitmap):
69+
### XXX todo: work on blinka
70+
if bitmap.width % 2:
71+
raise ValueError("Can only work on even-width bitmaps")
72+
return (
73+
np.frombuffer(bitmap, dtype=np.uint16)
74+
.reshape((bitmap.height, bitmap.width))
75+
.byteswap()
76+
)
77+
78+
79+
def bitmap_to_components_rgb565(bitmap):
80+
"""Convert a RGB65_BYTESWAPPED image to float32 components in the [0,1] inclusive range"""
81+
arr = bitmap_as_array(bitmap)
82+
83+
r = np.right_shift(arr, 11) * (1.0 / FIVE_BITS)
84+
g = (np.right_shift(arr, 5) & SIX_BITS) * (1.0 / SIX_BITS)
85+
b = (arr & FIVE_BITS) * (1.0 / FIVE_BITS)
86+
return r, g, b
87+
88+
89+
def bitmap_from_components_rgb565(r, g, b):
90+
"""Convert the float32 components to a bitmap"""
91+
h, w = r.shape
92+
result = displayio.Bitmap(w, h, 65535)
93+
return bitmap_from_components_inplace_rgb565(result, r, g, b)
94+
95+
96+
def bitmap_from_components_inplace_rgb565(bitmap, r, g, b):
97+
arr = bitmap_as_array(bitmap)
98+
r = np.array(np.maximum(np.minimum(r, 1.0), 0.0) * FIVE_BITS, dtype=np.uint16)
99+
g = np.array(np.maximum(np.minimum(g, 1.0), 0.0) * SIX_BITS, dtype=np.uint16)
100+
b = np.array(np.maximum(np.minimum(b, 1.0), 0.0) * FIVE_BITS, dtype=np.uint16)
101+
arr = np.left_shift(r, 11)
102+
arr[:] |= np.left_shift(g, 5)
103+
arr[:] |= b
104+
arr = arr.byteswap().flatten()
105+
dest = np.frombuffer(bitmap, dtype=np.uint16)
106+
dest[:] = arr
107+
return bitmap
108+
109+
110+
def buffer_from_components_rgb888(r, g, b):
111+
"""Convert the float32 components to a RGB888 buffer in memory"""
112+
r = np.array(
113+
np.maximum(np.minimum(r, 1.0), 0.0) * EIGHT_BITS, dtype=np.uint8
114+
).flatten()
115+
g = np.array(
116+
np.maximum(np.minimum(g, 1.0), 0.0) * EIGHT_BITS, dtype=np.uint8
117+
).flatten()
118+
b = np.array(
119+
np.maximum(np.minimum(b, 1.0), 0.0) * EIGHT_BITS, dtype=np.uint8
120+
).flatten()
121+
result = np.zeros(3 * len(r), dtype=np.uint8)
122+
result[2::3] = r
123+
result[1::3] = g
124+
result[0::3] = b
125+
return result
126+
127+
128+
def separable_filter(data, vh, vv=None):
129+
"""Apply a separable filter to a 2d array.
130+
131+
If the vertical coefficients ``vv`` are none, the ``vh`` components are
132+
used for vertical too."""
133+
if vv is None:
134+
vv = vh
135+
136+
result = data[:]
137+
138+
# First run the filter across each row
139+
n_rows = result.shape[0]
140+
for i in range(n_rows):
141+
result[i, :] = np_convolve_same(result[i, :], vh)
142+
143+
# Run the filter across each column
144+
n_cols = result.shape[1]
145+
for i in range(n_cols):
146+
result[:, i] = np_convolve_same(result[:, i], vv)
147+
148+
return result
149+
150+
151+
def bitmap_separable_filter(bitmap, vh, vv=None):
152+
"""Apply a separable filter to an image, returning a new image"""
153+
r, g, b = bitmap_to_components_rgb565(bitmap)
154+
r = separable_filter(r, vh, vv)
155+
g = separable_filter(g, vh, vv)
156+
b = separable_filter(b, vh, vv)
157+
return bitmap_from_components_rgb565(r, g, b)
158+
159+
160+
def bitmap_channel_filter3(
161+
bitmap, r_func=lambda r, g, b: r, g_func=lambda r, g, b: g, b_func=lambda r, g, b: b
162+
):
163+
"""Perform channel filtering where each function recieves all 3 channels"""
164+
r, g, b = bitmap_to_components_rgb565(bitmap)
165+
r = r_func(r, g, b)
166+
g = g_func(r, g, b)
167+
b = b_func(r, g, b)
168+
return bitmap_from_components_rgb565(r, g, b)
169+
170+
171+
def bitmap_channel_filter1(
172+
bitmap, r_func=lambda r: r, g_func=lambda g: g, b_func=lambda b: b
173+
):
174+
"""Perform channel filtering where each function recieves just one channel"""
175+
return bitmap_channel_filter3(
176+
bitmap,
177+
lambda r, g, b: r_func(r),
178+
lambda r, g, b: g_func(g),
179+
lambda r, g, b: b_func(b),
180+
)
181+
182+
183+
def solarize_channel(c, threshold=0.5):
184+
"""Solarize an image channel.
185+
186+
If the channel value is above a threshold, it is inverted. Otherwise, it is unchanged.
187+
"""
188+
return (-1 * arr) * (arr > threshold) + arr * (arr <= threshold)
189+
190+
191+
def solarize(bitmap, threshold=0.5):
192+
"""Apply a solarize filter to an image"""
193+
return bitmap_channel_filter1(
194+
bitmap,
195+
lambda r: solarize_channel(r, threshold),
196+
lambda g: solarize_channel(r, threshold),
197+
lambda b: solarize_channel(b, threshold),
198+
)
199+
200+
201+
def sepia(bitmap):
202+
"""Apply a sepia filter to an image
203+
204+
based on some coefficients I found on the internet"""
205+
return bitmap_channel_filter3(
206+
bitmap,
207+
lambda r, g, b: 0.393 * r + 0.769 * g + 0.189 * b,
208+
lambda r, g, b: 0.349 * r + 0.686 * g + 0.168 * b,
209+
lambda r, g, b: 0.272 * r + 0.534 * g + 0.131 * b,
210+
)
211+
212+
213+
def greyscale(bitmap):
214+
"""Convert an image to greyscale"""
215+
r, g, b = bitmap_to_components_rgb565(bitmap)
216+
l = 0.2989 * r + 0.5870 * g + 0.1140 * b
217+
return bitmap_from_components_rgb565(l, l, l)
218+
219+
220+
def red_cast(bitmap):
221+
return bitmap_channel_filter1(
222+
bitmap, lambda r: r, lambda g: g * 0.5, lambda b: b * 0.5
223+
)
224+
225+
226+
def green_cast(bitmap):
227+
return bitmap_channel_filter1(
228+
bitmap, lambda r: r * 0.5, lambda g: g, lambda b: b * 0.5
229+
)
230+
231+
232+
def blue_cast(bitmap):
233+
return bitmap_channel_filter1(
234+
bitmap, lambda r: r * 0.5, lambda g: g * 0.5, lambda b: b
235+
)
236+
237+
238+
def blur(bitmap):
239+
return bitmap_separable_filter(bitmap, np.array([0.25, 0.5, 0.25]))
240+
241+
242+
def sharpen(bitmap):
243+
y = 1 / 5
244+
return bitmap_separable_filter(bitmap, np.array([-y, -y, 2 - y, -y, -y]))
245+
246+
247+
def edgedetect(bitmap):
248+
coefficients = np.array([-1, 0, 1])
249+
r, g, b = bitmap_to_components_rgb565(bitmap)
250+
r = separable_filter(r, coefficients, coefficients) + 0.5
251+
g = separable_filter(g, coefficients, coefficients) + 0.5
252+
b = separable_filter(b, coefficients, coefficients) + 0.5
253+
return bitmap_from_components_rgb565(r, g, b)

0 commit comments

Comments
 (0)