Skip to content

Added Burkes dithering algorithm. #1916

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
87 changes: 87 additions & 0 deletions digital_image_processing/dithering/burkes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
"""
Implementation Burke's algorithm (dithering)
"""
from cv2 import destroyAllWindows, imread, imshow, waitKey
import numpy as np


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:
raise ValueError(f"Factor value should be from 0 to {self.max_threshold}")

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)
3.753
"""
return 0.114 * blue + 0.587 * green + 0.2126 * 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[x][y]
else:
self.output_img[y][x] = (255, 255, 255)
current_error = greyscale + self.error_table[x][y] - 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()
8 changes: 8 additions & 0 deletions digital_image_processing/test_digital_image_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
import digital_image_processing.change_contrast as cc
import digital_image_processing.convert_to_negative as cn
import digital_image_processing.sepia as sp
import digital_image_processing.dithering.burkes as bs
from cv2 import imread, cvtColor, COLOR_BGR2GRAY
from numpy import array, uint8
from PIL import Image

img = imread(r"digital_image_processing/image_data/lena_small.jpg")
gray = cvtColor(img, COLOR_BGR2GRAY)


# Test: convert_to_negative()
def test_convert_to_negative():
negative_img = cn.convert_to_negative(img)
Expand Down Expand Up @@ -74,3 +76,9 @@ def test_sobel_filter():
def test_sepia():
sepia = sp.make_sepia(img, 20)
assert sepia.all()


def test_burkes(file_path: str="digital_image_processing/image_data/lena_small.jpg"):
burkes = bs.Burkes(imread(file_path, 1), 120)
burkes.process()
assert burkes.output_img.any()