Added Burkes dithering algorithm. (#1916)

* 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 <cclauss@me.com>

* Update digital_image_processing/dithering/burkes.py

Co-Authored-By: Christian Clauss <cclauss@me.com>

* Update digital_image_processing/dithering/burkes.py

Co-Authored-By: Christian Clauss <cclauss@me.com>

* 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 <cclauss@me.com>
This commit is contained in:
mateuszz0000 2020-04-30 11:54:20 +02:00 committed by GitHub
parent fbc038d532
commit 3d0680eddf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 95 additions and 0 deletions

View File

@ -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()

View File

@ -10,6 +10,7 @@ import digital_image_processing.filters.convolve as conv
import digital_image_processing.change_contrast as cc import digital_image_processing.change_contrast as cc
import digital_image_processing.convert_to_negative as cn import digital_image_processing.convert_to_negative as cn
import digital_image_processing.sepia as sp import digital_image_processing.sepia as sp
import digital_image_processing.dithering.burkes as bs
from cv2 import imread, cvtColor, COLOR_BGR2GRAY from cv2 import imread, cvtColor, COLOR_BGR2GRAY
from numpy import array, uint8 from numpy import array, uint8
from PIL import Image from PIL import Image
@ -17,6 +18,7 @@ from PIL import Image
img = imread(r"digital_image_processing/image_data/lena_small.jpg") img = imread(r"digital_image_processing/image_data/lena_small.jpg")
gray = cvtColor(img, COLOR_BGR2GRAY) gray = cvtColor(img, COLOR_BGR2GRAY)
# Test: convert_to_negative() # Test: convert_to_negative()
def test_convert_to_negative(): def test_convert_to_negative():
negative_img = cn.convert_to_negative(img) negative_img = cn.convert_to_negative(img)
@ -74,3 +76,9 @@ def test_sobel_filter():
def test_sepia(): def test_sepia():
sepia = sp.make_sepia(img, 20) sepia = sp.make_sepia(img, 20)
assert sepia.all() 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()