Let's say I have an image like this:
For which I want to remove the border and get this:
So exactly the same image, without the border.
I found a "hacky" way to do it which finds the outer contour and draws a line over it... To be honest it's not the best way because I need to adjust the "thickness" of the line so that it is thick enough to cover up the border, but not too thick so it doesn't cover any of the circles.
The image variable is the image you see above (already grayscaled, thresholded).
cnts = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]
cv2.drawContours(image, cnts, -1, 0, 15) # 15 is the right thickness for this image, but might not be for other ones...
The results is the 2nd picture above. Works great, but it doesn't work for ALL images (because of different thickness). Is there a better way to do this?
This is what I meant in the comments... fill everything that is black like the top-left corner, and connected to it, with white so that the bit you want is now entirely surrounded by white all the way to the edges. Then fill everything that is white and connected to the top-left corner with black.
#!/usr/local/bin/python3
import numpy as np
import cv2
from PIL import Image
# Load image and greyscale it
im = np.array(Image.open("framedcircles.jpg").convert('L'))
# Normalize and threshold image
im = cv2.normalize(im, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX)
res, im = cv2.threshold(im, 64, 255, cv2.THRESH_BINARY)
# Fill everything that is the same colour (black) as top-left corner with white
cv2.floodFill(im, None, (0,0), 255)
# Fill everything that is the same colour (white) as top-left corner with black
cv2.floodFill(im, None, (0,0), 0)
# Save result
Image.fromarray(im).save("result.png")
Result:
Here I am considering you had done all the image processing and others.
The recommended threshold (cv2.THRESH_OTSU)!
mask = np.zeros(img.shape, dtype=img.dtype)
cont = cv2.findContours(img_th, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cont = cont[0] if len(cont) == 2 else cont[1]
for c in cont:
area = cv2.contourArea(c)
if area < 500:
cv2.drawContours(mask, [c], -1, (255, 255, 255), -1)
print(mask.shape)
mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY) # making it to 2D (removing the channel value)
removed_border = cv2.bitwise_and(img, img, mask=mask)
cv2.imshow("result", removed_border)
cv2.waitKey()
cv2.destroyAllWindows()
If you have any problem with understanding the code please let me know. I m happy to help you out and if you like the answer please thumps up!!! :)
Result:
I have a little hack for this.
If you're falling low on thickness, repeat the process.
Again, it's not the best way to do it, but it should work.
Related
I want to develop an algorithm to recognize through an image, if the object present in said image is a spoon, a fork, or a knife. To acomplish this I am thinking of first comparing the ratio between white and black pixels along a line and if the ratio is the same along the ROI (with a tolerance of say 30%) i can say it's a knife, if not then I move to determine if it's a spoon or a fork.
But my problem starts here, I can't control wheter or not the spoon/fork/knife comes correctly oriented or not, if it comes rotated I am afraid my algorithm needs to be able to check along rotated lines as well. I can alredy get the angle of rotation, but I don't really know what to do with it. The code I have so far is:
import cv2
import numpy as np
img = cv2.imread('GenericImages/PBL3/fork.jpg')
NC = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, threshold = cv2.threshold(NC, 50, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(threshold, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# Draws contours
for c in contours:
if cv2.contourArea(c) < 1000:
continue
rect = cv2.minAreaRect(c)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(img, [box], 0, (0, 191, 255), 2)
cv2.imshow('ROI', img)
cv2.waitKey(0)
I am only a couple weeks into learning coding with Python and OpenCV, but StackOverflow has helped me numerous times. However I cant seem to figure this issue out so decided to ask my first question.
I am trying to take an image
Find the largest contour by area
Remove the background outside the contour
Effectively removing the background from the largest "object" in the
picture.
I am struggling with the last part. I know I need to create a mask somehow then place the mask over the original image.
How do I create the correct type of mask? And how do I place the mask on top of the original image?
This is my code:
import cv2
import numpy as np
# Load image
image = cv2.imread('Resources/X.png')
# Grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Find Canny edges
edged = cv2.Canny(gray, 30, 200)
# Finding Contours
contours, hierarchy = cv2.findContours(edged,
cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cv2.imshow('Canny Edges After Contouring', edged)
print("Number of Contours found = " + str(len(contours)))
cv2.waitKey(0)
# Largest contour
c = max(contours, key=cv2.contourArea)
# Not sure what to do from here. Attempt below:
mask = np.zeros(image.shape, np.uint8) # What is this actually doing? what does np.unit8 mean?
cv2.drawContours(mask, c, -1, (255, 255, 255), 1) # I am drawing the correct outline/contour
cv2.imshow('Mask', mask)
cv2.waitKey(0)
Any help would be appreciated.
Thanks
Chris
EDIT:
I managed to do it but not exactly sure what I am doing :-(
How would I get a different color background? I presume I have to fill the blank_mask with another color?
Also not sure what the bitwise function is actually doing.
blank_mask = np.zeros(image.shape, dtype=np.uint8)
cv2.fillPoly(blank_mask, [c], (255,255,255))
blank_mask = cv2.cvtColor(blank_mask, cv2.COLOR_BGR2GRAY)
result = cv2.bitwise_and(original,original,mask=blank_mask)
cv2.imshow('Result', result)
Here is one way to change the background on your image using Python/OpenCV.
Read the input and get its dimensions
Threshold on black and invert to get white on black background
Get the largest contour from the inverted threshold image
Draw the largest contour as white filled on a black background as a mask
Create an inverted mask
Create a new colored background image
Apply the mask to the image
Apply the inverted mask to the background color image
Add the two images
Save the result
import cv2
import numpy as np
# Read image
img = cv2.imread('shapes.png')
hh, ww = img.shape[:2]
# threshold on black
# Define lower and uppper limits of what we call "white-ish"
lower = np.array([0, 0, 0])
upper = np.array([0, 0, 0])
# Create mask to only select black
thresh = cv2.inRange(img, lower, upper)
# invert mask so shapes are white on black background
thresh_inv = 255 - thresh
# get the largest contour
contours = cv2.findContours(thresh_inv, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
big_contour = max(contours, key=cv2.contourArea)
# draw white contour on black background as mask
mask = np.zeros((hh,ww), dtype=np.uint8)
cv2.drawContours(mask, [big_contour], 0, (255,255,255), cv2.FILLED)
# invert mask so shapes are white on black background
mask_inv = 255 - mask
# create new (blue) background
bckgnd = np.full_like(img, (255,0,0))
# apply mask to image
image_masked = cv2.bitwise_and(img, img, mask=mask)
# apply inverse mask to background
bckgnd_masked = cv2.bitwise_and(bckgnd, bckgnd, mask=mask_inv)
# add together
result = cv2.add(image_masked, bckgnd_masked)
# save results
cv2.imwrite('shapes_inverted_mask.jpg', mask_inv)
cv2.imwrite('shapes_masked.jpg', image_masked)
cv2.imwrite('shapes_bckgrnd_masked.jpg', bckgnd_masked )
cv2.imwrite('shapes_result.jpg', result)
cv2.imshow('mask', mask)
cv2.imshow('image_masked', image_masked)
cv2.imshow('bckgrnd_masked', bckgnd_masked)
cv2.imshow('result', result)
cv2.waitKey(0)
cv2.destroyAllWindows()
Mask image from largest contour:
Image masked:
Background masked:
Result:
I'm trying to process images that have bloc of text in rectangle with colored background.
See below original picture - I would need all text and numbers in black and all background in white to make it easier to read text.
I'm thinking about having a grayscale version and its opposite and find the area of the colored background and use that area from the inverted picture to replace the same area from the grayscale. I can't find the rectangle of interest though (blue and yellow in the picture)
Any idea to make all text black in white background in the entire image ?
In your case,
1. get connected components (connected components, MSER, region growing, ..., etc.)
2. thresh by width. # half the size of image would do in the example.
long (background / boxes / lines) : white
short (letters) : black
I was able to detect the contour of the colored background (see code below) - What is the best transformation to ensure that the text in those blocks are black and turn the background white ?
thank you!
`
# Make greyscale version and inverted, thresholded greyscale version
gr = cv2.cvtColor(page_image,cv2.COLOR_BGR2GRAY)
plt.figure(figsize = (30,30))
plt.imshow(gr)
plt.show()
blur = cv2.GaussianBlur(gr, (7,7), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Create rectangular structuring element and dilate
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2)) #5,5
dilate = cv2.dilate(thresh, kernel, iterations=4)
# Find contours and draw rectangle
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
thr=(gr.shape[1]/2)
print(thr)
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
if w>thr and h>20 :
cv2.rectangle(gr, (x,y ), (x+w,y+h ), (36,255,12), 2)
`
In the image above, I want to eventually be able to fill in the contours of the colored circles. Unfortunately, the yellow circle on the bottom is right at the edge of the image, so cv2.findContours() doesn't work:
_, green_contours, _ = cv2.findContours(green_seg, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
Does anyone know how I can somehow fill in the yellow circle contour even though it's at the edge of the image? Since it's at the edge of the image, the pixel value on the bottom edge of the image doesn't complete the circle, isn't valued at 255, if that makes sense.
I looked online and some people say it's possible to draw a big box around the image and then do the contours, but if I do that, then opencv only draws the big contour around the entire image and not the circle.
Any other thoughts would be greatly appreciated!
EDIT: The image I show above is just one case I'm considering. I'm trying to think how I can make this general enough such that for any contour that is at the edge of the border, I can still fill in the contour with cv2.drawContour().
If you have (more or less) convex polygons, you actually CAN use cv2.findContours. Having the contours, try to find the center of mass, e.g. using cv2.moments, and then use this as the seed point in cv2.floodFill.
Please see the following code snippet. I assume, you can identify your polygons by color. Also, instead of some advanced finding of the center of mass, I just used the center point of the bounding rectangle of each contour. Maybe, that's also sufficient for your use case!?
import cv2
import numpy as np
# Set up test image
colors = [(0, 255, 0), (0, 0, 255)]
input = np.zeros((400, 400, 3), np.uint8)
cv2.circle(input, (100, 100), 50, colors[0], 10)
cv2.circle(input, (150, 350), 75, colors[1], 10)
output = input.copy()
# Iterate all colors...
for i, c in enumerate(colors):
# Mask color
img = np.all(output == c, axis=2).astype(np.uint8) * 255
# Find contours with respect to OpenCV version
cnts = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
# Get bounding rectangles; derive seed points for flood filling; flood fill
rects = [cv2.boundingRect(c) for c in cnts]
seeds = [(np.int32(r[0] + r[2] / 2), np.int32(r[1] + r[3] / 2)) for r in rects]
[cv2.floodFill(output, mask=None, seedPoint=s, newVal=c) for s in seeds]
cv2.imshow('input', input)
cv2.imshow('output', output)
cv2.waitKey(0)
cv2.destroyAllWindows()
That's the input:
And, that's the output:
Hope that helps!
----------------------------------------
System information
----------------------------------------
Platform: Windows-10-10.0.16299-SP0
Python: 3.8.1
NumPy: 1.18.1
OpenCV: 4.1.2
----------------------------------------
A simple way, perhaps not so precise, is to compute the convex hull for each contour and draw the interior with a fill color:
import cv2
import numpy as np
import sys
# load input image from the cmd-line
img = cv2.imread('test_images/partial_contour.png')
if (img is None):
print('!!! Failed imread')
sys.exit(-1)
output_img = img.copy()
# isolate just the colored drawings
ret, thres_bgr = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY)
thres_bgr[np.where((thres_bgr == [255,255,255]).all(axis=2))] = [0,0,0] # replace white pixels for black
# convert from 3-channels (BGR) to a single channel (gray)
gray_img = cv2.cvtColor(thres_bgr, cv2.COLOR_BGR2GRAY)
# this loop processes all the contours found in the image
contours, hierarchy = cv2.findContours(gray_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for contourIdx, cnt in enumerate(contours):
# compute a convex hull
hull = cv2.convexHull(cnt)
# fill the inside with red
cv2.fillPoly(output_img, pts=[hull], color=(0, 0, 255))
cv2.imshow('output_img', output_img)
cv2.imwrite('fill_partial_cnt_output.png', output_img)
cv2.waitKey(0)
I'm trying to spot black rectangles using OpenCV in Python 2.7. I'm confused as to why my contour-finding code isn't spotting the black rectangle at the bottom of a PNG (downloadable image, pre-grayscale):
Obviously, I want to spot the large black box. Here's my code:
import cv2
import numpy as np
img = cv2.imread(f)
# grayscale
imgrey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold and invert
ret, thresh = cv2.threshold(imgrey, 127, 255, cv2.THRESH_BINARY_INV)
# find contours, and draw red highlights around them
contours, h = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
x, y, w, h = cv2.boundingRect(cnt)
mask = np.zeros(imgrey.shape, np.uint8)
cv2.drawContours(mask, [cnt], 0, 255, -1)
cv2.rectangle(img, (x-2, y), (x+w+2, y+h), (0, 0, 255), 3)
cv2.imwrite(f.replace('.png', '-output.png'), img)
I get output that looks like this - it seems to be doing a brilliant job of spotting every possible contour except the one I'm interested in (note the lack of red line around the black box):
I can easily find ways to exclude the smaller contours (e.g. by looking at the mean colour value). But simply not spotting the contour of the black box, I'm not sure what to do about that.
What am I doing wrong?
For reference, this is what thresh looks like if I save it - the threshold seems to be working OK:
try Eroding and Dilating before findContours like below ( sorry i am not familiar with python)
erode(thresh,thresh,Mat(),Point(-1,-1),20);
dilate(thresh,thresh,Mat(),Point(-1,-1),20);
by this way you will get only one contour