Concatenating Numpy arrays for OpenCV imshow - python

Using OpenCV and Python, I want to display the left hand half of one image concatenated with the right-hand half of another image, both of the same size - 512x512 pixels. I have identified several ways of doing this, but I am confused about the behaviour of one method. In the following code, assume that only one of the methods is used at any one time and the rest are commented out:
import cv2
import numpy as np
image1 = cv2.imread('img1.png',0)
image2 = cv2.imread('img2.png',0)
#Method 1 - works
image3 = np.concatenate([image1[:,0:256], image2[:,256:512]], axis=1)
#Method 2 - works
image3 = image1[:,:]
image3[:,256:512] = image2[:,256:512]
#Method 3 - works if I don't create image3 with np.zeros first.
#Otherwise displays black image - all zeros - but print displays correct values
image3 = np.zeros(shape=(512,512), dtype=int)
image3[:,0:256] = image1[:,0:256]
image3[:,256:512] = image2[:,256:512]
print(image3)
cv2.imshow("IMAGE", image3)
cv2.waitKey(0)
cv2.destroyAllWindows()
In method 3, I at first mistakenly thought that the new numpy array image 3 would need to be created first and so created an array filled with zeros and then seemingly overwrote that array with the correct values. When I print that array it displays the correct values, but when I show it as an image using cv2.imshow it is all black (i.e. all zeros). Why the difference? I understand that slicing creates a view, not a copy, but can someone please explain what is happening in method 3 and why cv2.imshow displays the underlying array but print doesn't.

Your problem is in:
np.zeros(shape=(512,512), dtype=int)
imshow will show images coded as float(32 bit) with a range of 0.-1. or 8bit(1-4 channels) with a range of 0-255. You are using int, which is 32 bit (in most cases) and it is not a floating point. What you should do to fix it, is to use np.uint8.
np.zeros(shape=(512,512), dtype=np.uint8)
I think also it can be displayed using matplotlib if you want to keep the int, but I am not 100% sure about it.

Related

Unclear difference in displaying the same image by opencv and matplotlib [with example code & exported .npy file]

