basic of image dithering

Is just read about image-dithering ? So I tried to implement that. Here is my result. Can someone verify it?

import numpy as np
from numpy.random import default_rng
import matplotlib.pyplot as plt

n = 300
real = np.random.randint(low = 0, high=256, size=(n,n), dtype='l')
#print(real)

dn = 3
rng = default_rng()
dither = rng.choice(dn*dn, size=(dn,dn), replace=False)
print(dither)

res = np.zeros((n,n))
for y in range (0,n):
for x in range (0,n):
i = x % dn
j = y % dn
res[x][y] = 255 if (real[x][y] > dither[i][j]) else 0
#print(res)

fig = plt.figure()

ax1 = plt.subplot(1, 3, 1)
ax1.title.set_text('Real Image(300x300)')
plt.imshow(real, cmap="gray")

ax2 = plt.subplot(1, 3, 2)
ax2.title.set_text('Dithering Matrix(3x3)')
plt.imshow(dither, cmap="gray")

ax3 = plt.subplot(1, 3, 3)
ax3.title.set_text('Resultant Image(300x300)')
plt.imshow(res, cmap="gray")

plt.show()


reduced value of n to 6, so that image may become easily understandable.

Result :

• It might be helpful to use a much higher resolution input image, at 6x6 it's not east to see what's happening across a range of brightnesses Feb 7, 2020 at 7:57
• here is a snap for 90x90, seems abnormal.. that's why asking ... can you verify the code.. for y in range (0,n): for x in range (0,n): i = x % dn j = y % dn res[x][y] = 255 if (real[x][y] > dither[i][j]) else 0 Feb 7, 2020 at 9:04
• Your algorithm looks sound, but I'm not a python programmer, so I can't say if there is a logic problem in your code, I'm not sure about your use of range(0,n) in both the X/Y for loops? Maybe a better test image would be a vertical gradient (say 256x256) so you can see how it behaves across an entire range of inputs. Feb 8, 2020 at 10:34

There is a small issue in the original code. Indeed, real array values ranging between [0, 255]. However, the mask values ranging between [0, 3*3] (as dn = 3 in the example above).

After fixing the issue:

The correct code (with image reading and all data normalized between [0, 1.0]):

import numpy as np
from numpy.random import default_rng
import matplotlib.pyplot as plt
from PIL import Image

# Read an image from the disk
real = Image.open("example.jpeg")
real = np.array(real) / 255.0
# If it is a RGB image, transform it to a gray image
# TODO: Could use any function (like luminance)
real = (real[:,:,0] + real[:,:,1] + real[:,:,2]) / 3.0

# Create the mask by shuffling
dn = 6
rng = default_rng()
# In the original code, there was a problem here.
# Indeed, the mask values ranging between [0, dn*dn]
# So the generated mask needs to be properly normalized
dither = rng.choice(dn*dn, size=(dn,dn), replace=False) / float(dn*dn)
print(dither)

# Generate the results
res = np.zeros_like(real)
width, height = res.shape[1], res.shape[0]
for y in range (0,width):
for x in range (0,height):
i = x % dn
j = y % dn
res[x][y] = 1.0 if (real[x][y] > dither[i][j]) else 0.0

# Display the result
fig = plt.figure()
ax1 = plt.subplot(1, 3, 1)
ax1.title.set_text('Real Image(300x300)')
plt.imshow(real, cmap="gray")
ax2 = plt.subplot(1, 3, 2)
ax2.title.set_text('Dithering Matrix(3x3)')
plt.imshow(dither, cmap="gray")
ax3 = plt.subplot(1, 3, 3)
ax3.title.set_text('Resultant Image(300x300)')
plt.imshow(res, cmap="gray")
plt.show()

• what are you doing with real = (real[:,:,0] + real[:,:,1] + real[:,:,2]) / 3.0 Feb 9, 2020 at 16:34
• It is a RGB to gray color coversion (here average the three color channel). Indeed, you need to convert the image before applying dithering. If you have only scalar values (as your original example), you do not need to do this conversion. Feb 9, 2020 at 22:50