Python PIL image compare issue - python

I am trying to compare 2 images using PIL and the below is my scenario.
img1:
img2:
img1 = Image.open(img1.png)
img2 = Image.open(img2.png)
I have written a simple diff function which will return -1 if there is a difference or 0 if they are same.
def diff(img1, img2):
im1 = img1.load()
im2 = img2.load()
for i in range(0, img1.size[0]):
for j in range(0, img1.size[1]):
if(im1[i,j] != im2[i,j]):
return -1
return 0
I am passing the following:
diff(img2, img1.transpose(Image.FLIP_LEFT_RIGHT))
Both are exactly the same image but I get a difference. The difference seems to be at:
[27 84]
Can someone please explain me why?

"Both are exactly the same image but I get a difference."
But they're not.
You can see this, using the code below for example:
def show_diff(img1, img2):
diff = Image.new("RGB", img1.size, (255,255,255))
for x1 in range(img1.size[0]):
for y1 in range(img1.size[1]):
x2 = img1.size[0] - 1 - x1
y2 = img1.size[1] - 1 - y1
if img1.getpixel((x1,y1)) != img2.getpixel((x2,y2)):
print(x1,y1,x2,y2)
diff.putpixel((x1,y1), (255,0,0))
diff.show()
img_r = Image.open("img/pacman-r.png")
img_l = Image.open("img/pacman-l.png")
show_diff(img_r, img_l)
Which results in
(Here, any pixel that differs between the two images is colored red.)
Or with
def show_delta(img1, img2):
diff = Image.new("RGB", img1.size, (255,255,255))
for x1 in range(img1.size[0]):
for y1 in range(img1.size[1]):
x2 = img1.size[0] - 1 - x1
y2 = img1.size[1] - 1 - y1
p1 = img1.getpixel((x1,y1))
p2 = img2.getpixel((x2,y2))
p3 = round((p1[0] / 2) - (p2[0] / 2)) + 128
diff.putpixel((x1,y1), (p3,p3,p3))
diff.show()
img_r = Image.open("img/pacman-r.png")
img_l = Image.open("img/pacman-l.png")
show_delta(img_r, img_l)
which results in
(Here, equivalent pixels are gray while a white pixel signifies a pixel in img1 was set (dark) while unset in img2 and a black pixel signifies the opposite.)
It seems like you suspected that PIL's Image.transpose method caused the problem, but the source images aren't just transposed.
Image.transpose works as you'd expect -- so something like:
def diff(img1, img2):
im1 = img1.load()
im2 = img2.load()
images_match = True
for i in range(0, img1.size[0]):
for j in range(0, img1.size[1]):
if(im1[i,j] != im2[i,j]):
images_match = False
return images_match
img_r = Image.open("img/pacman-r.png")
# NOTE: **NOT** Using img_l here
print(diff(img_r, img_r.transpose(Image.FLIP_LEFT_RIGHT).transpose(Image.FLIP_LEFT_RIGHT)))
returns True.
(Here, an image is compared to a twice-transposed version of itself)

Related

Inserting 2d-array into another 2d-array, while taking values into account

