How to apply brightness from one image onto another in Python

Question:

I’m trying to make a texture using an image with 3 colors, and a Perlin noise grayscale image.

This is the original image:

Original Image

This is the grayscale Perlin noise image:

Grayscale Perlin Image

What I need to do is apply the original image’s brightness to the grayscale image, such that darkest and lightest brightness in the Perlin noise image is no longer 100% black (0) and 100% white (1), but taken from the original image. Then, apply the new mapping of brightness from the grayscale Perlin noise image back to the original image.

This is what I tried:

from PIL import Image

alpha = 0.5
im = Image.open(filename1).convert("RGBA")
new_img = Image.open(filename2).convert("RGBA")
new_img = Image.blend(im, new_img, alpha)
new_img.save("foo.png","PNG")

And this is the output that I get:

resulting gradient, sort of

Which is wrong, but imagine the dark and light orange and bright color having the same gradient as the grayscale image, BUT with no 100% black or 100% white.

I believe I need to:

  1. Convert original image to HSV (properly, I’ve tried with a few functions from colorsys and matplotlib and they give me weird numbers.

  2. Get highest and lowest V value from the original image.

  3. Convert grayscale image to HSV.

  4. Transform or normalize (I think that’s what its called) the grayscale HSV using the V values from the original HSV image.

  5. Remap all the original V values with the new transformed/normalized grayscale V values.

Asked By: MCG

||

Answers:

I dont know much Python, but Maybe Try Converting the int into a float or double. Maybe it’ll work

Answered By: Arkadiusz Brzoza

Why is it not working?

The approach that you are using will not work as expected because instead of keeping color and saturation information from one image and taking the other image’s lightness information (totally or partially), you are just interpolating all the channels from both images at the same time, based on a constant alpha, as stated on the docs:

PIL.Image.blend(im1, im2, alpha)

Creates a new image by interpolating between two input images, using a constant alpha: out = image1 * (1.0 - alpha) + image2 * alpha

[…]

alpha – The interpolation alpha factor. If alpha is 0.0, a copy of the first image is returned. If alpha is 1.0, a copy of the second image is returned. There are no restrictions on the alpha value. If necessary, the result is clipped to fit into the allowed output range.


Basic working example

First, let’s get a basic example working. I’m going to use cv2 instead of PIL, just because I’m more familiar with it and I already have it installed on my machine.

I will also use HSL (HLS in cv2) instead of HSV, as I think that will produce an output that is closer to what you might be looking for.

import cv2

filename1 = './f1.png'
filename2 = './f2.png'

# Load both images and convert them from BGR to HLS:

img1 = cv2.cvtColor(cv2.imread(filename1, cv2.IMREAD_COLOR), cv2.COLOR_BGR2HLS)
img2 = cv2.cvtColor(cv2.imread(filename2, cv2.IMREAD_COLOR), cv2.COLOR_BGR2HLS)

# Copy img1, the one with relevant color and saturation information:

texture = img1.copy()

# Replace its lightness information with the one from img2:

texture[:,:,1] = img2[:,:,1]

# Convert the image back from HLS to BGR and save it:

cv2.imwrite('./texture.png', cv2.cvtColor(texture, cv2.COLOR_HLS2BGR))

This is the final output:

enter image description here


️ Adjust lightness

Ok, so we have a simple case working, but you might not want to replace img1‘s lightness with img2‘s completely, so in that case just replace this line:

texture[:,:,1] = img2[:,:,1]

With these two:

alpha = 0.25
texture[:,:,1] = alpha * img1[:,:,1] + (1.0 - alpha) * img2[:,:,1]

Now, you will retain 25% lightness from img1 and 75% from img2, and you can adjust it as needed.

For alpha = 0.25, the output will look like this:

enter image description here


HSL & HSV

Although HSL and HSV look quite similar, there are a few differences, mainly regarding how they represent pure white and light colors, that would make this script generate slightly different images when using one or the other:

HSL ColorSolid Cylinder
HSV ColorSolid Cylinder

We just need to change a couple of things to make it work with HSV:

import cv2

filename1 = './f1.png'
filename2 = './f2.png'

# Load both images and convert them from BGR to HSV:

img1 = cv2.cvtColor(cv2.imread(filename1, cv2.IMREAD_COLOR), cv2.COLOR_BGR2HSV)
img2 = cv2.cvtColor(cv2.imread(filename2, cv2.IMREAD_COLOR), cv2.COLOR_BGR2HSV)

# Copy img1, the one with relevant color and saturation information:

texture = img1.copy()

# Merge img1 and img2's value channel:

alpha = 0.25
texture[:,:,2] = alpha * img1[:,:,2] + (1.0 - alpha) * img2[:,:,2]

# Convert the image back from HSV to BGR and save it:

cv2.imwrite('./texture.png', cv2.cvtColor(texture, cv2.COLOR_HSV2BGR))

This is how the first example looks like when using HSV:

enter image description here

And this is the second example (with alpha = 0.25):

enter image description here

You can see the most noticeable differences are in the lightest areas.

Answered By: Danziger