I'm pre-processing some images in order to remove the background from my area of interest. However, the images on my bench have rounded edges due to the focus of the camera. How do I discard these rounded edges and be able to remove only my object of interest from the image? The code below I can remove the background of the image, but it does not work right due to the edges around.
import numpy as np
import cv2
#Read the image and perform threshold and get its height and weight
img = cv2.imread('IMD408.bmp')
h, w = img.shape[:2]
# Transform to gray colorspace and blur the image.
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),0)
# Make a fake rectangle arround the image that will seperate the main contour.
cv2.rectangle(blur, (0,0), (w,h), (255,255,255), 10)
# Perform Otsu threshold.
_,thresh = cv2.threshold(blur,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# Create a mask for bitwise operation
mask = np.zeros((h, w), np.uint8)
# Search for contours and iterate over contours. Make threshold for size to
# eliminate others.
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
for i in contours:
cnt = cv2.contourArea(i)
if 1000000 >cnt > 100000:
cv2.drawContours(mask, [i],-1, 255, -1)
# Perform the bitwise operation.
res = cv2.bitwise_and(img, img, mask=mask)
# Display the result.
cv2.imwrite('IMD408.png', res)
cv2.imshow('img', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
input image:
Exit:
Error:
Since you mentioned that all the images have the same hue, then this should work well for them. Steps is to do some white balancing which will increase the contrast a bit.
Get the greyscale.
Threshold the grayscale image. Values less than 127 are set to 255 (white). This will give you a binary image, which will become a mask for the original image.
Apply the mask
You might have to play around with the thresholding if you want better results, here is the link for that. But this should get you started. I'm using a different OpenCV version compared to you might have to tweak the code a bit.
import cv2
def equaliseWhiteBalance(image):
''' Return equilised WB of an image '''
wb = cv2.xphoto.createSimpleWB() #Create WB Object
imgWB = wb.balanceWhite(img) #Balance White on image
r,g,b = cv2.split(imgWB) #Get individual r,g,b channels
r_equ = cv2.equalizeHist(r) #Equalise RED channel
g_equ = cv2.equalizeHist(g) #Equalise GREEN channel
b_equ = cv2.equalizeHist(b) #Equalise BLUE channel
img_equ_WB = cv2.merge([r_equ,g_equ,b_equ]) #Merge equalised channels
return imgWB
#Read the image
img = cv2.imread('IMD408.bmp')
result = img.copy()
#Get whiteBalance of image
imgWB = equaliseWhiteBalance(img)
cv2.imshow('img', imgWB)
cv2.waitKey(0)
# Get gray image
gray = cv2.cvtColor(imgWB,cv2.COLOR_RGB2GRAY)
cv2.imshow('img', gray)
cv2.waitKey(0)
# Perform threshold
_, thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
cv2.imshow('img', thresh)
cv2.waitKey(0)
# Apply mask
result[thresh!=0] = (255,255,255)
cv2.imshow('img', result)
cv2.waitKey(0)
If all the dark corner vignettes have different sizes per image, then I suggest looking for centroid of contours on the binary (mask) image. Centroids with a 'short' distance to any corner of your image will be the dark vignettes, so their value can be changed from black to white.
Related
I'm using a clothing dataset for some tasks and I need to create a black and white mask of the clothing items. I'm using this code to achieve the below:
img = cv2.imread(filepath, 0)
#img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(img,(5,5),0)
ret3,th3 = cv2.threshold(blur,0,250,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
cv2.imwrite(os.path.join(new_dir_path, filename), th3)
Here's an example of what I have so far:
and I'm getting erroneous ones like these
How can I fill out the holes appropriately so I can get a solid appearance like the first pair?
** EDIT **
I also tried employing edge detection with the code below:
edges = cv2.Canny(image=img_blur, threshold1=100, threshold2=200) # Canny Edge Detection
and I get this for this
The problem is it's a very hit and miss. For certain cases I get correct edges, and for others I don't. Also how would I be able to fill out the wholes if the edges are complex like above?
Given that you know the background will always be white, I think you can do something like this:
import cv2
import numpy as np
# read the image
filepath = 'dress.png'
img = cv2.imread(filepath)
# find the mask
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# set some absolute values for the threshold, since we know the background will always be white
_, mask = cv2.threshold(img_gray,244,255,cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
# find the largest contour
contours, _ = cv2.findContours(mask_inv, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
largest_contour = sorted(contours, key=cv2.contourArea, reverse=True)[0]
# draw the largest contour to fill in the holes in the mask
final_result = np.ones(img.shape[:2]) # create a blank canvas to draw the final result
final_result = cv2.drawContours(final_result, [largest_contour], -1, color=(0, 255, 0), thickness=cv2.FILLED)
# show results
cv2.imshow('mask', mask)
cv2.imshow('img', img)
cv2.imshow('final_result', final_result)
cv2.waitKey(0)
cv2.destroyAllWindows()
I'm trying to calculate the size of a wound. For that, I am capturing an image with a coin that serves as a basis to know the scale of the image and calculate the size of the region of the wound. I'm using the watershed algorithm for segmentation, but the currency is being covered by the tracking. Can someone help me get around the coin and detach it from the image along with the wound area.
Input
Exit:
import numpy as np
import cv2
# Read the image and perfrom an OTSU threshold
img = cv2.imread('feridatest.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# Remove hair with opening
kernel = np.ones((6,6),np.uint8)
opening = cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel, iterations = 2)
# Combine surrounding noise with ROI
kernel = np.ones((6,6),np.uint8)
dilate = cv2.dilate(opening,kernel,iterations=3)
# Blur the image for smoother ROI
blur = cv2.blur(dilate,(5,5))
# Perform another OTSU threshold and search for biggest contour
ret, thresh = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
# Create a new mask for the result image
h, w = img.shape[:2]
mask = np.zeros((h, w), np.uint8)
# Draw the contour on the new mask and perform the bitwise operation
cv2.drawContours(mask, [cnt],-1, 255, -1)
res = cv2.bitwise_and(img, img, mask=mask)
# Display the result
cv2.imshow('img', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
Input
[![enter image description here][1]][1]
Exit:
[![enter image description here][2]][2]
kinda stuck trying to figure out how I can expand the background color inwards.
I have this image that has been generated through a mask after noisy background subtraction.
I am trying to make it into this:
So far I have tried to this, but to no avail:
import cv2
from PIL import Image
import numpy as np
Image.open("example_of_misaligned_frame.png") # open poor frame
img_copy = np.asanyarray(img).copy()
contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX) # find contours
# create bounding box around blob and figure out the row.cols to iterate over
x,y,w,h = cv2.boundingRect(max(contours, key = cv2.contourArea))
# flood fill the entire region with back, hoping that off-white region gets filled due to connected components.
for row in range(y, y+h):
for col in range(x, x+w):
cv2.floodFill(img_copy, None, seedPoint=(col,row), newVal=0)
This results in a completely black image :(
Any help, pointing me in the right direction, is greatly appreciated.
You can solve it by using floodFill twice:
First time - fill the black pixels with Off-White color.
Second time - fill the Off-White pixels with black color.
There is still an issue for finding the RGB values of the Off-White color.
I found an improvised solution for finding the Off-White color (I don't know the exact rules for what color is considered to be background).
Here is a working code sample:
import cv2
import numpy as np
#Image.open("example_of_misaligned_frame.png") # open poor frame
img = cv2.imread("example_of_misaligned_frame.png")
#img_copy = np.asanyarray(img).copy()
img_copy = img.copy()
#Improvised way to find the Off White color (it's working because the Off White has the maximum color components values).
tmp = cv2.dilate(img, np.ones((50,50), np.uint8), iterations=10)
# Color of Off-White pixel
offwhite = tmp[0, 0, :]
# Convert to tuple
offwhite = tuple((int(offwhite[0]), int(offwhite[1]), int(offwhite[2])))
# Fill black pixels with off-white color
cv2.floodFill(img_copy, None, seedPoint=(0,0), newVal=offwhite)
# Fill off-white pixels with black color
cv2.floodFill(img_copy, None, seedPoint=(0,0), newVal=0, loDiff=(2, 2, 2, 2), upDiff=(2, 2, 2, 2))
cv2.imshow("img_copy", img_copy)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result of cv2.dilate:
Result of first cv2.floodFill:
Result of second cv2.floodFill:
In Python/OpenCV, you can simply extract a binary mask from your flood filled process image and erode that mask. Then reapply it to the input or to your flood filled result.
Input:
import cv2
# read image
img = cv2.imread("masked_image.png")
# convert img to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# make anything not black into white
gray[gray!=0] = 255
# erode mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (51,51))
mask = cv2.morphologyEx(gray, cv2.MORPH_ERODE, kernel)
# make mask into 3 channels
mask = cv2.merge([mask,mask,mask])
# apply new mask to img
result = img.copy()
result = cv2.bitwise_and(img, mask)
# write result to disk
cv2.imwrite("masked_image_original_mask.png", gray)
cv2.imwrite("masked_image_eroded_mask.png", mask)
cv2.imwrite("masked_image_eroded_image.png", result)
# display it
cv2.imshow("IMAGE", img)
cv2.imshow("MASK", mask)
cv2.imshow("RESULT", result)
cv2.waitKey(0)
Mask:
Eroded Mask:
Result:
Adjust the size of the circular (elliptical) morphology kernel as desired for more or less erosion.
This is the source image I am working with:
I am using this github repository (the file I'm using is tools/test_lanenet.py) to do binary lane segmentation. now I get this image:
The second image is actually an image resulted from this command:
# this line results in an array with the shape of (512, 256). this is just a hypothetical line of code. what I really care is the line which saves the image with matplotlib library.
binary_seg_image = lane_segmenter.binary_segment()
# this line saves the image
plt.imsave('binary_image_plt.png', binary_seg_image[0] * 255, cmap='gray')
First I have to do the same operation with opencv module and preferably faster.
In next operation I have to map the lanes segmented in second image on the source image road lanes. I think I have to use the second image as mask and use cv2.bitwise_andto do job right? Can anybody help me?
thank you guys
If you want to color the image where the mask exists, then this is one way using Python/OpenCV. In place of bitwise_and, you simply have to do numpy coloring where the mask is white. Note again, your images are not the same size and I do not know how best to align them. I leave that to you. I am using your two input images as in my other answer. The code is nearly the same.
import cv2
import numpy as np
# read image
img = cv2.imread('road.png')
ht, wd, cc = img.shape
print(img.shape)
# read mask as grayscale
gray = cv2.imread('road_mask.png', cv2.IMREAD_GRAYSCALE)
hh, ww = gray.shape
print(gray.shape)
# get minimum dimensions
hm = min(ht, hh)
wm = min(wd, ww)
print(hm, wm)
# crop img and gray to min dimensions
img = img[0:hm, 0:wm]
gray = gray[0:hm, 0:wm]
# threshold gray as mask
thresh = cv2.threshold(gray,128,255,cv2.THRESH_BINARY)[1]
print(thresh.shape)
# apply mask to color image
result = img.copy()
result[thresh==255] = (0,0,255)
cv2.imshow('image', img)
cv2.imshow('gray', gray)
cv2.imshow('thresh', thresh)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
# save results
cv2.imwrite('road_colored_by_mask.png', result)
Your images are not the same size. To mask the black/white image onto the color image, they need to align. I tried to simply crop them to the same minimum dimensions at the top left corner, but that did not align them properly.
However, this Python/OpenCV code will give you some idea how to start once you figure out how to align them.
Color Input:
B/W Lane Image:
import cv2
import numpy as np
# read image
img = cv2.imread('road.png')
ht, wd, cc = img.shape
print(img.shape)
# read mask as grayscale
gray = cv2.imread('road_mask.png', cv2.IMREAD_GRAYSCALE)
hh, ww = gray.shape
print(gray.shape)
# get minimum dimensions
hm = min(ht, hh)
wm = min(wd, ww)
print(hm, wm)
# crop img and gray to min dimensions
img = img[0:hm, 0:wm]
gray = gray[0:hm, 0:wm]
# threshold gray as mask
thresh = cv2.threshold(gray,128,255,cv2.THRESH_BINARY)[1]
print(thresh.shape)
# make thresh 3 channels as mask
mask = cv2.merge((thresh, thresh, thresh))
# apply mask to color image
result = cv2.bitwise_and(img, mask)
cv2.imshow('image', img)
cv2.imshow('gray', gray)
cv2.imshow('thresh', thresh)
cv2.imshow('mask', mask)
cv2.imshow('masked image', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
# save results
cv2.imwrite('road_masked_on_black.png', result)
I am using the code below to remove the backroung of the images and highlight only my region of interest (ROI), however, the algorithm behaves in a wrong way in some images, discarding the stain (ROI) and deleting along with the background.
import numpy as np
import cv2
#Read the image and perform threshold
img = cv2.imread('photo.bmp')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.medianBlur(gray,5)
_,thresh = cv2.threshold(blur,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
#Search for contours and select the biggest one
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
cnt = max(contours, key=cv2.contourArea)
#Create a new mask for the result image
h, w = img.shape[:2]
mask = np.zeros((h, w), np.uint8)
#Draw the contour on the new mask and perform the bitwise operation
cv2.drawContours(mask, [cnt],-1, 255, -1)
res = cv2.bitwise_and(img, img, mask=mask)
#Display the result
cv2.imwrite('photo.png', res)
#cv2.imshow('img', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
I don't know if I understand correctly because when I run your code I do not get the output you posted (exit). If you would like to obtain only the mole it can't be done by simply thresholding because the mole is too near the border plus if you look at your image closley you will see that it has some sort of frame. However there is a simple way to do this for this image but it may not work in other cases. You can draw a fake border over your image and seperate the ROI from other noise area. Then make a threshold for which contour you wish to display. Cheers!
Example:
#Import all necessery libraries
import numpy as np
import cv2
#Read the image and perform threshold and get its height and weight
img = cv2.imread('moles.png')
h, w = img.shape[:2]
# Transform to gray colorspace and blur the image.
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray,(5,5),0)
# Make a fake rectangle arround the image that will seperate the main contour.
cv2.rectangle(blur, (0,0), (w,h), (255,255,255), 10)
# Perform Otsu threshold.
_,thresh = cv2.threshold(blur,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
# Create a mask for bitwise operation
mask = np.zeros((h, w), np.uint8)
# Search for contours and iterate over contours. Make threshold for size to
# eliminate others.
_, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)
for i in contours:
cnt = cv2.contourArea(i)
if 1000000 >cnt > 100000:
cv2.drawContours(mask, [i],-1, 255, -1)
# Perform the bitwise operation.
res = cv2.bitwise_and(img, img, mask=mask)
# Display the result.
cv2.imwrite('mole_res.jpg', res)
cv2.imshow('img', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result: