opencv: how to find contours in a single depth image? - python

I'm getting the following error when trying to find contours in a single depth image. I already have a single depth img, I believe I don't need to use cv.cvtcolor.
img = np.random.rand(224,224)
img.astype('uint8')
threshold_value = int(np.max(img) * 0.2)
print(threshold_value)
_, img = cv.threshold(img, threshold_value, 255, cv.THRESH_BINARY)
plt.imshow(img)
plt.show()
_, contours, _ = cv.findContours(img,
cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
plt.imshow(img)
plt.show()
error:
FindContours supports only CV_8UC1 images when mode !=
CV_RETR_FLOODFILL otherwise supports CV_32SC1 images only in function
'cvStartFindContours_Impl'

img.astype('uint8')
does nothing. you should assign the result to something, like:
img = img.astype('uint8')
then you won't get the error you described.
but the results still won't be as you might expect, because np.random.rand() assigns values in range (0..1) and by converting them to uint8 everything becomes zero. might want to fix that part as well.

8UC1 means 8bit pixels, therefore try using the converting image into grayscale.

You should first convert values so that the values are up to 255. Then, you should transform the type. Something like this:
img = np.random.rand(224,224)
# Take values from 0-1 to 0-255
img *= 255
# Convert type
img = img.astype('uint8')
(...)

Related

opencv save filtered Image

I'm trying to save a filtered/hybrid image after manipulating it with OpenCV in Jupyter Notebook on a mac. Everything goes fine until I try to save the image. I have tried saving it as .jpg, .bmp. and .png. I've tried each of those and multiplying the image by 255 as well as not multiplying. The resulting saved file is either completely black or completely white depending on whether I multiplied by 255.
im1 = cv2.imread(im1_file, cv2.IMREAD_GRAYSCALE)
im1 = cv2.cvtColor(im1, cv2.COLOR_BGR2GRAY) / 255.0
# filtering and cropping operations
cv2.imwrite('hybrid_image.jpg', cropped_image*255)
I also tried 'converting' it back into a bgr image. But it gives an error of 'depth' is 6 (CV_64F)
cropped_image_255 = cropped_image*255.0
convertedimage = cv2.cvtColor(cropped_image_255, cv2.COLOR_GRAY2BGR)
cv2.imwrite('hybrid_image.bmp', convertedimage)
The fix for your problem is converting the Image type to unsigned 8-bit integer before saving.
You can replace the following line:
cv2.imwrite('hybrid_image.jpg', cropped_image*255)
with
cropped_image = cropped_image * 255 # Still (can be) floats
cv2.imwrite('hybrid_image.jpg', cropped_image.astype(np.uint8))
Please make sure that before converting to np.uint8 your image doesn't have any numbers above 255. Or else, OpenCV will clamp them to white pixel (255 value).

In opencv's findContours, what does cv.RETR_FLOODFILL do?

In the opencv documentation it says:
If mode equals to RETR_CCOMP or RETR_FLOODFILL, the input can also be
a 32-bit integer image of labels (CV_32SC1).
After sending a converted image to the function, I got 180k contours, resulting the black mess below, if I plot them. So what does RETR_FLOODFILL do and how do I use it correctly?
img = cv2.imread("lena.png")
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_, contours, _ = cv2.findContours(image=np.array(img, dtype=np.int32), mode=cv2.RETR_FLOODFILL, method=cv2.CHAIN_APPROX_SIMPLE)
len(contours)
Out[7]: 183295
I think it does whatever function cv2.floodFill does. Finds all the pixels that are:
1-connected to each other
2-have intensity values close to each other
and consider them as a connected component. For example In your sample image, you have 183295 groups of pixels that are stick together and have a close intensity.

Determine if a specific image is contained within another, with a simple True/False

I would like to know if a big image contains a small image. The small image can be semi-transparent (similar to watermark, so it's not a fully filled photo). I've tried following different SO answers on this topic, but they're all matching the EXACT photo, but what I am looking for is whether the photo exists with 80% accuracy as the photo will be a lossy rendered version of the original one.
This is a procedure of how the images I am searching in will be generated:
Use any photo, put a semi-transparent "watermark" on it within Photoshop and save it. Then I want to check if the "watermark" exists within created photo with certain percent of accuracy (80% is good enough).
I've tried using the original template matching example provided on their docs page but I'm getting barely any match at all.
This is the code I'm using:
import cv2
import numpy as np
img_rgb = cv2.imread('photo2.jpeg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('small-image.png', 0)
w, h = template.shape[::-1]
res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
threshold = 0.7
loc = np.where( res >= threshold)
for pt in zip(*loc[::-1]):
cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 2)
cv2.imshow('output', img_rgb)
cv2.waitKey(0)
Here are the photos I've been using for the test, as this is something similar I am trying to make a match on.
small-image.png
photo2.jpeg
I am assuming the whole watermark will have the same RGB values and the text will have a little different RGB values otherwise this technique will not work. Based on this we can obtain the RGB values of a pixel of the small image and treated it as a mask by using cv2.inRange to find those pixel values in the large image. Similarly a mask is also created for the small image using those pixel values.
small = cv2.imread('small_find.png')
large = cv2.imread('large_find.jpg')
pixel = np.reshape(small[3,3], (1,3))
lower =[pixel[0,0]-10,pixel[0,1]-10,pixel[0,2]-10]
lower = np.array(lower, dtype = 'uint8')
upper =[pixel[0,0]+10,pixel[0,1]+10,pixel[0,2]+10]
upper = np.array(upper, dtype = 'uint8')
mask = cv2.inRange(large,lower, upper)
mask2 = cv2.inRange(small, lower, upper)
I had to take a buffer value of 20 because the values were not clearly matching in the large image otherwise only 1 is enough in either upper or lower. Then we find contours in mask and find values of its bounding rectangle which is cut out and reshaped to the size of mask2.
im, contours, hierarchy = cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
#cv2.drawContours(large, contours, -1, (0,0,255), 1)
cnt = max(contours, key = cv2.contourArea)
x,y,w,h = cv2.boundingRect(cnt)
wanted_part = mask[y:y+h, x:x+w]
wanted_part = cv2.resize(wanted_part, (mask2.shape[1], mask2.shape[0]), interpolation = cv2.INTER_LINEAR)
The two masks side by side (inverted them otherwise they were not visible).
For comparing they you can use any parameter and check whether it satisfies your condition or not. I used mean square error and got error of only 6.20 which is very low.
def MSE(img1, img2):
squared_diff = img1 - img2
summed = np.sum(squared_diff)
num_pix = img1.shape[0] * img1.shape[1] #img1 and 2 should have same shape
err = summed / num_pix
return err

How do I adjust brightness, contrast and vibrance with opencv python?

I am new to image processing. I program in Python3 and uses the OpenCV image processing library.I want to adjust the following attributes.
Brightness
Contrast
Vibrance
Hue
Saturation
Lightness
For 4, 5, 6. I am using the following code to convert to HSV space.
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
h += value # 4
s += value # 5
v += value # 6
final_hsv = cv2.merge((h, s, v))
img = cv2.cvtColor(final_hsv, cv2.COLOR_HSV2BGR)
The only tutorial I found for 1 and 2 is here. The tutorial uses C++, but I program in Python. Also, I do not know how to adjust 3. vibrance. I would very much appreciate the help, thanks!.
Thanks to #MarkSetchell for providing the link.
In short, the answers uses numpy only and the formula can be presented as in below.
new_image = (old_image) × (contrast/127 + 1) - contrast + brightness
Here contrast and brightness are integers in the range [-127,127]. The scalar 127 is used for this range.
Also, below is the code I used.
brightness = 50
contrast = 30
img = np.int16(img)
img = img * (contrast/127+1) - contrast + brightness
img = np.clip(img, 0, 255)
img = np.uint8(img)
a simple way for brightness adjustment, proper for both color and monochrome images is
img = cv2.imread('your path',0)
brt = 40
img[img < 255-brt] += brt
cv2.imshow('img'+ img)
where brt could be a positive number for increase brightness or a negative for darkness.
The following links for a before and after of an image processed in this code, when the brt = 40 :
input image
output image
I am not sure if this would help, but for changing Brightness, Contrast I personally switch the image to PIL.Image and use PIL.ImageEnhance which comes in handy when using the ratios or percentages.
image = PIL.Image.open("path_to_image")
#increasing the brightness 20%
new_image = PIL.ImageEnhance.Brightness(image).enhance(1.2)
#increasing the contrast 20%
new_image = PIL.ImageEnhance.Contrast(image).enhance(1.2)
I still have not found a clean way for Vibrance. For more on ImageEnahance, I'd suggest to read the official doc - https://pillow.readthedocs.io/en/stable/reference/ImageEnhance.html
For Conversion, I use this ..
NOTE - OpenCV uses BGR and PIL uses RGB channels. So, can get messy if not converted properly.
#convert pil.image to opencv (numpy.ndarray)
#need numpy library for this
cv_image = numpy.array(pil_image)
#convert opencv to pil.image
image = cv2.cvtColor(cv_image, cv2.COLOR_BGR2RGB)
pil_image = Image.fromarray(image)
Here is one way to do the vibrance in Python/OpenCV.
Convert to HSV. Then create a sigmoid function LUT.
(The sigmoid function increases linearly from the origin, but then tapers off to flat.)
See https://en.wikipedia.org/wiki/Sigmoid_function
Apply the LUT to S channel.
Convert back to BGR.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('yellow_building.jpg')
# convert image to hsv colorspace as floats
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
print(np.amax(s), np.amin(s), s.dtype)
# set vibrance
vibrance=1.4
# create 256 element non-linear LUT for sigmoidal function
# see https://en.wikipedia.org/wiki/Sigmoid_function
xval = np.arange(0, 256)
lut = (255*np.tanh(vibrance*xval/255)/np.tanh(1)+0.5).astype(np.uint8)
# apply lut to saturation channel
new_s = cv2.LUT(s,lut)
# combine new_s with original h and v channels
new_hsv = cv2.merge([h,new_s,v])
# convert back to BGR
result = cv2.cvtColor(new_hsv, cv2.COLOR_HSV2BGR)
# save output image
cv2.imwrite('yellow_building_vibrance.jpg', result)
# display images
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:

OpenCV, Python: How to use mask parameter in ORB feature detector

By reading a few answers on stackoverflow, I've learned this much so far:
The mask has to be a numpy array (which has the same shape as the image) with data type CV_8UC1 and have values from 0 to 255.
What is the meaning of these numbers, though? Is it that any pixels with a corresponding mask value of zero will be ignored in the detection process and any pixels with a mask value of 255 will be used? What about the values in between?
Also, how do I initialize a numpy array with data type CV_8UC1 in python? Can I just use dtype=cv2.CV_8UC1
Here is the code I am using currently, based on the assumptions I'm making above. But the issue is that I don't get any keypoints when I run detectAndCompute for either image. I have a feeling it might be because the mask isn't the correct data type. If I'm right about that, how do I correct it?
# convert images to grayscale
base_gray = cv2.cvtColor(self.base, cv2.COLOR_BGRA2GRAY)
curr_gray = cv2.cvtColor(self.curr, cv2.COLOR_BGRA2GRAY)
# initialize feature detector
detector = cv2.ORB_create()
# create a mask using the alpha channel of the original image--don't
# use transparent or partially transparent parts
base_cond = self.base[:,:,3] == 255
base_mask = np.array(np.where(base_cond, 255, 0))
curr_cond = self.base[:,:,3] == 255
curr_mask = np.array(np.where(curr_cond, 255, 0), dtype=np.uint8)
# use the mask and grayscale images to detect good features
base_keys, base_desc = detector.detectAndCompute(base_gray, mask=base_mask)
curr_keys, curr_desc = detector.detectAndCompute(curr_gray, mask=curr_mask)
print("base keys: ", base_keys)
# []
print("curr keys: ", curr_keys)
# []
So here is most, if not all, of the answer:
What is the meaning of those numbers
0 means to ignore the pixel and 255 means to use it. I'm still unclear on the values in between, but I don't think all nonzero values are considered "equivalent" to 255 in the mask. See here.
Also, how do I initialize a numpy array with data type CV_8UC1 in python?
The type CV_8U is the unsigned 8-bit integer, which, using numpy, is numpy.uint8. The C1 postfix means that the array is 1-channel, instead of 3-channel for color images and 4-channel for rgba images. So, to create a 1-channel array of unsigned 8-bit integers:
import numpy as np
np.zeros((480, 720), dtype=np.uint8)
(a three-channel array would have shape (480, 720, 3), four-channel (480, 720, 4), etc.) This mask would cause the detector and extractor to ignore the entire image, though, since it's all zeros.
how do I correct [the code]?
There were two separate issues, each separately causing each keypoint array to be empty.
First, I forgot to set the type for the base_mask
base_mask = np.array(np.where(base_cond, 255, 0)) # wrong
base_mask = np.array(np.where(base_cond, 255, 0), dtype=uint8) # right
Second, I used the wrong image to generate my curr_cond array:
curr_cond = self.base[:,:,3] == 255 # wrong
curr_cond = self.curr[:,:,3] == 255 # right
Some pretty dumb mistakes.
Here is the full corrected code:
# convert images to grayscale
base_gray = cv2.cvtColor(self.base, cv2.COLOR_BGRA2GRAY)
curr_gray = cv2.cvtColor(self.curr, cv2.COLOR_BGRA2GRAY)
# initialize feature detector
detector = cv2.ORB_create()
# create a mask using the alpha channel of the original image--don't
# use transparent or partially transparent parts
base_cond = self.base[:,:,3] == 255
base_mask = np.array(np.where(base_cond, 255, 0), dtype=np.uint8)
curr_cond = self.curr[:,:,3] == 255
curr_mask = np.array(np.where(curr_cond, 255, 0), dtype=np.uint8)
# use the mask and grayscale images to detect good features
base_keys, base_desc = detector.detectAndCompute(base_gray, mask=base_mask)
curr_keys, curr_desc = detector.detectAndCompute(curr_gray, mask=curr_mask)
TL;DR: The mask parameter is a 1-channel numpy array with the same shape as the grayscale image in which you are trying to find features (if image shape is (480, 720), so is mask).
The values in the array are of type np.uint8, 255 means "use this pixel" and 0 means "don't"
Thanks to Dan Mašek for leading me to parts of this answer.

Categories