In continue to my previous question (that still not answered here)
Here is a simple code with some unsuccessful attempts and exported image.npy file that illustrates the problem:
'''
conda install -c conda-forge opencv
pip install numpy
pip install matplotlib
'''
import cv2
import numpy as np
import matplotlib.pyplot as plt
if __name__ == "__main__":
image = np.load('image.npy') # Link to the file under this code block
print(image.shape) # output: (256, 256, 1)
print(image.dtype) # output: float64
# Unsuccessful attempts:
# image[np.where(image.max() != 255)] = 0
# max_img = image.max(axis=0)
# int_image = image.astype(int)
Link to download the image.npy file
And when I display it with opencv using the following code:
cv2.imshow('image', image)
cv2.waitKey()
I get an image like the following result:
In contrast, when I display it with matplotlib using the following code:
plt.imshow(image, cmap="gray")
(The 'cmap' parameter is not the issue here, It's only plot the image in Black & White)
I get an image the following result:
The second result is the desired one as far as I'm concerned -
my question is how to make the image like this (by code only and without the need to save to a file and load the image) and make it so that I get the same image in opencv as well.
I researched the issue but did not find a solution.
This reference helps me understand the reason in general but I'm still don't know how to show the image in opencv like matplotlib view in this case.
Thank you!
Finally, I found the solution:
int_image = image.astype(np.uint8)
cv2.imshow('image', int_image)
cv2.waitKey()
plt.imshow(image, cmap="gray")
plt.title("image")
plt.show()
Now - The 2 plots are same.
Hope this helps more people in the future
You can use the following steps:
load the data from the file
truncate the data into 0-255 values, same type as original (float64)
filtering using the == operator, which gives True/False values, then multiplying by 255 to get 0/255 integer values
use cv2.imshow combined with astype to get the required type
import numpy as np
import cv2
if __name__ == '__main__':
data = np.load(r'image.npy')
data2 = np.trunc(data)
data3 = (data2 == 255) * 255
cv2.imshow('title', data3.astype('float32'))
cv2.waitKey(0)
You have very few unique values in image, and their distribution is severely skewed. The next smallest value below 255 is 3.something, leaving a huge range of values unused.
image.max() == 255
sorted(set(im.flat)) == [0.0, 0.249, 0.499, 0.748, 0.997, 1.246, 1.496, 1.745, 1.994, 2.244, 2.493, 2.742, 2.992, 3.241, 3.490, 3.739, 3.989, 255.0]
cv.imshow, given floats, will map 0.0 to black and 1.0 to white. That is why your picture looks like
Your options:
convert to uint8 using image.astype(np.uint8) because cv.imshow, given uint8 data, maps 0 to black and 255 to white
divide by 255, so your values, being float, range from 0 to 1
normalize by whatever way you want, but remember how imshow behaves given a certain element type and range of values. It also accepts uint16, but not int (which would be int32/int64).
The other answer, recommending np.trunc and stuff, is just messing around. The trunc doesn't change any of the 255.0 values (in the comparison), it's redundant. There's no need to threshold the data, unless you need such a result. It is also wrong in that it tells you to take a binary array, blow its value range up 0 to 255, and then convert to float, which is pointless, because imshow, given float, expects a value range of 0 to 1. Either the *255 step is pointless, or the astype step should have used uint8.

What is the difference between PIL.ImageChops.subtract and first converting to numpy arrays and then subtract those?

So I want to subtract Image2 from Image1 using Pillow. I first tried to do this by converting them to numpy arrays, subtract those and then converting the arrays back to Images. But this gave me very strange behavior s.t. large parts of the difference picture became white.
I found that i can do the same thing directly using PIL.IamgeChops.subtract which works fine, but I would like to understand why the array way does not work.
Here ist my Code:
import os.path
from PIL import Image, ImageShow, ImageChops
root = os.path.abspath(os.path.curdir)
Image1 = Image.open(
root + "\\Messwerte\\Messungen_20_11\\AOM_Copy\\1.bmp"
).convert("L")
Image2 = Image.open(
root + "\\Messwerte\\Messungen_20_11\\B Feld Abh\\hintergrund_B_3.bmp"
).convert("L")
DiffImage = ImageChops.subtract(Image1, Image2) #this way works perfectly
DiffImage.show()
DiffImageArray = np.array(DiffImage)
arr1 = np.array(Image1)
arr2 = np.array(Image2)
diff = arr1 - arr2 #this doesn't work
Image.fromarray(diff).show()
print(np.max(DiffImageArray)) #this gives me 77
print(np.max(diff)) # and this 255
I also checked that the error does not lie in the converting from array to Image object.
It's because the the type of the image in unit8 and when you do the subtraction, any negative values get circled back up-to 255 because the data-format cannot handle negative integers. That is, 7-8 would be stored as 255 as opposed to -1, 7-9 would be 254 as opposed to -2 and so on. You would need bigger sized data-type and you would also need to take the absolute value of the result of subtraction (or clip negative values to zero depending on use-case) as opposed to just a simple subtraction.
You can solve this by simply doing
arr1 = np.array(Image1).astype(np.int32)
arr2 = np.array(Image2).astype(np.int32)
diff = np.abs(arr1 - arr2)
when you create the arrays.

cv2 convert Range and copyTo functions of c++ to python

I'm writing a python video stabilizer and in some part of the code i need to copy 2 images into a canvas.
I tried to convert this c++ code to python but i wasn't able.
Mat cur2;
warpAffine(cur, cur2, T, cur.size());
cur2 = cur2(Range(vert_border, cur2.rows-vert_border),
Range(HORIZONTAL_BORDER_CROP, cur2.cols-HORIZONTAL_BORDER_CROP));
// Resize cur2 back to cur size, for better side by side comparison
resize(cur2, cur2, cur.size());
// Now draw the original and stablised side by side for coolness
Mat canvas = Mat::zeros(cur.rows, cur.cols*2+10, cur.type());
cur.copyTo(canvas(Range::all(), Range(0, cur2.cols)));
cur2.copyTo(canvas(Range::all(), Range(cur2.cols+10, cur2.cols*2+10)));
I wrote this code but i got error:
ret, frame = cap.read()
new_frame = transform(frame,data[counter]) #some kind of low pass filter
canvas = np.zeros ((frame_height, frame_width*2+10,3))
np.copyto (canvas[:frame_width], frame)
np.copyto (canvas[frame_width+10:frame_width*2+10], new_frame)
I got
"couldnt boradcast from shape into shape"
err. But i think i used canvas in wrong way. in cpp code there is canvas(Range::all(), Range(0, cur2.cols)) which i dont know how to use it in python
How can i use Range function and copyTo function in python?
And how should i copy an image to a specific part of canvas?
Any help?
cv::Mat are actually numpy arrays in python. And in this case, you should use numpy functions and not OpenCV ones.
For the copyTo as clone, use copy() as in:
a = np.zeros((10,10,3), dtype=np.uint8)
b = a.copy()
For ranges, in numpy is easier... just use:
a[y1:y2, x1:x2,:]
which means from row y1 to row y2 and from column x1 to column x2. In case you need all, just leave the : alone like all rows:
a[:, x1:x2,:]
The last colon is for channels, in this case all channels, but you can also limit it. And if you need only 1 column, or channel you can put the number directly instead of using a "range" like
a[4, x1:x2, 0]
You can also drop the last colon of the channels, and it will use all of them. Like:
a[1:3, 4:8]
Finally, to copy a value to a place in the image you can do something like:
bigImage[y1:y2, x1:x2] = image
You have to make sure that image fits in this place (channels included). That means, if image is of size 640x480 you can not do this:
bigImage[10:20, 20:30] = image
but you can do something like
bigImage[10:20, 20:30] = image[10:20, 10:20]
assuming both have the same number of channels

Storing an image dataset into 4D array then showing each image leads to distorted colors

I am trying to store an image dataset into a 4D ndarray then plot each image as follows:
i=0
for j in imagelist:
imageall[i] = misc.imread(j) ##(36, 570, 760, 3)
plt.imshow(imageall[i])
plt.show()
i=i+1
However, showing the image from the 4D ndarray gives a bluish image whereas simply reading the image and plotting it shows the image in its normal coloring.
I have compared channels (visually and by computing means in the 2 cases and they are exactly the same).
Can anyone explain the reason for change in displayed image coloration when reading single image and when reading to a 4D ndarray?
Your images have the same channel values as you noted in the question, so the difference in the result suggests that your values are being interpreted differently by plt.imshow. There's some magic to how plt.imshow interprets images based on type, so the most likely reason is that your original array is initialized with the wrong dtype.
Assuming that your pre-allocation is just something like
import numpy as np
imageall = np.empty((n_img,width,height,3))
# or imageall = np.zeros((n_img,width,height,3))
the resulting array will automatically have double type, i.e. dtype=np.float64. When you mutate this array with each image, the input dtype=np.uint8 (as returned from plt.imread) is converted to double, effectively doing
imageall[i] = misc.imread(j).astype(np.float64)
So your channel values ranging from 0 to 255 are stored as floats, which is then misinterpreted by plt.imshow.
You just need to pre-allocate with the right dtype:
imageall = np.empty((n_img,width,height,3),dtype=np.uint8)

How to subtract two images using python opencv2 to get the foreground object

Is there any way to subtract two images in python opencv2 ?
Image 1 : Any image (eg. a house Image) (static image)
Image 2 : The same Image with an Object (In house, a person is standing...)
(static image + dynamic objects)
Image 3 = Image 2 - Image 1
If we subtract Image2 from Image1 means Image3 should give Object(person) only.
Try background subtraction.
Use cv2.subtract(img1,img2) instead of arithmetic operation, as cv2 will take care of negative values.
If the background in the two images are exactly the same, you can subtract them as you mention in your post.
image1 = imread("/path/to/image1")
image2 = imread("/path/to/image2")
image3 = image1 - image2
#dvigneshwr's answer does a subtraction where the resulting negative values are rounded up to 0. #Taha Anwar M-Holmes' answer preserves the negatives but changes the datatype of the resulting array so its no longer a conventional image type.
For those wanting to identify a foreground from a background image based on the absolute difference in values and return an array of the same data type as the inputs (that's how I ended up here), use absdiff.
Assuming the arrays are the same width and height...
import cv2 as cv
image3 = cv.absdiff(image1, image2)
Its worth noting that the OP did not provide any details wrt to the images being subtracted here... depending on what the contents of the images are, all of these approaches may answer the OP's question.
cv2.subtract does not work it just binds the values between 0-255 so if you wanna get negative values just convert the image from unit8 to int32 or int64. Note unint8 could only take on the values of 0-255 thus it could not handle negative values
image1= np.int32(image1)
image2= np.int32(image2)
image3 = image1 - image2

Categories