findContours not spotting extremely obvious contours in OpenCV? - python

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

Related

Calculating the ratio of black and white pixels along a rotated line

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)

Crop subimage from image with Edge Detection

So basically I have an image with whitespace and a text above.
The output should be only the picture. Without the text and the whitespaces. The best example would probably be a meme:
I believe I would have to get the corner coordinates and then use something like pillow's Image.crop(corner_coordinates).
How could I implement this?
Edit: So I tried a little bit. I used the Canny Edge Detection Algorithm (opencv). Im now getting the desired edges bit also the edges from the text. Would be nice if someone could help me:)
You may find bounding rectangle of the largest contour that is not white.
I suggest using the following stages:
Convert image from BGR to Gray.
Convert from gray to to binary image.
Use automatic threshold (use cv2.THRESH_OTSU flag) and invert polarity.
The result is white color where original image is dark, and black where image is bright.
Find contours using cv2.findContours() (as Mark Setchell commented).
Finding the outer contour is simpler solution than detecting the edges.
Find the bounding rectangle of the contour with the maximum area.
Crop the bounding rectangle from the input image.
I used NumPy array slicing instead of using pillow.
Here is the code:
import cv2
# Read input image
img = cv2.imread('img.jpg')
# Convert from BGR to Gray.
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Convert to binary image using automatic threshold and invert polarity
_, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
# Find contours on thresh
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[-2] # Use index [-2] to be compatible to OpenCV 3 and 4
# Get contour with maximum area
c = max(cnts, key=cv2.contourArea)
x, y, w, h = cv2.boundingRect(c)
# Crop the bounding rectangle (use .copy to get a copy instead of slice).
crop = img[y:y+h, x:x+w, :].copy()
# Draw red rectangle for testing
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 0, 255), thickness = 2)
# Show result
cv2.imshow('img', img)
cv2.imshow('crop', crop)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
crop:
img:
thresh:

Python OpenCV: remove border from image

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.

How to find area of a curve in python where coordinates are not known?

So I am currently working on a project where I need to find the area in terms of cm of a particular curve.The problem is the curve has more than one colors each representing a different values
Something Like This
Can someone help me do it? There are more than one such curves in the image. How to simultaneously calculate all of them in Python.
You can use the following code to print area in terms of pixels. To get the area in cm^2, you need to know the relationship between pixels and actual length.
The following code prints the area of largest blob in the image.
To get the area of all the blobs in the image, just replace [c] with contours
import cv2
import numpy as np
img = cv2.imread("image.png", 0)
blank = np.zeros_like(img)
ret, thresh = cv2.threshold(img, 0 ,255, cv2.THRESH_BINARY)
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
if( len(contours) != 0 ):
c = max(contours, key = cv2.contourArea)
cv2.drawContours(blank, [c], -1, 255, -1)
print cv2.countNonZero(blank)
cv2.imshow("img", blank)
cv2.waitKey(0)
cv2.destroyAllWindows()
Edit:
import cv2
import numpy as np
img = cv2.imread("images.png", 0)
blank = np.zeros_like(img)
ret, thresh = cv2.threshold(img, 0 ,255, cv2.THRESH_BINARY)
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
for i in range(len(contours)):
cv2.drawContours(blank, contours[i], -1, 255, -1)
print "area of contour " + str(i)+" = " + str(cv2.contourArea(contours[i]))
cv2.imshow("img", blank)
cv2.waitKey(0)
cv2.destroyAllWindows()
1, separate out the different colored blobs. They look like they are generated by some other mapping software so presumably the colors are fixed and known. Make a new image for each color
2, For an image that contains only blobs of a fixed color and a black background you can make contours of the outlines (see findContours). Opencv will give you a separate contour for each blob.
3, Calculate the area of each contour -there is an opencv function to do this.

OpenCV ROI background padding

I am trying to focus on a Region of Interest on the face by using Numpy cropping. For large images, I have no trouble. But for smaller ones, I can find the face and find the matching bounding box, but when I try to crop it and show the image, I get a gray background to the right of the image. I wouldn't mind it, but when I try to apply contours to the image, the bottom and right edges are picked up as contour edges. How do I make sure that the gray padding doesn't affect my contour?
Here is my function:
def head_ratio(self):
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
faces = face_cascade.detectMultiScale(self.gray, 1.3, 5)
m_height, m_width, channels = self.image.shape
x, y, w, h = faces[0]
ROI = self.image[y:y+h,x:x+w]
kernel = np.ones((5,5), np.float32)/10
blurred = cv2.filter2D(self.gray[y:y+h, x:x+w], -1, kernel)
ret, thresh = cv2.threshold(blurred, 127, 255, cv2.THRESH_BINARY)
_,contours,_ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(ROI, contours, -1, (255, 255, 255), 3)
cv2.imshow("output", ROI)
cv2.waitKey(0)
cv2.destroyAllWindows()
return 1
Thank you for your help. I have been trying to look for an answer to this but was having trouble with what to search for.

Categories