Wrong output when following the filter formula?

Question:

I am trying to make my image sepia, but I get the wrong filter and I cant see why? Is this the incorrect formula of the sepia filter?

im = Image.open("some.jpg")
image = np.asarray(im)  
sepia_image = np.empty_like(image)

for i in range(image.shape[0]):
    for j in range(image.shape[1]):
        sepia_image[i][j][0] = 0.393*image[i][j][0] + 0.769*image[i][j][1] + 0.189*image[i][j][2]
        sepia_image[i][j][1] = 0.349*image[i][j][0] + 0.686*image[i][j][1] + 0.168*image[i][j][2]
        sepia_image[i][j][2] = 0.272*image[i][j][0] + 0.534*image[i][j][1] + 0.131*image[i][j][2]
        for k in range(image.shape[2]):
            if sepia_image[i][j][k] > 255:
                sepia_image[i][j][k] = 255
sepia_image = sepia_image.astype("uint8")
Image.fromarray(sepia_image).show()

The image i get is this

enter image description here

Asked By: feter

||

Answers:

The problem is that your values are going out of bounds.

For example, using your formula on my example image below, the red channel in the first pixel ends up being 205*0.393 + 206*0.769 + 211*0.189, which is 278. If you are using unsigned 8-bit integers, this will overflow to 22.

To fix it, you need to use floats and clip the range back to 0 to 255, for example by using this instead of your np.empty_like() instantiation:

sepia_image = np.zeros_like(image, dtype=float)

Then, after running your loops:

sepia_image.astype(np.uint8)

Then your code works on my image at least.

Unsolicited advice: don’t use loops

Another issue is the difficulty of debugging code like this. In general, you want to avoid loops over arrays in Python. It’s slow, and it tends to require more code. Instead, take advantage of NumPy’s elementwise maths. For example, you can use np.matmul (or the @ operator, which does the same thing) like so:

from io import BytesIO

import requests
import numpy as np
from PIL import Image

# Image CC BY-SA Leiju / Wikimedia Commons 
uri = 'https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Neckertal_20150527-6384.jpg/640px-Neckertal_20150527-6384.jpg'
r = requests.get(uri)
img = Image.open(BytesIO(r.content))

# Turn this PIL Image into a NumPy array.
imarray = np.asarray(img)[..., :3] / 255

# Make a `sepia` multiplier.
sepia = np.array([[0.393, 0.349, 0.272],
                  [0.769, 0.686, 0.534],
                  [0.189, 0.168, 0.131]])

# Compute the result and clip back to 0 to 1.
imarray_sepia = np.clip(imarray @ sepia, 0, 1)

This produces:

Sepia version of image CC BY-SA Leiju / Wikimedia Commons

Answered By: kwinkunks
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.