Last active
June 30, 2023 10:16
-
-
Save pthom/5155d319a7957a38aeb2ac9e54cc0999 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
import cv2 | |
def overlay_alpha_image_lazy(background_rgb, overlay_rgba, alpha): | |
# cf https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending | |
# If the destination background is opaque, then | |
# out_rgb = overlay_rgb * overlay_alpha + background_rgb * (1 - overlay_alpha) | |
overlay_alpha = overlay_rgba[: , : , 3].astype(np.float) / 255. * alpha | |
overlay_alpha_3 = np.dstack((overlay_alpha, overlay_alpha, overlay_alpha)) | |
overlay_rgb = overlay_rgba[: , : , : 3].astype(np.float) | |
background_rgb_f = background_rgb.astype(np.float) | |
out_rgb = overlay_rgb * overlay_alpha_3 + background_rgb_f * (1. - overlay_alpha_3) | |
out_rgb = out_rgb.astype(np.uint8) | |
return out_rgb | |
def overlay_alpha_image_precise(background_rgb, overlay_rgba, alpha, gamma_factor=2.2): | |
""" | |
cf minute physics brilliant clip "Computer color is broken" : https://www.youtube.com/watch?v=LKnqECcg6Gw | |
the RGB values are gamma-corrected by the sensor (in order to keep accuracy for lower luminancy), | |
we need to undo this before averaging. | |
""" | |
overlay_alpha = overlay_rgba[: , : , 3].astype(np.float) / 255. * alpha | |
overlay_alpha_3 = np.dstack((overlay_alpha, overlay_alpha, overlay_alpha)) | |
overlay_rgb_squared = np.float_power(overlay_rgba[: , : , : 3].astype(np.float), gamma_factor) | |
background_rgb_squared = np.float_power( background_rgb.astype(np.float), gamma_factor) | |
out_rgb_squared = overlay_rgb_squared * overlay_alpha_3 + background_rgb_squared * (1. - overlay_alpha_3) | |
out_rgb = np.float_power(out_rgb_squared, 1. / gamma_factor) | |
out_rgb = out_rgb.astype(np.uint8) | |
return out_rgb | |
def test_overlay(): | |
img = np.zeros((100, 800, 3), np.uint8) | |
img[ : , : , : ] = (0, 0, 255) | |
overlay = np.zeros((100, 800, 4), np.uint8) | |
def make_gradient(x0, color): | |
x1 = x0 + 40 | |
for x in range(x0, x1): | |
for y in range(0, 100): | |
k = (x - x0) / (x1 - x0) | |
alpha = int(round(k * 255.)) | |
color_grad = (color[0], color[1], color[2], alpha) | |
overlay[y, x, :] = color_grad | |
make_gradient(100, (255, 0, 0)) | |
make_gradient(200, (0, 255, 0)) | |
make_gradient(300, (255, 255, 0)) | |
make_gradient(400, (0, 255, 255)) | |
make_gradient(500, (250, 50, 200)) | |
mix_precise = overlay_alpha_image_precise(img, overlay, alpha=1.) | |
mix_lazy = overlay_alpha_image_lazy(img, overlay, alpha=1.) | |
cv2.imshow("mix_precise", mix_precise) | |
cv2.imshow("mix_lazy", mix_lazy) | |
#cv2.imshow("img", img) | |
#cv2.imshow("overlay", overlay) | |
cv2.waitKey() | |
test_overlay() |
There's an unpleasant edge in both versions (arguably more obvious in the precise one), possibly due to lack of alpha resolution. Might be a good idea to not do the 255 thing to alpha.
nope, did a full-float version and it still looks harsh. guess 40 steps is just not enough. dithering may do something, but meh that's too much work.
doing gamma in 0-255 space is also not a great idea, but it works out approximately. at least it always works in cases of a simple power, just not for the more interesting curves like sRGB.sqrt((255 * x1)^2 * (1-a) + (255 * x2)^2 * a) = 255 * sqrt(x1^2 * (1-a) + x2^2 * a)
. The same does not quite hold for 2.2 (wolfram alpha), though it remains very close.
difference is very hard to tell:
new code:
import numpy as np
import cv2
def overlay_alpha_image_lazy(background_rgb, overlay_rgba, alpha):
# cf https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
# If the destination background is opaque, then
# out_rgb = overlay_rgb * overlay_alpha + background_rgb * (1 - overlay_alpha)
overlay_alpha = overlay_rgba[: , : , 3] * alpha
overlay_alpha_3 = np.dstack((overlay_alpha, overlay_alpha, overlay_alpha))
overlay_rgb = overlay_rgba[: , : , : 3]
background_rgb_f = background_rgb
out_rgb = overlay_rgb * overlay_alpha_3 + background_rgb_f * (1. - overlay_alpha_3)
out_rgb *= 255
out_rgb = out_rgb.astype(np.uint8)
return out_rgb
def overlay_alpha_image_precise(background_rgb, overlay_rgba, alpha, gamma_factor=2.2):
"""
cf minute physics brilliant clip "Computer color is broken" : https://www.youtube.com/watch?v=LKnqECcg6Gw
the RGB values are gamma-corrected by the sensor (in order to keep accuracy for lower luminancy),
we need to undo this before averaging.
"""
overlay_alpha = overlay_rgba[: , : , 3] * alpha
overlay_alpha_3 = np.dstack((overlay_alpha, overlay_alpha, overlay_alpha))
overlay_rgb_squared = np.float_power(overlay_rgba[: , : , : 3], gamma_factor)
background_rgb_squared = np.float_power(background_rgb, gamma_factor)
out_rgb_squared = overlay_rgb_squared * overlay_alpha_3 + background_rgb_squared * (1. - overlay_alpha_3)
out_rgb = np.float_power(out_rgb_squared, 1. / gamma_factor)
out_rgb *= 255
out_rgb = out_rgb.astype(np.uint8)
return out_rgb
def test_overlay():
img = np.zeros((100, 800, 3), np.single)
img[ : , : , : ] = (0, 0, 1)
overlay = np.zeros((100, 800, 4), np.single)
def make_gradient(x0, color):
x1 = x0 + 40
for x in range(x0, x1):
for y in range(0, 100):
alpha = float(x - x0) / float(x1 - x0)
color_grad = (color[0] / 255., color[1] / 255., color[2] / 255., alpha)
overlay[y, x, :] = color_grad
make_gradient(100, (255, 0, 0))
make_gradient(200, (0, 255, 0))
make_gradient(300, (255, 255, 0))
make_gradient(400, (0, 255, 255))
make_gradient(500, (250, 50, 200))
mix_precise = overlay_alpha_image_precise(img, overlay, alpha=1.)
mix_lazy = overlay_alpha_image_lazy(img, overlay, alpha=1.)
cv2.imshow("mix_precise", mix_precise)
cv2.imshow("mix_lazy", mix_lazy)
#cv2.imshow("img", img)
#cv2.imshow("overlay", overlay)
cv2.waitKey()
test_overlay()
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
overlay_alpha_image_precise()
gives the following result:Compared to

overlay_alpha_image_lazy()