Question: I have 750+ images, like this 'test.png', that I need to subtract the vignetting in 'vig-raw.png' from. I just started using opencv-python, so "I don't even know what I don't know". Using GIMP, I desaturated 'vig-raw.png' to create 'vig-desat.png', which I then converted with Color to Alpha to create 'vig-alpha.png'. This is my attempt to subtract 'vig-alpha.png' from 'test.png'. import cv2 as cv import numpy as np img1 = cv.imread('test.png',0) img1 = cv.cvtColor(img1, cv.COLOR_BGR2BGRA) # add alpha channel to RGB image print(img1[0][0]) # show alpha img2 = cv.imread('vig-alpha.png',flags=cv.IMREAD_UNCHANGED) # read RGBA image print(img2[0][0]) #show alpha img3 = cv.subtract(img1, img2) img3 = cv.resize(img3, (500,250)) print(img3[0][0]) # show alpha cv.imshow('result',img3) cv.waitKey() cv.destroyAllWindows()  However, this is the 'result'. I need to produce a uniform shading throughout the image while leaving the original colors intact. I don't know the correct terminology for this sort of thing, and it's hard to search for a solution with what I do know. Thanks in advance. EDIT: As per Rotem's answer, image file format matters. StackOverflow converted the PNG files I posted to JPEG, which did effect results while checking their answer. See the comment I left on Rotem's answer below for more information.

Answer: Vignette template is not supposed to be subtracted, it supposed to be scaled. The vignette correction process is known as Flat-field correction applies: G = m / (F - D) C = (R - D) * G When D is dark field or dark frame. We don't have dark frame sample - we may assume that the dark frame is all zeros. Assuming D=zeros, the correction formula is: G = m / F C = R * G m = mean(F), and F applies vig-alpha. R is test.png.  For computing G (name it inv_vig_norm, we may use the following stages):  Read vig-alpha.png as grayscale, and convert it to float in range [0, 1] (vig_norm applies F):  vig = cv2.imread('vig-alpha.png', cv2.IMREAD_GRAYSCALE) vig_norm = vig.astype(np.float32) / 255   Divide m by F:  vig_mean_val = cv2.mean(vig_norm)[0] inv_vig_norm = vig_mean_val / vig_norm # Compute G = m/F   Compute C = R * G - scale img1 by inv_vig_norm:  inv_vig_norm = cv2.cvtColor(inv_vig_norm, cv2.COLOR_GRAY2BGR) img2 = cv2.multiply(img1, inv_vig_norm, dtype=cv2.CV_8U) # Compute: C = R * G    For removing noise and artifacts, we may apply Median Blur and Gaussian Blur over vig (it may be required because the site converted vig-alpha.png to JPEG format).  Code sample: import cv2 import numpy as np img1 = cv2.imread('test.png') vig = cv2.imread('vig-alpha.png', cv2.IMREAD_GRAYSCALE) # Read vignette template as grayscale vig = cv2.medianBlur(vig, 15) # Apply median filter for removing artifacts and extreem pixels. vig_norm = vig.astype(np.float32) / 255 # Convert vig to float32 in range [0, 1] vig_norm = cv2.GaussianBlur(vig_norm, (51, 51), 30) # Blur the vignette template (because there are still artifacts, maybe because SO convered the image to JPEG). #vig_max_val = vig_norm.max() # For avoiding "false colors" we may use the maximum instead of the mean. vig_mean_val = cv2.mean(vig_norm)[0] # vig_max_val / vig_norm inv_vig_norm = vig_mean_val / vig_norm # Compute G = m/F inv_vig_norm = cv2.cvtColor(inv_vig_norm, cv2.COLOR_GRAY2BGR) # Convert inv_vig_norm to 3 channels before using cv2.multiply. https://stackoverflow.com/a/48338932/4926757 img2 = cv2.multiply(img1, inv_vig_norm, dtype=cv2.CV_8U) # Compute: C = R * G cv2.imshow('inv_vig_norm', cv2.resize(inv_vig_norm / inv_vig_norm.max(), (500, 250))) # Show inv_vig_norm for testing cv2.imshow('img1', cv2.resize(img1, (500, 250))) cv2.imshow('result', cv2.resize(img2, (500, 250))) cv2.waitKey() cv2.destroyAllWindows()   Results: img1:  inv_vig_norm:  img2: