I am trying to isolate certain colored lines(the field lines) in a set of hockey images. I have applied a Hue Lightness Saturation(HLS) color space filter and have managed to pass all the components of the original image which are in the specified HLS range. However, some parts of the image are also being passed through because they satisfy the color range, for example, parts of the crowd and parts of the playing field. However, I want to isolate only the line which is required. How can I do this?
NOTE: I have separate programs for blue and yellow because they require different HLS ranges. In some images, there are more than one line. Also, as can be seen in the 2nd image I have put, the lines may be slightly curved. In the 2nd image, it would be enough if I can get the straight part only.
I have tried various image transformations and morphological operations with no luck. I have also experimented quite a lot with the HLS ranges and have set them so that they produce the best result on a set of images which I have, but I still don't get a satisfactory result.
Original images:
Code:
import cv2
import numpy as np
frame = cv2.imread('hockey4.jpg')
width=900
height=600
frame = cv2.resize(frame,(width,height))
# Convert BGR to HLS
hls = cv2.cvtColor(frame, cv2.COLOR_BGR2HLS)
#HLS ranges for blue
#lower array defines the lower limit and upper array defines the upper limit of the range
#The mask is a binary image where the output is white if the corresponding pixel in the input image is between the range specified by upper and lower limits
#blue
lower = np.array([90,90,19]) #90,90,19
upper = np.array([130,190,100]) #130,190,100
mask = cv2.inRange(hls, lower, upper)
# Bitwise-AND mask and original image
res = cv2.bitwise_and(frame,frame, mask= mask)
You can get better results from range thresholding than you're currently getting. Additionally, try using the morphological operations like opening and closing after thresholding to remove spurious bits and add in any pieces you didn't mean to remove.
These were the results I was able to get through thresholding with cv2.inRange() and applying opening and closing with cv2.morphologyEx():
image 1 yellow, image 1 blue, image 2 yellow, image 2 blue
Here was my code for the first image:
import cv2
import numpy as np
img = cv2.imread('0.jpg')
# crop out the top of the image where the scores are
h, w = img.shape[:2]
score_h = int(h/8)
img = img[score_h:h, :]
h, w = img.shape[:2]
# blur for better thresholding
blur = cv2.GaussianBlur(img, (5,5), 1)
# threshold in HSV space
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
# threshold for yellow
lb_y = np.array([11, 0, 153])
ub_y = np.array([52, 255, 255])
bin_y = cv2.inRange(hsv, lb_y, ub_y)
# cv2.imshow("thresh yellow", bin_y)
# cv2.waitKey(0)
# open to remove spurious yellow bits
open_kern = np.ones((3,3), dtype=np.uint8)
bin_y = cv2.morphologyEx(bin_y, cv2.MORPH_OPEN, open_kern, iterations=2)
# cv2.imshow("opened yellow", bin_y)
# cv2.waitKey(0)
# threshold for blue
lb_b = np.array([113, 41, 191])
ub_b = np.array([119, 76, 232])
bin_b = cv2.inRange(hsv, lb_b, ub_b)
# cv2.imshow("thresh blue", bin_b)
# cv2.waitKey(0)
# open to remove spurious blue bits
kern = np.ones((3,3), dtype=np.uint8)
bin_b = cv2.morphologyEx(bin_b, cv2.MORPH_OPEN, kern, iterations=2)
# cv2.imshow("opened blue", bin_b)
# cv2.waitKey(0)
# combine to show yellow detection
rip_y = img.copy()
rip_y[bin_y==0] = 0
mark_y = cv2.addWeighted(img, .4, rip_y, .6, 1)
cv2.imshow("marked yellow", mark_y)
cv2.waitKey(0)
# cv2.imwrite('0-y.jpg',mark_y)
# combine to show blue detection
rip_b = img.copy()
rip_b[bin_b==0] = 0
mark_b = cv2.addWeighted(img, .4, rip_b, .6, 1)
cv2.imshow("marked blue", mark_b)
cv2.waitKey(0)
# cv2.imwrite('0-b.jpg',mark_b)
And for the second:
import cv2
import numpy as np
img = cv2.imread('1.jpg')
# crop out the top of the image where the scores are
h, w = img.shape[:2]
score_h = int(h/10)
img = img[score_h:h, :]
h, w = img.shape[:2]
# blur for better thresholding
blur = cv2.GaussianBlur(img, (5,5), 1)
# threshold in HSV space
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
# threshold for yellow
lb_y = np.array([14, 79, 140])
ub_y = np.array([25, 255, 217])
bin_y = cv2.inRange(hsv, lb_y, ub_y)
# cv2.imshow("thresh yellow", bin_y)
# cv2.waitKey(0)
# open to remove spurious yellow bits
open_kern = np.ones((3,3), dtype=np.uint8)
bin_y = cv2.morphologyEx(bin_y, cv2.MORPH_OPEN, open_kern, iterations=2)
# cv2.imshow("opened yellow", bin_y)
# cv2.waitKey(0)
# threshold for blue
lb_b = np.array([113, 50, 150])
ub_b = np.array([135, 255, 255])
bin_b = cv2.inRange(hsv, lb_b, ub_b)
# cv2.imshow("thresh blue", bin_b)
# cv2.waitKey(0)
# close to fill in blue lines
kern = np.ones((3,3), dtype=np.uint8)
bin_b = cv2.morphologyEx(bin_b, cv2.MORPH_CLOSE, kern, iterations=2)
# cv2.imshow("closed blue", bin_b)
# cv2.waitKey(0)
# open to remove spurious lines
bin_b = cv2.morphologyEx(bin_b, cv2.MORPH_OPEN, kern, iterations=2)
# cv2.imshow("opened blue", bin_b)
# cv2.waitKey(0)
# combine to show yellow detection
rip_y = img.copy()
rip_y[bin_y==0] = 0
mark_y = cv2.addWeighted(img, .4, rip_y, .6, 1)
cv2.imshow("marked yellow", mark_y)
cv2.waitKey(0)
cv2.imwrite('1-y.jpg',mark_y)
# combine to show blue detection
rip_b = img.copy()
rip_b[bin_b==0] = 0
mark_b = cv2.addWeighted(img, .4, rip_b, .6, 1)
cv2.imshow("marked blue", mark_b)
cv2.waitKey(0)
cv2.imwrite('1-b.jpg',mark_b)
The first and the second are identical aside from their threshold values, and the second image I am applying a closing operation on the blue (not done for the other three thresholds) to close up the gaps in the line a bit.
It's not clear whether you're looking to do this automatically or manually for a few images. If you're looking to do it automatically, my first go-to would be to create a set of threshold values that work relatively well for all the images, and be harsh with morphological operations. This will likely thin out the lines a good bit, but then you could use numpy operations to fit a curve to the lines, and then widen it to achieve roughly the same line.
If you're going to be doing it manually, then the easiest bet is to play around with threshold values for each image. I've just recently created a tool that gives you sliders for changing the threshold range and previewing it on the fly. Check it out on GitHub if you're interested in trying it out. It will output the binary thresholded image, the colorspace you're thresholding in, and the lower and upper bounds that were used for thresholding.
Related
I wanted to Remove all the texts USING INPAINTING from this IMAGE. I had been trying various methods, and eventually found that I can get the results through OCR and then using thresholding MASK THE IMAGE.
processedImage = preprocess(partOFimg)
mask = np.ones(img.shape[:2], dtype="uint8") * 255
for c in cnts:
cv2.drawContours(mask, [c], -1, 0, -1)
img = cv2.inpaint(img,mask,7,cv2.INPAINT_TELEA)
Preprocess operations:
ret,thresh1 = cv2.threshold(gray, 0, 255,cv2.THRESH_OTSU|cv2.THRESH_BINARY_INV)
rect_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15, 3))
dilation = cv2.dilate(thresh1, rect_kernel, iterations = 1)
edged = cv2.Canny(dilation, 50, 100)
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
mask =
np.ones(img.shape[:2], dtype="uint8") * 255
When I run the above code, I here am the OUTPUT Image OUTPUT. As we can see, it is making some BLOCKS OF DIFFERENT COLOR over the IMAGE, I want to prevent that, How do I achieve this? I see that mask images are not formed well many times, and in cases when the text is white the PREPROCESSING doesn't occur properly.
How do I prevent these BLOCKS of other colours to FORM on the IMAGE?
Grayed Sub Image GRAYED
Threshold Sub IMG part: Thresholded Image
Masked Image Masked
EDIT 1:
I've managed to get this new better result by noticing that my threshold is the best mask I can get. After doing this I performed the masking process 3 different times with variable masks and inversions. I did the inpainting algorithm 3 times, it basically the other times inverse the mask, because in some cases required mask is the inversed mask. Still I think it needs improvement, If I chose a different image the results are not so good.
Python/OpenCV inpaint methods, generally, are not appropriate to your type of image. They work best on thin (scratch-like) regions, not large blocks. You really need an exemplar type method such as https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/criminisi_tip2004.pdf. But OpenCV does not have that.
However, the OpenCV methods do work here, I suspect, because you are filling with constant colors (green) and not texture. So you are best to try to get the mask of just the letters (characters), not rectangular blocks for the words. So, to show you what I mean, here is my Python/OpenCV approach.
Input:
Read the input
Threshold on the green sign
Apply morphology to close it up and keep as mask1
Apply the mask to the image to blacken out the outside of the sign
Threshold on the white in this new image and keep as mask2
Apply morphology dilate to enlarge it slightly and save as mask3
Do the inpaint
Save the results
import cv2
import numpy as np
# read input
img = cv2.imread('airport_sign.jpg')
# threshold on green sign
lower = (30,80,0)
upper = (70,120,20)
thresh = cv2.inRange(img, lower, upper)
# apply morphology close
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (135,135))
mask1 = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# apply mask to img
img2 = img.copy()
img2[mask1==0] = (0,0,0)
# threshold on white
#gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
#mask2 = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)[1]
lower = (120,120,120)
upper = (255,255,255)
mask2 = cv2.inRange(img2, lower, upper)
# apply morphology dilate
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
mask3 = cv2.morphologyEx(mask2, cv2.MORPH_DILATE, kernel)
# do inpainting
result1 = cv2.inpaint(img,mask3,11,cv2.INPAINT_TELEA)
result2 = cv2.inpaint(img,mask3,11,cv2.INPAINT_NS)
# save results
cv2.imwrite('airport_sign_mask.png', mask3)
cv2.imwrite('airport_sign_inpainted1.png', result1)
cv2.imwrite('airport_sign_inpainted2.png', result1)
# show results
cv2.imshow('thresh',thresh)
cv2.imshow('mask1',mask1)
cv2.imshow('img2',img2)
cv2.imshow('mask2',mask2)
cv2.imshow('mask3',mask3)
cv2.imshow('result1',result1)
cv2.imshow('result2',result2)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask 3:
Inpaint 1 (Telea):
Inpaint 2 (NS):
I'm working on automating changing image colors using python. The image I'm using is below, i'd love to move it from red to another range of colors, say green, keeping the detail and shading if possible. I've been able to convert some of the image to a solid color, losing all detail.
The code I'm currently using is below, I can't quite figure out the correct range of red to make it work correctly, and also it only converts to a single color, again losing all detail and shade.
Any help is appreciated, thank you.
import cv2
import numpy as np
import skimage.exposure
# load image and get dimensions
img = cv2.imread("test5.jpg")
# convert to hsv
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
## mask of upper red (170,50,50) ~ (180,255,255)
## mask of lower red (0,50,50) ~ (10,255,255)
# threshold using inRange
range1 = (0,50,50)
range2 = (1,255,255)
mask = cv2.inRange(hsv,range1,range2)
mask = 255 - mask
# apply morphology opening to mask
kernel = np.ones((3,3), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_ERODE, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# antialias mask
mask = cv2.GaussianBlur(mask, (0,0), sigmaX=3, sigmaY=3, borderType = cv2.BORDER_DEFAULT)
mask = skimage.exposure.rescale_intensity(mask, in_range=(127.5,255), out_range=(0,255))
result = img.copy()
result[mask==0] = (255,255,255)
# write result to disk
cv2.imwrite("test6.jpg", result)
This is one way to approach the problem in Python/OpenCV. But for red, it is very hard to do because red spans 0 hue, which also is the hue for gray and white and black, which you have in your image. The other issue you have is that skin tones has red shades, so you cannot pick too large of ranges for your colors. Also when dealing with red ranges, you need two sets, one for hues up to 180 and another for hues above 0.
Input:
import cv2
import numpy as np
# load image
img = cv2.imread('red_clothes.jpg')
# convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
blue_hue = 120
red_hue = 0
# diff hue (blue_hue - red_hue)
diff_hue = blue_hue - red_hue
# create mask for red color in hsv
lower1 = (150,150,150)
upper1 = (180,255,255)
mask1 = cv2.inRange(hsv, lower1, upper1)
lower2 = (0,150,150)
upper2 = (30,255,255)
mask2 = cv2.inRange(hsv, lower2, upper2)
mask = cv2.add(mask1,mask2)
mask = cv2.merge([mask,mask,mask])
# apply morphology to clean mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# modify hue channel by adding difference and modulo 180
hnew = np.mod(h + diff_hue, 180).astype(np.uint8)
# recombine channels
hsv_new = cv2.merge([hnew,s,v])
# convert back to bgr
bgr_new = cv2.cvtColor(hsv_new, cv2.COLOR_HSV2BGR)
# blend with original using mask
result = np.where(mask==(255, 255, 255), bgr_new, img)
# save output
cv2.imwrite('red_clothes_mask.png', mask)
cv2.imwrite('red_clothes_hue_shift.png', bgr_new)
cv2.imwrite('red_clothes_red2blue.png', result)
# Display various images to see the steps
cv2.imshow('mask1',mask1)
cv2.imshow('mask2',mask2)
cv2.imshow('mask',mask)
cv2.imshow('bgr_new',bgr_new)
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask:
Hue Shifted Image:
Blend between Input and Hue Shifted Image using Mask to blend:
So the result is speckled because of the black mixed with the red and from limited ranges due to skin color.
You can start with red, but the trick is to invert the image so red is now at hue 90 in OpenCV range and for example blue is at hue 30. So in Python/OpenCV, you can do the following:
Input:
import cv2
import numpy as np
# load image
img = cv2.imread('red_clothes.jpg')
# invert image
imginv = 255 - img
# convert to HSV
hsv = cv2.cvtColor(imginv, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
blueinv_hue = 30 #(=120+180/2=210-180=30)
redinv_hue = 90 #(=0+180/2=90)
# diff hue (blue_hue - red_hue)
diff_hue = blueinv_hue - redinv_hue
# create mask for redinv color in hsv
lower = (80,150,150)
upper = (100,255,255)
mask = cv2.inRange(hsv, lower, upper)
mask = cv2.merge([mask,mask,mask])
# apply morphology to clean mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# modify hue channel by adding difference and modulo 180
hnew = np.mod(h + diff_hue, 180).astype(np.uint8)
# recombine channels
hsv_new = cv2.merge([hnew,s,v])
# convert back to bgr
bgrinv_new = cv2.cvtColor(hsv_new, cv2.COLOR_HSV2BGR)
# invert
bgr_new = 255 -bgrinv_new
# blend with original using mask
result = np.where(mask==(255, 255, 255), bgr_new, img)
# save output
cv2.imwrite('red_clothes_mask.png', mask)
cv2.imwrite('red_clothes_hue_shift.png', bgr_new)
cv2.imwrite('red_clothes_red2blue.png', result)
# Display various images to see the steps
cv2.imshow('mask',mask)
cv2.imshow('bgr_new',bgr_new)
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask:
Red to Blue before masking:
Red to Blue after masking:
However, one is still limited by the fact that red is close to skin tones, so the range for red is limited.
Starting with a blue image rather than red allows one to use an expanded range for inRange() and do a better job in Python/OpenCV. Here is a change from blue to red.
Input:
import cv2
import numpy as np
# load image
img = cv2.imread('blue_clothes.jpg')
# convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)
red_hue = 0
blue_hue = 120
# diff hue (red_hue - blue_hue)
diff_hue = red_hue - blue_hue
# create mask for blue color in hsv
lower = (100,90,90)
upper = (140,255,255)
mask = cv2.inRange(hsv, lower, upper)
mask = cv2.merge([mask,mask,mask])
# apply morphology to clean mask
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (9,9))
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# modify hue channel by adding difference and modulo 180
hnew = np.mod(h + diff_hue, 180).astype(np.uint8)
# recombine channels
hsv_new = cv2.merge([hnew,s,v])
# convert back to bgr
bgr_new = cv2.cvtColor(hsv_new, cv2.COLOR_HSV2BGR)
# blend with original using mask
result = np.where(mask==(255, 255, 255), bgr_new, img)
# save output
cv2.imwrite('blue_clothes_mask.png', mask)
cv2.imwrite('blue_clothes_hue_shift.png', bgr_new)
cv2.imwrite('blue_clothes_blue2red.png', result)
# Display various images to see the steps
cv2.imshow('mask',mask)
cv2.imshow('bgr_new',bgr_new)
cv2.imshow('result',result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask:
Blue to Red before masking:
Blue to Red after masking:
I have a dataset that contains full width human images I want to remove all the backgrounds in those Images and just leave the full width person,
my questions:
is there any python code that does that ?
and do I need to specify each time the coordinate of the person object?
Here is one way using Python/OpenCV.
Read the input
Convert to gray
Threshold and invert as a mask
Optionally apply morphology to clean up any extraneous spots
Anti-alias the edges
Convert a copy of the input to BGRA and insert the mask as the alpha channel
Save the results
Input:
import cv2
import numpy as np
# load image
img = cv2.imread('person.png')
# convert to graky
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold input image as mask
mask = cv2.threshold(gray, 250, 255, cv2.THRESH_BINARY)[1]
# negate mask
mask = 255 - mask
# apply morphology to remove isolated extraneous noise
# use borderconstant of black since foreground touches the edges
kernel = np.ones((3,3), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# anti-alias the mask -- blur then stretch
# blur alpha channel
mask = cv2.GaussianBlur(mask, (0,0), sigmaX=2, sigmaY=2, borderType = cv2.BORDER_DEFAULT)
# linear stretch so that 127.5 goes to 0, but 255 stays 255
mask = (2*(mask.astype(np.float32))-255.0).clip(0,255).astype(np.uint8)
# put mask into alpha channel
result = img.copy()
result = cv2.cvtColor(result, cv2.COLOR_BGR2BGRA)
result[:, :, 3] = mask
# save resulting masked image
cv2.imwrite('person_transp_bckgrnd.png', result)
# display result, though it won't show transparency
cv2.imshow("INPUT", img)
cv2.imshow("GRAY", gray)
cv2.imshow("MASK", mask)
cv2.imshow("RESULT", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Transparent result:
I have a problem while getting a binary image from colored images. cv2.inRange() function is used to get mask of an image (simillar with thresholding) and I want to delete unnecessary parts, minimizing erosion of mask images. The biggest problem is that masks are not regularly extracted.
Samples
Crack:
Typical one
Ideal one:
My first object is making second picture as third one. I guess getting contour that has biggest area and deleting other contours(also for the mask) would be work. But can't not find how.
Second probleme is that the idea I described above would not work for the first image(crack). This kind of images could be discarded. But anyway it should be labeled as crack. In so far, I don't have ideas for this.
What I did
Here is input image and codes 42_1.jpg
class Real:
__ex_low=np.array([100,30,60])
__ex_high=np.array([140,80,214])
__ob_low=np.array([25,60,50]) #27,65,100])
__ob_high=np.array([50,255,255]) #[45,255,255])
def __opening(self, mask):
kernel = np.ones((3,3), np.uint8)
op = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
return op
def __del_ext(self, img_got):
img = img_got[0:300,]
hsv = cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
mask = cv2.inRange(hsv, self.__ex_low, self.__ex_high)
array1 = np.transpose(np.nonzero(mask))
array2 = np.nonzero(mask)
temp=array1.tolist()
xmin=min(array2[0]) #find the highest point covered blue
x,y,channel=img.shape
img=img[xmin:x,]
hsv=hsv[xmin:x,]
return img, hsv
def __init__(self, img_got):
img, hsv = self.__del_ext(img_got)
mask_temp = cv2.inRange(hsv, self.__ob_low, self.__ob_high)
mask = self.__opening(mask_temp)
array1 = np.transpose(np.nonzero(mask))
array2 = np.nonzero(mask)
ymin=min(array2[1])
ymax=max(array2[1])
xmin=min(array2[0])
xmax=max(array2[0])
self.x = xmax-xmin
self.y = ymax-ymin
self.ratio = self.x/self.y
# xmargin = int(self.x*0.05)
#ymargin = int(self.y*0.05)
self.img = img[(xmin):(xmax),(ymin):(ymax)]
self.mask = mask[(xmin):(xmax),(ymin):(ymax)]
#models = glob.glob("D:/Python36/images/motor/*.PNG")
img = cv2.imread("D:/Python36/images/0404/33_1.jpg")#<- input image
#last_size = get_last_size(models[-1])
#m2= Model(models[39],last_size)
r1 = Real(img)
cv2.imshow("2",r1.img)
cv2.imshow("3",r1.mask)
It would be great if codes are written in python3, but anything will be okay.
In general, you method is ok, except the wrong kernel to remove the horizontal lines.
I finish it by in following steps:
(1) Read and convert to HSV
(2) Find the target yellow color region in HSV
(3) morph-op to remove horizone lines
(4) crop the region
This is the result:
The code:
#!/usr/bin/python3
# 2018/04/16 13:20:07
# 2018/04/16 14:13:03
import cv2
import numpy as np
## (1) Read and convert to HSV
img = cv2.imread("euR2X.png")
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
## (2) Find the target yellow color region in HSV
hsv_lower = (25, 100, 50)
hsv_upper = (33, 255, 255)
mask = cv2.inRange(hsv, hsv_lower, hsv_upper)
## (3) morph-op to remove horizone lines
kernel = np.ones((5,1), np.uint8)
mask2 = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
## (4) crop the region
ys, xs = np.nonzero(mask2)
ymin, ymax = ys.min(), ys.max()
xmin, xmax = xs.min(), xs.max()
croped = img[ymin:ymax, xmin:xmax]
pts = np.int32([[xmin, ymin],[xmin,ymax],[xmax,ymax],[xmax,ymin]])
cv2.drawContours(img, [pts], -1, (0,255,0), 1, cv2.LINE_AA)
cv2.imshow("croped", croped)
cv2.imshow("img", img)
cv2.waitKey()
References:
what are recommended color spaces for detecting orange color in open cv?
Find single color, horizontal spaces in image
i am trying to remove noise in an image less and am currently running this code
import numpy as np
import argparse
import cv2
from skimage import morphology
# Construct the argument parser and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", required = True,
help = "Path to the image")
args = vars(ap.parse_args())
# Load the image, convert it to grayscale, and blur it slightly
image = cv2.imread(args["image"])
cv2.imshow("Image", image)
cv2.imwrite("image.jpg", image)
greenLower = np.array([50, 100, 0], dtype = "uint8")
greenUpper = np.array([120, 255, 120], dtype = "uint8")
green = cv2.inRange(image, greenLower, greenUpper)
#green = cv2.GaussianBlur(green, (3, 3), 0)
cv2.imshow("green", green)
cv2.imwrite("green.jpg", green)
cleaned = morphology.remove_small_objects(green, min_size=64, connectivity=2)
cv2.imshow("cleaned", cleaned)
cv2.imwrite("cleaned.jpg", cleaned)
cv2.waitKey(0)
However, the image does not seem to have changed from "green" to "cleaned" despite using the remove_small_objects function. why is this and how do i clean the image up? Ideally i would like to isolate only the image of the cabbage.
My thought process is after thresholding to remove pixels less than 100 in size, then smoothen the image with blur and fill up the black holes surrounded by white - that is what i did in matlab. If anybody could direct me to get the same results as my matlab implementation, that would be greatly appreciated. Thanks for your help.
Edit: made a few mistakes when changing the code, updated to what it currently is now and display the 3 images
image:
green:
clean:
my goal is to get somthing like this picture below from matlab implementation:
Preprocessing
A good idea when you're filtering an image is to lowpass the image or blur it a bit; that way neighboring pixels become a little more uniform in color, so it will ease brighter and darker spots on the image and keep holes out of your mask.
img = cv2.imread('image.jpg')
blur = cv2.GaussianBlur(img, (15, 15), 2)
lower_green = np.array([50, 100, 0])
upper_green = np.array([120, 255, 120])
mask = cv2.inRange(blur, lower_green, upper_green)
masked_img = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow('', masked_img)
cv2.waitKey()
Colorspace
Currently, you're trying to contain an image by a range of colors with different brightness---you want green pixels, regardless of whether they are dark or light. This is much more easily accomplished in the HSV colorspace. Check out my answer here going in-depth on the HSV colorspace.
img = cv2.imread('image.jpg')
blur = cv2.GaussianBlur(img, (15, 15), 2)
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
lower_green = np.array([37, 0, 0])
upper_green = np.array([179, 255, 255])
mask = cv2.inRange(hsv, lower_green, upper_green)
masked_img = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow('', masked_img)
cv2.waitKey()
Removing noise in a binary image/mask
The answer provided by ngalstyan shows how to do this nicely with morphology. What you want to do is called opening, which is the combined process of eroding (which more or less just removes everything within a certain radius) and then dilating (which adds back to any remaining objects however much was removed). In OpenCV, this is accomplished with cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel). The tutorials on that page show how it works nicely.
img = cv2.imread('image.jpg')
blur = cv2.GaussianBlur(img, (15, 15), 2)
hsv = cv2.cvtColor(blur, cv2.COLOR_BGR2HSV)
lower_green = np.array([37, 0, 0])
upper_green = np.array([179, 255, 255])
mask = cv2.inRange(hsv, lower_green, upper_green)
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15))
opened_mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
masked_img = cv2.bitwise_and(img, img, mask=opened_mask)
cv2.imshow('', masked_img)
cv2.waitKey()
Filling in gaps
In the above, opening was shown as the method to remove small bits of white from your binary mask. Closing is the opposite operation---removing chunks of black from your image that are surrounded by white. You can do this with the same idea as above, but using cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel). This isn't even necessary after the above in your case, as the mask doesn't have any holes. But if it did, you could close them up with closing. You'll notice my opening step actually removed a small bit of the plant at the bottom. You could actually fill those gaps with closing first, and then opening to remove the spurious bits elsewhere, but it's probably not necessary for this image.
Trying out new values for thresholding
You might want to get more comfortable playing around with different colorspaces and threshold levels to get a feel for what will work best for a particular image. It's not complete yet and the interface is a bit wonky, but I have a tool you can use online to try out different thresholding values in different colorspaces; check it out here if you'd like. That's how I quickly found values for your image.
Although, the above problem is solved using cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel). But, if somebody wants to use morphology.remove_small_objects to remove area less than a specified size, for those this answer may be helpful.
Code I used to remove noise for above image is:
import numpy as np
import cv2
from skimage import morphology
# Load the image, convert it to grayscale, and blur it slightly
image = cv2.imread('im.jpg')
cv2.imshow("Image", image)
#cv2.imwrite("image.jpg", image)
greenLower = np.array([50, 100, 0], dtype = "uint8")
greenUpper = np.array([120, 255, 120], dtype = "uint8")
green = cv2.inRange(image, greenLower, greenUpper)
#green = cv2.GaussianBlur(green, (3, 3), 0)
cv2.imshow("green", green)
cv2.imwrite("green.jpg", green)
imglab = morphology.label(green) # create labels in segmented image
cleaned = morphology.remove_small_objects(imglab, min_size=64, connectivity=2)
img3 = np.zeros((cleaned.shape)) # create array of size cleaned
img3[cleaned > 0] = 255
img3= np.uint8(img3)
cv2.imshow("cleaned", img3)
cv2.imwrite("cleaned.jpg", img3)
cv2.waitKey(0)
Cleaned image is shown below:
To use morphology.remove_small_objects, first labeling of blobs is essential. For that I use imglab = morphology.label(green). Labeling is done like, all pixels of 1st blob is numbered as 1. similarly, all pixels of 7th blob numbered as 7 and so on. So, after removing small area, remaining blob's pixels values should be set to 255 so that cv2.imshow() can show these blobs. For that I create an array img3 of the same size as of cleaned image. I used img3[cleaned > 0] = 255 line to convert all pixels which value is more than 0 to 255.
It seems what you want to remove is a disconnected group of small blobs.
I think erode() will do a good job remove them with the right kernel.
Given an nxn kernel, erode moves the kernel through the image and replaces the center pixel by the minimum pixel in the kernel.
Then you can dilate() the resulting image to restore eroded edges of the green part.
Another option would be to use fastndenoising
##### option 1
kernel_size = (5,5) # should roughly have the size of the elements you want to remove
kernel_el = cv2.getStructuringElement(cv2.MORPH_RECT, kernel_size)
eroded = cv2.erode(green, kernel_el, (-1, -1))
cleaned = cv2.dilate(eroded, kernel_el, (-1, -1))
##### option 2
cleaned = cv2.fastNlMeansDenoisingColored(green, h=10)