-
-
Notifications
You must be signed in to change notification settings - Fork 45.9k
/
burkes.py
98 lines (82 loc) · 3.55 KB
/
burkes.py
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
"""
Implementation Burke's algorithm (dithering)
"""
import numpy as np
from cv2 import destroyAllWindows, imread, imshow, waitKey
class Burkes:
"""
Burke's algorithm is using for converting grayscale image to black and white version
Source: Source: https://en.wikipedia.org/wiki/Dither
Note:
* Best results are given with threshold= ~1/2 * max greyscale value.
* This implementation get RGB image and converts it to greyscale in runtime.
"""
def __init__(self, input_img, threshold: int):
self.min_threshold = 0
# max greyscale value for #FFFFFF
self.max_threshold = int(self.get_greyscale(255, 255, 255))
if not self.min_threshold < threshold < self.max_threshold:
msg = f"Factor value should be from 0 to {self.max_threshold}"
raise ValueError(msg)
self.input_img = input_img
self.threshold = threshold
self.width, self.height = self.input_img.shape[1], self.input_img.shape[0]
# error table size (+4 columns and +1 row) greater than input image because of
# lack of if statements
self.error_table = [
[0 for _ in range(self.height + 4)] for __ in range(self.width + 1)
]
self.output_img = np.ones((self.width, self.height, 3), np.uint8) * 255
@classmethod
def get_greyscale(cls, blue: int, green: int, red: int) -> float:
"""
>>> Burkes.get_greyscale(3, 4, 5)
4.185
>>> Burkes.get_greyscale(0, 0, 0)
0.0
>>> Burkes.get_greyscale(255, 255, 255)
255.0
"""
"""
Formula from https://en.wikipedia.org/wiki/HSL_and_HSV
cf Lightness section, and Fig 13c.
We use the first of four possible.
"""
return 0.114 * blue + 0.587 * green + 0.299 * red
def process(self) -> None:
for y in range(self.height):
for x in range(self.width):
greyscale = int(self.get_greyscale(*self.input_img[y][x]))
if self.threshold > greyscale + self.error_table[y][x]:
self.output_img[y][x] = (0, 0, 0)
current_error = greyscale + self.error_table[y][x]
else:
self.output_img[y][x] = (255, 255, 255)
current_error = greyscale + self.error_table[y][x] - 255
"""
Burkes error propagation (`*` is current pixel):
* 8/32 4/32
2/32 4/32 8/32 4/32 2/32
"""
self.error_table[y][x + 1] += int(8 / 32 * current_error)
self.error_table[y][x + 2] += int(4 / 32 * current_error)
self.error_table[y + 1][x] += int(8 / 32 * current_error)
self.error_table[y + 1][x + 1] += int(4 / 32 * current_error)
self.error_table[y + 1][x + 2] += int(2 / 32 * current_error)
self.error_table[y + 1][x - 1] += int(4 / 32 * current_error)
self.error_table[y + 1][x - 2] += int(2 / 32 * current_error)
if __name__ == "__main__":
# create Burke's instances with original images in greyscale
burkes_instances = [
Burkes(imread("image_data/lena.jpg", 1), threshold)
for threshold in (1, 126, 130, 140)
]
for burkes in burkes_instances:
burkes.process()
for burkes in burkes_instances:
imshow(
f"Original image with dithering threshold: {burkes.threshold}",
burkes.output_img,
)
waitKey(0)
destroyAllWindows()