I'm using OpenCV to detect Pneumonia in chest-x-ray using Image Processing, so I need to remove the attached area to the image border to get the lung only, can anyone help me code this in python?
This image explains what I want this image after applying this methods: resized, Histogram Equalization, otsu Thresholded and inverse binary Thresholded, morphological processes(opening then closing)
This is the Original Image Original Image
This is how I would approach the problem in Python/OpenCV. Add a white border all around, flood fill it with black to replace the white, then remove the extra border.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('lungs.jpg')
h, w = img.shape[:2]
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# add 1 pixel white border all around
pad = cv2.copyMakeBorder(gray, 1,1,1,1, cv2.BORDER_CONSTANT, value=255)
h, w = pad.shape
# create zeros mask 2 pixels larger in each dimension
mask = np.zeros([h + 2, w + 2], np.uint8)
# floodfill outer white border with black
img_floodfill = cv2.floodFill(pad, mask, (0,0), 0, (5), (0), flags=8)[1]
# remove border
img_floodfill = img_floodfill[1:h-1, 1:w-1]
# save cropped image
cv2.imwrite('lungs_floodfilled.png',img_floodfill)
# show the images
cv2.imshow("img_floodfill", img_floodfill)
cv2.waitKey(0)
cv2.destroyAllWindows()
You can try using a morphological reconstruction with a border as a marker. This is an analogue of the function imclearborder from Matlab or Octave.
import cv2
import numpy as np
img = cv2.imread('5R0Zs.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 40, 255, cv2.THRESH_BINARY)[1]
kernel = np.ones((7,7),np.uint8)
kernel2 = np.ones((3,3),np.uint8)
marker = thresh.copy()
marker[1:-1,1:-1]=0
while True:
tmp=marker.copy()
marker=cv2.dilate(marker, kernel2)
marker=cv2.min(thresh, marker)
difference = cv2.subtract(marker, tmp)
if cv2.countNonZero(difference) == 0:
break
mask=cv2.bitwise_not(marker)
mask_color = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
out=cv2.bitwise_and(img, mask_color)
cv2.imwrite('out.png', out)
cv2.imshow('result', out )
cv2.waitKey(0) # waits until a key is pressed
cv2.destroyAllWindows()
Related
import cv2
import numpy as np
img = cv2.imread('img.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret,thresh = cv2.threshold(gray,127,255,1)
contours,h = cv2.findContours(thresh,1,2)
for cnt in contours:
approx = cv2.approxPolyDP(cnt,0.01*cv2.arcLength(cnt,True),True)
if len(approx)==3:
print('Triangle')
elif len(approx)==4:
print('Sq')
elif len(approx)==5:
print('Pen')
elif len(approx) ==6:
print('Hex')
else:
print('Cir')
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
**How to we can find the color of detected shapes in just BRG colors.This code is able to find shapes but I do not have any idea to detect color of this detected shapes. **
There is certain RGB values of the certain color.
There are some websites on which you can upload the image and it give you the exact RGB values of the color you want.
You can write condition where you define the some range of the color which you got from the website, if the color in the image lies in the range you defined then its your color.
Here is one way in Python/OpenCV. Given that you know the contours or polygons, you can create a mask for a given contour. Then use the mask to find all the pixels in the image corresponding to the mask white pixels. The get the mean of those pixels.
Input:
For simplicity here, I simply threshold on the gray level version of the green to make a mask.
import cv2
import numpy as np
# Read image
img = cv2.imread('rgb.png')
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Define graylevel lower and uppper limits for green
lower = 145
upper = 155
# Create mask
mask = cv2.inRange(gray, lower, upper)
# get mean color
region = img[np.where(mask == 255)]
mean = np.mean(region, axis=0)
print(mean)
# save results
cv2.imwrite('rgb_gray.png', gray)
cv2.imwrite('rgb_mask.png', mask)
cv2.imshow('gray', gray)
cv2.imshow('mask', mask)
cv2.waitKey(0)
cv2.destroyAllWindows()
Gray image:
Mask:
Mean color:
[ 0. 255. 0.]
Which is green as expected.
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'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.
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: