Skip to content

Commit

Permalink
Added Burkes dithering algorithm. (TheAlgorithms#1916)
Browse files Browse the repository at this point in the history
* Added Burkes dithering algorithm

* Added unit tests for burkes algorithm

* Fix burkes algorithm

* Added some additional information

* Fixed CI tests

* Update digital_image_processing/dithering/burkes.py

Co-Authored-By: Christian Clauss <[email protected]>

* Update digital_image_processing/dithering/burkes.py

Co-Authored-By: Christian Clauss <[email protected]>

* Update digital_image_processing/dithering/burkes.py

Co-Authored-By: Christian Clauss <[email protected]>

* Propogate the += and add a doctest

* Fix doctest

* @staticmethod --> @ classmethod to ease testing

* def test_burkes(file_path):

* Fix for mypy checks

* Fix variable order in get_greyscale

* Fix get_greyscale method

* Fix get_greyscale method

* 3.753

Co-authored-by: Christian Clauss <[email protected]>
  • Loading branch information
l3str4nge and cclauss authored Apr 30, 2020
1 parent fbc038d commit 3d0680e
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 0 deletions.
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()

0 comments on commit 3d0680e

Please sign in to comment.