Im trying to insert one picture(transparent .png) into another on certain coordinates.
While the solution from How to add an image over another image using x,y coordinates?
frame[y: y+insert_size[1], x: x+insert_size[0]] = image (where insert_size - width and height of inserted picture) works, i also dont want black pixels(thats how opencv represents transparent pixels) on the final image.
I wrote a function that iterates pixel by pixel, and while it works - it is horribly slow(it completes about 2 image inserts per second), code:
def insert_image(frame, image, insert_coordinates, masked_value):
img_height = len(image)
img_width = len(image[0])
mask = np.ndarray((3,), np.uint8, buffer=np.array(masked_value))
y_diff = 0 #current vertical position in insert picture
for y, line in enumerate(frame):
if y_diff == img_height-1:
continue #interested until last row
if y < insert_coordinates[1] or y > insert_coordinates[1]+img_height:
continue #interested only in rows that will be changed
else:
x_diff = 0 #current horizontal position in insert picture
for x, col in enumerate(line):
if x_diff == img_width-1:
continue #interested until last column
if x < insert_coordinates[0] or x > insert_coordinates[0]+img_width:
continue #interested only in columns that will be changed
else:
if (image[y_diff][x_diff] != mask).all():
frame[y][x] = image[y_diff][x_diff] #setting pixel value if its not of masked value
x_diff += 1
y_diff += 1
return frame
maybe there is a smarter way to do so?
opencv version 4.5.0
numpy version 1.20.0rc1
UPDATE:
By "insert" i do mean assign a pixel value from image to some pixel of frame.
i added data and code for reproducible example(also modified function so its a bit faster):
"frame" - original picture, that image will be added to to, has red square sized (500,500) at (100,100) coordinates
"image" - transparent .png, sized (500,500) that will be "inserted" into original frame
"result1" - result, where red pixels were replaced with black "transparent" pixels from inserted image
"result2" - desired result
code, requires opencv-python and numpy modules: example.py
import cv2
import numpy as np
import copy
def insert_image_v2(frame, image, insert_coordinates, masked_value):
img_height = len(image)
img_width = len(image[0])
mask = np.ndarray((3,), np.uint8, buffer=np.array(masked_value))
y_diff = 0
for y in range(insert_coordinates[1], insert_coordinates[1]+img_height, 1):
x_diff = 0
for x in range(insert_coordinates[0], insert_coordinates[0]+img_width, 1):
if (image[y_diff][x_diff] != mask).all():
frame[y][x] = image[y_diff][x_diff]
x_diff += 1
y_diff += 1
return frame
if __name__ == "__main__":
frame = cv2.imread('frame.png')
image = cv2.imread('image.png')
insert_size = (image.shape[0], image.shape[1])
insert_coordinates = (100, 100)
x = insert_coordinates[0]
y = insert_coordinates[1]
result1 = copy.deepcopy(frame)
result1[y: y+insert_size[1], x: x+insert_size[0]] = image
result2 = insert_image_v2(frame, image, insert_coordinates, [0,0,0])
cv2.imshow('result1', result1)
cv2.imshow('result2', result2)
cv2.imwrite('result1.jpg', result1)
cv2.imwrite('result2.jpg', result2)
print()
Found a solution in Image alpha composite with OpenCV, it is about 20 times faster than what i had. code:
import cv2
import numpy as np
import time
def insert_image_v2(frame, image, insert_coordinates):
x = insert_coordinates[0]
y = insert_coordinates[1]
insert_size = (image.shape[1], image.shape[0])
background = frame[y: y+insert_size[1], x: x+insert_size[0]]
foreground = image
kernel = np.ones((5,5), np.uint8)
image_gray = cv2.cvtColor(foreground, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(image_gray, 1, 255, cv2.THRESH_BINARY_INV)
opening = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
output = np.zeros(foreground.shape, dtype=foreground.dtype)
for i in range(3):
output[:,:,i] = background[:,:,i] * (opening/255)+foreground[:,:,i]*(1-opening/255)
frame[y: y+insert_size[1], x: x+insert_size[0]] = output
return frame
if __name__ == "__main__":
frame = cv2.imread('frame.png')
image = cv2.imread('image_1.png')
insert_size = (image.shape[0], image.shape[1])
insert_coordinates = (100, 100)
t1 = time.time()
frame = insert_image_v2(frame, image, insert_coordinates)
t2 = time.time()
print(f"{t2-t1}")
cv2.imshow('img', frame)
cv2.waitKey(0)

Efficiently transform and blend images in OpenCV

I'm making a program to simulate brush strokes with a given image, and I'm held up the actual putting the brush strokes down part. Right now I'm using OpenCV and Numpy to rotate and mix images together by setting a section of the original image to be the new one. Here's the current code.
def rotate(img, deg):
"""Rotates an image a certain number of degrees"""
h, w, c = img.shape
matrix = cv2.getRotationMatrix2D((w / 2, h / 2), deg, 1)
out = cv2.warpAffine(img, matrix, (w, h), flags=cv2.INTER_LINEAR)
return out
def paste(img1, img2, mask, x, y):
"""Pastes one image on top of another at a given location"""
h1, w1, _ = img1.shape # dimensions of original image
h2, w2, _ = img2.shape # dimensions of pasted image
if y > h1 or x > w1:
return img1
stamp = img2
factor = mask
subx = ((w2 + x) - w1)
suby = ((h2 + y) - h1)
if suby <= 0:
suby = 0
if subx <= 0:
subx = 0
stamp = img2[0:h2 - suby, 0:w2 - subx]
factor = mask[0:h2 - suby, 0:w2 - subx]
snippet = img1[y:y + (h2 - suby), x:x + (w2 - subx)]
stamp = cv2.add(cv2.multiply(snippet, (1-factor)), cv2.multiply(stamp, factor))
out = img1
out[y:y + h2, x:x + w2] = stamp
return out
It works, but only barely. There are issues that come up with certain scenarios, it's very limited as to what you're allowed to do with it, and is extremely slow. Is there an easier/better way to transform and blend images with OpenCV, or would a different framework (sprite/object based, like pygame) be better for this? Thanks!
I suggest you try seamless cloning. Check this excellent tutorial from Satya Mallick: https://www.learnopencv.com/seamless-cloning-using-opencv-python-cpp/
In this case you can substitute the plane in the tutorial for your brush.

OpenCV: augmenting video stream source .png not rendering transparency as expected

I am trying to create my own face filtering augmented reality program in open-cv. The idea is it will map a beaver to the users face. Currently, I am unable to get proper transperency when loading this image with 'cv2.imread(...)'. It looks black in the background, and often shows partially in certain areas that are white. When I open this image in photoshop, I am fully capable of moving this image on top of a background with expected transparency results. I am wondering if the alpha is not getting rendered properly. Here is the relevent code where I am loading the image in.
import numpy
import cv2
def augment_stream(face: numpy.array, augment: numpy.array) -> numpy.array:
face_h, face_w, _ = face.shape
augment_h, augment_w, _ = augment.shape
scalar = min(face_h / augment_h, face_w / augment_w)
delta_augment_h = int(scalar * augment_h)
delta_augment_w = int(scalar * augment_w)
delta_augment_shape = (delta_augment_w, delta_augment_h)
resized_augment = cv2.resize(augment, delta_augment_shape)
augmented_face = face.copy()
dark_pixels = (resized_augment < 250).all(axis=2)
offset_x = int((face_w - delta_augment_w) / 2)
offset_y = int((face_h - delta_augment_h) / 2)
augmented_face[offset_y: offset_y+delta_augment_h, offset_x: offset_x+delta_augment_w][dark_pixels] = resized_augment[dark_pixels]
return augmented_face
def main():
stream = cv2.VideoCapture(0)
cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
augment = cv2.imread('assets/normal.png')
# tmp = cv2.cvtColor(augment, cv2.COLOR_BGR2GRAY)
# _,alpha = cv2.threshold(tmp,0,255,cv2.THRESH_BINARY)
# b, g, r = cv2.split(augment)
# rgba = [b,g,r, alpha]
# dst = cv2.merge(rgba,4)
# cv2.imwrite("assets/normal.png", dst)
while True:
ret, border = stream.read()
border_h, border_w, _ = border.shape
bw = cv2.equalizeHist(cv2.cvtColor(border, cv2.COLOR_BGR2GRAY))
rects = cascade.detectMultiScale(bw, scaleFactor=1.3, minNeighbors=4, minSize=(30, 30), flags=cv2.CASCADE_SCALE_IMAGE)
for x, y, w, h in rects:
y0 = int(y - 0.25*h)
y1 = int(y + 0.75*h)
x0 = x
x1 = x + w
if x0 < 0 or x1 > border_w or y0 < 0 or y1 > border_h:
continue
border[y0: y1, x0: x1] = augment_stream(border[y0: y1, x0: x1], augment)
cv2.imshow('border', border)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
stream.release()
cv2.destroyAllWindows()
if __name__ == '__main__':
main()
Using example from question overlay a smaller image on a larger image python OpenCv
I reduced it to show only how to put image
use cv2.IMREAD_UNCHANGED to load it as RGBA.
split it to RGB and A
use A to create masks for image and border
use loop to add channels
import cv2
stream = cv2.VideoCapture(0)
# load RGBA
augment = cv2.imread('image.png', cv2.IMREAD_UNCHANGED) # load RGBA
# make it smaller then frame - only for test
W = 320
H = 240
augment = cv2.resize(augment, (W, H))
# split image and alpha
image = augment[:,:,0:3]
alpha = augment[:,:,3]
mask_image = alpha / 255.0
mask_border = 1.0 - mask_image
# ROI - region of interest
x1 = 200
y1 = 100
x2 = x1 + W
y2 = y1 + H
while True:
ret, border = stream.read()
# copy only in some region (ROI) (don't assign to variable) but gives worse result
#cv2.copyTo(image, alpha, border[y1:y2, x1:x2])
for c in range(0, 3): # channels RGB
border[y1:y2, x1:x2, c] = (image[:, :, c]*mask_image + border[y1:y2, x1:x2, c]*mask_border)
cv2.imshow('border', border)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
stream.release()
cv2.destroyAllWindows()
BTW: I tried to use cv2.copyTo(image, mask_image, border) but it gives worse result - maybe mask/alpha needs 3 channels.
It seems it can be done in C/C++ - how to insert a small size image on to a big image

Crop overlapping images with pillow

I need help, please.
I'm trying to select and crop the overlapping area of two images with the Python Pillow library.
I have the upper-left pixel coordinate of the two pictures. With these, I can find out which one is located above the other.
I wrote a function, taking two images as arguments:
def function(img1, img2):
x1 = 223 #x coordinate of the first image
y1 = 197 #y coordinate of the first image
x2 = 255 #x coordinate of the second image
y2 = 197 #y coordinate of the second image
dX = x1 - x2
dY = y1 - y2
if y1 <= y2: #if the first image is above the other
upper = img1
lower = img2
flag = False
else:
upper = img2
lower = img1
flag = True
if dX <= 0: #if the lower image is on the left
box = (abs(dX), abs(dY), upper.size[0], upper.size[1])
a = upper.crop(box)
box = (0, 0, upper.size[0] - abs(dX), upper.size[1] - abs(dY))
b = lower.crop(box)
else:
box = (0, abs(dY), lower.size[0] - abs(dX), upper.size[1])
a = upper.crop(box)
box = (abs(dX), 0, lower.size[0], upper.size[1] - abs(dY))
b = lower.crop(box)
if flag:
return b,a #switch the two images again
else:
return a,b
I know for sure that the result is wrong (It's a school assignment).
Thanks for your help.
First of all, I don't quite get what do you mean by one picture being "above" the other (shouldn't that be a z-position?), but take a look at this: How to make rect from the intersection of two? , the first answer might be a good lead. :)

