I read an image, and converted it to gray-scale using this function:
def rgb2gray(img):
if len(img.shape)==3 & img.shape[-1] == 3: # img is RGB
return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
return img
now, I try to convert my image to binary using this:
def apply_threshold(img):
if len(np.unique(img))==2: #img is already binary
return img
gray_img=rgb2gray(img)
_,binary_img=cv2.threshold(gray_img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
return binary_img
but I get this annoying error:
cv2.error: OpenCV(3.4.1) C:\projects\opencv-python\opencv\modules\imgproc\src\thresh.cpp:1406: error: (-215) src.type() == (((0) & ((1 << 3) - 1)) + (((1)-1) << 3)) in function cv::threshold
I can't understand why since gray_img is for sure gray-scale!
I looked at this question, and the top answer by salvador daly proposed that the input picture is not gray-scale, but I checked it multiple times and it for sure is.
Any help will be appreciated!
You can try this approach for getting the threshold version/binary image of color image.
""" Read the original image in color form"""
image = cv2.imread(r'image.png')
""" Convert the image to gray scale"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
""" reducing the Noise """
blur = cv2.GaussianBlur(gray, (3,3), 0)
""" Applying Otsu thresholding """
_, thres = cv2.threshold(blur, 0, 255, cv2.THRESH_OTSU | cv2.THRESH_BINARY_INV)
or if you want adaptive threshold of image, you can try this as well
thres = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
cv2.THRESH_BINARY_INV,15,2)
for more details on thresholding you can check this site
https://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_thresholding/py_thresholding.html
also you can check whether the image is of color or gray scale version by checking the shape of the channels of image.
The color image has 3 channels or 3-D matrix ( Red, Green and Blue) so the dimension of the image matrix will be W x H x C (width x height x channel ) for e.g 300 x 300 x 3
The grayscale or binary image has only one channel (gray scale) or it is only 2-D matrix. for e.g 300 x 300.
Related
I'm currently working on a simple photo editor using CV2 in the backend and I'm wondering whether it is possible to add a bloom effect to the image using CV2.
Thanks for every help!
One can achieve a bloom effect in Python/OpenCV as follows:
- Read the input
- Convert to HSV colorspace as floats and separate channels
- Invert the saturation channel and multiply with the value channel, since to find white, we need low saturation and high brightness
- Blur the product and convert to 3 channels
- Blend the blurred image with the original applying gain to the blurred image
- Save the result
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('barn.jpg')
# set arguments
thresh_value = 245 # threshold to find white
blur_value = 50 # bloom smoothness
gain = 6 # bloom gain in intensity
# convert image to hsv colorspace as floats
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV).astype(np.float64)
h, s, v = cv2.split(hsv)
# Desire low saturation and high brightness for white
# So invert saturation and multiply with brightness
sv = ((255-s) * v / 255).clip(0,255).astype(np.uint8)
# threshold
thresh = cv2.threshold(sv, thresh_value, 255, cv2.THRESH_BINARY)[1]
# blur and make 3 channels
blur = cv2.GaussianBlur(thresh, (0,0), sigmaX=blur_value, sigmaY=blur_value)
blur = cv2.cvtColor(blur, cv2.COLOR_GRAY2BGR)
# blend blur and image using gain on blur
result = cv2.addWeighted(img, 1, blur, gain, 0)
# save output image
cv2.imwrite('barn_bloom.jpg', result)
# display IN and OUT images
cv2.imshow('image', img)
cv2.imshow('sv', sv)
cv2.imshow('thresh', thresh)
cv2.imshow('blur', blur)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
I currently have the following image and the salience map below which reflects the attention areas of the first image:
Both of them are the same size. What I am trying to do is amplify the region of areas that are very white in the salient region. For example, the eyes, collar and hair would be a bit more highlighted. I have the following code which I have tried to split the image into h, s, v and then multiply through but the output is black and white. Any help is greatly appreciated:
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv_image)
dimensions = (384, 384)
saliencyMap = cv2.resize(saliencyMap, dimensions)
s1 = s * saliencyMap.astype(s.dtype)
hsv_image = cv2.merge([h, s1, v])
out = cv2.cvtColor(hsv_image, cv2.COLOR_HSV2BGR)
cv2.imshow('example', out)
cv2.waitKey()
Here is how to do that in Python/OpenCV. Add the two images (from your other post). Modify the mask to have values near a mean of mid-gray. Separate the image into H,S,V channels. Apply the mask to the Saturation channel doing hard light composition. Combine the new saturation with the old hue and value channels and convert back to BGR.
Input 1:
Input 2:
Mask:
import cv2
import numpy as np
# read image 1
img1 = cv2.imread('img1.png')
hh, ww = img1.shape[:2]
# read image 2 and resize to same size as img1
img2 = cv2.imread('img2.png')
img2 = cv2.resize(img2, (ww,hh))
# read saliency mask as grayscale and resize to same size as img1
mask = cv2.imread('mask.png')
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
mask = cv2.resize(mask, (ww,hh))
# add img1 and img2
img12 = cv2.add(img1, img2)
# get mean of mask and shift mean to mid-gray
# desirable for hard light compositing
# add bias as needed
mean = np.mean(mask)
bias = -32
shift = 128 - mean + bias
mask = cv2.add(mask, shift)
# threshold mask at mid gray and convert to 3 channels
# (needed to use as src < 0.5 "if" condition in hard light)
thresh = cv2.threshold(mask, 128, 255, cv2.THRESH_BINARY)[1]
# convert img12 to hsv
hsv = cv2.cvtColor(img12, cv2.COLOR_BGR2HSV)
# separate channels
hue,sat,val = cv2.split(hsv)
# do hard light composite of saturation and mask
# see CSS specs at https://www.w3.org/TR/compositing-1/#blendinghardlight
satf = sat.astype(np.uint8)/255
maskf = mask.astype(np.uint8)/255
threshf = thresh.astype(np.uint8)/255
threshf_inv = 1 - threshf
low = 2.0 * satf * maskf
high = 1 - 2.0 * (1-satf) * (1-maskf)
new_sat = ( 255 * (low * threshf_inv + high * threshf) ).clip(0, 255).astype(np.uint8)
# combine new_sat with old hue and val
result = cv2.merge([hue,new_sat,val])
# save results
cv2.imwrite('img12_sat_hardlight.png', result)
# show results
cv2.imshow('img12', img12)
cv2.imshow('mask', mask)
cv2.imshow('thresh', thresh)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
I'm trying to cartoonify a face using opencv.Here's the original image
Currently I'm doing
Downscaling the image, applying bifilter and upscaling back to original
Then converting RGB of original image to grayscale and followed
medianblur to reduce nice
Apply Adaptive Threshold to create edgemask
Combining the image obtained from step1 with the edge mask with
bitmap
Here's the output
Then applied non-photorealistic rendering using OpenCV. Here's the final output
I want to generate face with uniform color(remove light reflection as well)without affecting the eyes, mouth. How can I achieve that either by tweaking my current code or another possible approach in opencv(python)
Based on: https://www.pyimagesearch.com/2014/07/07/color-quantization-opencv-using-k-means-clustering/
Here is a code that does what you are looking for:
import cv2
import numpy as np
from sklearn.cluster import MiniBatchKMeans
n = 32
# read image and convert to gray
img = cv2.imread('./obama.jpg',cv2.IMREAD_COLOR)
img = cv2.resize(img, (0,0), fx=.2, fy=.2)
img = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
(h, w) = img.shape[:2]
img =np.reshape(img, (img.shape[0]* img.shape[1], 3))
clt = MiniBatchKMeans(n_clusters=n)
labels = clt.fit_predict(img)
quant = clt.cluster_centers_.astype("uint8")[labels]
quant = np.reshape(quant, (h,w,3))
img = np.reshape(img, (h,w,3))
quant = cv2.cvtColor(quant, cv2.COLOR_LAB2BGR)
img = cv2.cvtColor(img, cv2.COLOR_LAB2BGR)
double = np.hstack([img, quant])
while True:
cv2.imshow('img', double)
k = cv2.waitKey(30) & 0xff
if k == 27:
break
You can use this tutorial to apply the color quantization only to boxes containing faces.
https://realpython.com/face-recognition-with-python/
I am working in python on openCV 3.0. In order to find the largest white pixel region, first of all thresholded gray image to binary image.
import cv2
import numpy as np
img = cv2.imread('graimage.png')
img = cv2.resize(img,(400,500))
gray = img.copy()
(thresh, im_bw) = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY )
derp,contours,hierarchy = cv2.findContours(im_bw,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
cnts = max(cnts, key=cv2.contourArea)
But it shows error as follows.
cv2.error: ..../opencv/modules/imgproc/src/contours.cpp:198: error: (-210) [Start]FindContours supports only CV_8UC1 images when mode != CV_RETR_FLOODFILL otherwise supports CV_32SC1 images only in function cvStartFindContours.
It looks like this was answered in the comments, but just to mark the question as answered:
CV_8UC1 means 8-bit pixels, unsigned, and only one channel, so grayscale. It looks like you're reading it in with 3 color channels, or CV_8UC3. You can check the image type by printing img.dtype and img.shape. The dtype should be uint8, and the shape should be (#, #), indicating two dimensions. I'm guessing you'll see that shape prints (#, #, 3) for your image as-is, indicating three color channels.
As #user3515225 said, you can fix that by reading the image in as grayscale using cv2.imread('img.png', cv2.IMREAD_GRAYSCALE). That assumes you have no use for color anywhere else, though. If you want a separate grayscale copy of the image, then replace gray = img.copy() with gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) instead.
How can I apply mask to a color image in latest python binding (cv2)? In previous python binding the simplest way was to use cv.Copy e.g.
cv.Copy(dst, src, mask)
But this function is not available in cv2 binding. Is there any workaround without using boilerplate code?
Here, you could use cv2.bitwise_and function if you already have the mask image.
For check the below code:
img = cv2.imread('lena.jpg')
mask = cv2.imread('mask.png',0)
res = cv2.bitwise_and(img,img,mask = mask)
The output will be as follows for a lena image, and for rectangular mask.
Well, here is a solution if you want the background to be other than a solid black color. We only need to invert the mask and apply it in a background image of the same size and then combine both background and foreground. A pro of this solution is that the background could be anything (even other image).
This example is modified from Hough Circle Transform. First image is the OpenCV logo, second the original mask, third the background + foreground combined.
# http://opencv-python-tutroals.readthedocs.io/en/latest/py_tutorials/py_imgproc/py_houghcircles/py_houghcircles.html
import cv2
import numpy as np
# load the image
img = cv2.imread('E:\\FOTOS\\opencv\\opencv_logo.png')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# detect circles
gray = cv2.medianBlur(cv2.cvtColor(img, cv2.COLOR_RGB2GRAY), 5)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20, param1=50, param2=50, minRadius=0, maxRadius=0)
circles = np.uint16(np.around(circles))
# draw mask
mask = np.full((img.shape[0], img.shape[1]), 0, dtype=np.uint8) # mask is only
for i in circles[0, :]:
cv2.circle(mask, (i[0], i[1]), i[2], (255, 255, 255), -1)
# get first masked value (foreground)
fg = cv2.bitwise_or(img, img, mask=mask)
# get second masked value (background) mask must be inverted
mask = cv2.bitwise_not(mask)
background = np.full(img.shape, 255, dtype=np.uint8)
bk = cv2.bitwise_or(background, background, mask=mask)
# combine foreground+background
final = cv2.bitwise_or(fg, bk)
Note: It is better to use the opencv methods because they are optimized.
import cv2 as cv
im_color = cv.imread("lena.png", cv.IMREAD_COLOR)
im_gray = cv.cvtColor(im_color, cv.COLOR_BGR2GRAY)
At this point you have a color and a gray image. We are dealing with 8-bit, uint8 images here. That means the images can have pixel values in the range of [0, 255] and the values have to be integers.
Let's do a binary thresholding operation. It creates a black and white masked image. The black regions have value 0 and the white regions 255
_, mask = cv.threshold(im_gray, thresh=180, maxval=255, type=cv.THRESH_BINARY)
im_thresh_gray = cv.bitwise_and(im_gray, mask)
The mask can be seen below on the left. The image on its right is the result of applying bitwise_and operation between the gray image and the mask. What happened is, the spatial locations where the mask had a pixel value zero (black), became pixel value zero in the result image. The locations where the mask had pixel value 255 (white), the resulting image retained its original gray value.
To apply this mask to our original color image, we need to convert the mask into a 3 channel image as the original color image is a 3 channel image.
mask3 = cv.cvtColor(mask, cv.COLOR_GRAY2BGR) # 3 channel mask
Then, we can apply this 3 channel mask to our color image using the same bitwise_and function.
im_thresh_color = cv.bitwise_and(im_color, mask3)
mask3 from the code is the image below on the left, and im_thresh_color is on its right.
You can plot the results and see for yourself.
cv.imshow("original image", im_color)
cv.imshow("binary mask", mask)
cv.imshow("3 channel mask", mask3)
cv.imshow("im_thresh_gray", im_thresh_gray)
cv.imshow("im_thresh_color", im_thresh_color)
cv.waitKey(0)
The original image is lenacolor.png that I found here.
Answer given by Abid Rahman K is not completely correct. I also tried it and found very helpful but got stuck.
This is how I copy image with a given mask.
x, y = np.where(mask!=0)
pts = zip(x, y)
# Assuming dst and src are of same sizes
for pt in pts:
dst[pt] = src[pt]
This is a bit slow but gives correct results.
EDIT:
Pythonic way.
idx = (mask!=0)
dst[idx] = src[idx]
The other methods described assume a binary mask. If you want to use a real-valued single-channel grayscale image as a mask (e.g. from an alpha channel), you can expand it to three channels and then use it for interpolation:
assert len(mask.shape) == 2 and issubclass(mask.dtype.type, np.floating)
assert len(foreground_rgb.shape) == 3
assert len(background_rgb.shape) == 3
alpha3 = np.stack([mask]*3, axis=2)
blended = alpha3 * foreground_rgb + (1. - alpha3) * background_rgb
Note that mask needs to be in range 0..1 for the operation to succeed. It is also assumed that 1.0 encodes keeping the foreground only, while 0.0 means keeping only the background.
If the mask may have the shape (h, w, 1), this helps:
alpha3 = np.squeeze(np.stack([np.atleast_3d(mask)]*3, axis=2))
Here np.atleast_3d(mask) makes the mask (h, w, 1) if it is (h, w) and np.squeeze(...) reshapes the result from (h, w, 3, 1) to (h, w, 3).