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
CodePudding user response:
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: