3
votes

Suppose I have two numpy image arrays, a and b, of the same dimensions, 8-bit color, RGB format. Now suppose I want to produce a new numpy array whose pixel values are that of the previous two combined using the "Overlay" blending mode.

Its definition, as taken from Wikipedia, is as follows, where b is the top layer and a is the bottom layer:

enter image description here

In the formula, I believe a and b are represented in terms of their "whiteness" in the sense that a completely white pixel is 1 and a completely black pixel is 0. I am not sure how hue plays into that.

I'm not sure if there's a faster way to do this other than by iterating over the two images pixel by pixel, which is REALLY slow for 1920x1080 images. I need to be able to do this as fast as possible.

For example, I managed to implement the Addition blending mode as follows:

import numpy as np
import cv2

a = cv2.imread("a.jpg", cv2.IMREAD_UNCHANGED)
b = cv2.imread("b.jpg", cv2.IMREAD_UNCHANGED)

a = a.astype(float)
b = b.astype(float)

ab = a
for i in range(len(ab)):
  ab[i] = a[i] + b[i]

cv2.imwrite('Out.png', ab)

It seems to be pretty fast, and is certainly much faster than attempting to achieve the same thing by iterating pixel by pixel. But once again, this is just the Addition blending mode, and I need the Overlay blending mode.

If you know of any Python implementation of the Overlay blending mode between two RGB images in the form of numpy arrays that is very efficient, please help me find it. If not, can you implement it as efficiently as possible?

1

1 Answers

4
votes
import numpy as np
import cv2

a = cv2.imread("a.jpg", cv2.IMREAD_UNCHANGED)
b = cv2.imread("b.jpg", cv2.IMREAD_UNCHANGED)

a = a.astype(float)/255  
b = b.astype(float)/255 # make float on range 0-1

mask = a >= 0.5 # generate boolean mask of everywhere a > 0.5 
ab = np.zeros_like(a) # generate an output container for the blended image 

# now do the blending 
ab[~mask] = (2*a*b)[~mask] # 2ab everywhere a<0.5
ab[mask] = (1-2*(1-a)*(1-b))[mask] # else this 

I think that should do it. Now ab is a float image on -1,2 and is a blend of a and b. This will be relatively fast because it utilizes broadcasting and masking instead of a loop. I'm curious to hear the speed difference.


Following material added by Mark Setchell 16-NOV-2018, just so you all know who the guilty party is :-)

The values calculated by Mr Kayaks' code are floats in the range 0..1, whereas imwrite() is expecting uint8s in the range 0..255. So you just need to add the following to the bottom of his code:

# Scale to range 0..255 and save
x=(ab*255).astype(np.uint8) 
cv2.imwrite('result.png',x) 

If you then take these two images as a.jpg and b.jpg:

enter image description here enter image description here

You will get the result on the left - the one on the right is what you get from Photoshop if you choose Overlay blend mode:

enter image description here enter image description here