What's the most efficient way to select a non-rectangular ROI of an Image in OpenCV?

I want to create a binary image mask, containing only ones and zeros in python. The Region of Interest(white) is non-rectangular, defined by 4 corner points and looks for example as follows:
In my approach, I first calculate the line equation of the upper and lower ROI border and then I check for each mask element, if it's smaller or bigger than the boarders. The code is working, but far to slow. A 2000x1000 mask takes up to 4s of processing my machine.
from matplotlib import pyplot as plt
import cv2
import numpy as np
import time
def line_eq(line):
"""input:
2 points of a line
returns:
slope and intersection of the line
"""
(x1, y1), (x2, y2) = line
slope = (y2 - y1) / float((x2 - x1))
intersect = int(slope * (-x1) + y1)
return slope,intersect
def maskByROI(mask,ROI):
"""
input:
ROI: with 4 corner points e.g. ((x0,y0),(x1,y1),(x2,y2),(x3,y3))
mask:
output:
mask with roi set to 1, rest to 0
"""
line1 = line_eq((ROI[0],ROI[1]))
line2 = line_eq((ROI[2],ROI[3]))
slope1 = line1[0]
intersect1 = line1[1]
#upper line
if slope1>0:
for (x,y), value in np.ndenumerate(mask):
if y > slope1*x +intersect1:
mask[x,y] = 0
else:
for (x,y), value in np.ndenumerate(mask):
if y < slope1*x +intersect1:
mask[x,y] = 0
#lower line
slope2 = line2[0]
intersect2 = line2[1]
if slope2<0:
for (x,y), value in np.ndenumerate(mask):
if y > slope2*x +intersect2:
mask[x,y] = 0
else:
for (x,y), value in np.ndenumerate(mask):
if y < slope2*x +intersect2:
mask[x,y] = 0
return mask
mask = np.ones((2000,1000))
myROI = ((750,0),(900,1000),(1000,1000),(1500,0))
t1 = time.time()
mask = maskByROI(mask,myROI)
t2 = time.time()
print "execution time: ", t2-t1
plt.imshow(mask,cmap='Greys_r')
plt.show()
What is a more efficient way to create a mask like this?
Are there any similar solutions for non-rectangular shapes provided by
numpy, OpenCV or a similar Library?
Draw the mask with fillPoly:
mask = np.ones((1000, 2000)) # (height, width)
myROI = [(750, 0), (900, 1000), (1000, 1000), (1500, 0)] # (x, y)
cv2.fillPoly(mask, [np.array(myROI)], 0)
This should take ~1ms.

Categories

Resources