Both techniques should result in similar results. Why is the image
getting inverted in the for loop technique?
Because you should've done this way, converting data type:
imgc = np.uint64(img_paris.copy()) # <-- convert datatype
for x in range(yellow_image.shape[0]):
for y in range(yellow_image.shape[1]):
imgc[x,y] = (imgc[x,y] * yellow_image[x,y])
Explanation part 1 (dtype)
This is because dtype changes when reassigning the full matrix, while it does not happen reassigning slices:
a = np.array([[1]], np.uint8)
b = np.array([[1]], np.float64)
a[0] = a[0] * b[0] # assigning slices dtype of a does not change
print(a.dtype) #=> uint8
a = a * b # while assigning the full matrix it does
print(a.dtype) #=> float64
If you print your dtype along the way you see this:
yellow_image_1 = np.ones(img_paris.shape) * 255
print(yellow_image_1.dtype) #=> float64
yellow_image_1[:,:,0] *= 0
print(yellow_image_1.dtype) #=> float64
imgc_1 = img_paris.copy()
print(imgc_1.dtype) #=> uint8
imgc_1 = (imgc_1 * yellow_image_1)
print(imgc_1.dtype) #=> float64
And this:
yellow_image_2 = np.ones(img_paris.shape) * 255
print(yellow_image_2.dtype) #=> float64
yellow_image_2[:,:,0] *= 0
print(yellow_image_2.dtype) #=> float64
imgc_2 = img_paris.copy()
print(imgc_2.dtype) #=> uint8
for x in range(yellow_image_2.shape[0]):
for y in range(yellow_image_2.shape[1]):
imgc_2[x,y] = (imgc_2[x,y] * yellow_image_2[x,y])
print(imgc_2.dtype) #=> uint8
So you end up with different dtype matrix.
Explanation part 2 (OpenCV BGR)
As mentioned, keep in mind that OpenCv uses the BGR format and each pixel value goes from 0 to 255, says np.uint8.
So, if you are using for example matplotlib, for showing the image you must swap the B and R channels:
img_paris = cv2.imread('3ClnT.jpg')
plt.imshow(img_paris[:,:,::-1])
While this is not required if you are saving using cv2.imwrite() or cv2imshow(), for example:
cv2.imwrite('paris.jpg', img_paris)
That said, you can generate your solid yellow image using this one liner command:
yellow_image = np.ones_like(img_paris) * (0, 255, 255)
And show it or save it:
plt.imshow(yellow_image[:,:,::-1])
cv2.imwrite('solid_yellow.jpg', yellow_image)
Now, your result from the multiplication paris_yellow = img_paris * yellow_image results in a value greater than 255:
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
So, when you multiply you end up with a max pixel value which can be 255 * 255 = 65025.
You then need:
- convert terms of multiplication to a datatype that supports integer up to
65025
- After multiplication, normalize then convert back to
uint8
This is an example:
paris_yellow_2 = np.int64(img_paris) * np.int64(yellow_image) # <- use int64 terms
max_px_val = np.amax(paris_yellow_2) # <-- Max pixel alue
paris_yellow_2 = np.uint8((paris_yellow_2/max_px_val) * 255) # <- normalize and convert back to uint8
plt.imshow(paris_yellow_2[:,:,::-1])
And this is the result:

Other option, which gives a different result is to multiply the G and R channels for a coefficient greater than 1 then crop values >255. In this case you need to use a float dtype:
paris_yellow_3 = np.float64(img_paris) * (1, 3, 3)
paris_yellow_3[paris_yellow_3 > 255] = 255 # <- crops to 255 pixels values > 255
paris_yellow_3 = paris_yellow_3.astype(np.uint8) # <- back to uint8
In this case B is multiplied by 1 (no change), G and R are multiplied by 3, giving the following result:
