I would like to draw contours in the middle of a blank image. I don't know how to set the contour location to be drawn. this is the line I use.
cv2.drawContours(bimg, c, -1, 255, 1)
bimg is the blank image, c is the contour I've extracted from an image. I believe I can move the contour by manipulating c, but I don't understand how c is written actually
You can look at the official documentation of opencv for contours. This code can be used to find contours of an image threshold and draw them on a white background with red color.
img = cv2.imread('image_name.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_,thresh1 = cv2.threshold(img,127,255,cv2.THRESH_BINARY)
_, cnts, _ = cv2.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
bgr = np.ones((img.shape[0], img.shape[1]), dtype= 'uint8')*255 #this creates a white background of the same size of input shape
cv2.drawContours(bgr, cnts, -1, (0,0,255), 1)
Related
I'm trying to draw contours i have found using findContours.
If i draw like this, i get a black background with the contour drawn on it.
out = np.zeros_like(someimage)
cv2.drawContours(out, contours, -1, 255, 1)
cv2.imwrite('contours.png',out)
If i draw like this, i get a fully transparent image with no drawn contours.
out = np.zeros((55, 55, 4), dtype=np.uint8)
cv2.drawContours(out, contours, -1, 255, 1)
cv2.imwrite('contours.png',out)
How do i go about making an image with size (55,55) and draw a contour on this, while keeping a transparent background?
Thanks
To work with transparent images in OpenCV you need to utilize the fourth channel after BGR called alpha with controls it. So instead of creating a three-channel image, create one with four channels, and also while drawing make sure you assign the fourth channel to 255.
mask = np.zeros((55, 55, 4), dtype=np.uint8)
cv2.drawContours(mask, cnts, -1, (255, 255, 255, 255), 1) #change first three channels to any color you want.
cv2.imwrite('res.png', mask)
Input image whose contours to draw.
Result
In Python/OpenCV, use the black and white image as the alpha channel as well as using it for a 3 channel BGR image.
cntr_img = np.zeros((55, 55, 4), dtype=np.uint8)
cv2.drawContours(cntr_img, contours, -1, 255, 1)
out = cv2.cvtColor(cntr_img, cv2.COLOR_GRAY2BGRA)
out[:,:,3] = cntr_img
cv2.imwrite('contours.png',out)
This works for me in Python/OpenCV. I am using a white blob on black background for input, since I do not have a contour image available. The contour image needs to be grayscale.
Input:
import cv2
import numpy as np
# read image
img = cv2.imread('mask.png')
# convert to gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
out = cv2.cvtColor(gray, cv2.COLOR_GRAY2BGRA)
out[:,:,3] = gray
# write output
cv2.imwrite('mask_transp.png',out)
# display it
cv2.imshow("out", out)
cv2.waitKey(0)
Transparent result (download to see it since it is white on transparent background):
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:
I got a map image here.
I need to extract the edges of buildings for further process, the result would be like step 2 for the post here.
Since I am not familiar with this field, can this be done by libraries such as OpenCV?
Seems you want to select individual buildings, so I used color separation. The walls are darker, which makes for good separation in the HSV colorspace. Note that the final result can be improved by zooming in more and/or by using an imagetype with less compression, such as PNG.
Select walls
First I determined good values for separation. For that I used this script. I found that the best result would be to separate the yellow and the gray separately and then combine the resulting masks. Not all walls closed perfectly, so I improved the result by closing the mask a bit. The result is a mask that displays all walls:
Left to right: Yellow mask, Gray mask, Combined and solidified mask
Find buildings
Next I used findCountours to separate out buildings. Since the wall contours will probably not be very useful (as walls are interconnected), I used the hierarchy to find the 'lowest' contours (that have no other contours inside of them). These are the buildings.
Result of findContours: the outline of all contours in green, the outline of individual buildings in red
Note that buildings on the edge are not detected. This is because using this technique they are not a separate contour, but part of the exterior of the image. This can be solve this by drawing a rectangle in gray on the border of the image. You may not want this in your final application, but I included it in case you do.
Code:
import cv2
import numpy as np
#load image and convert to hsv
img = cv2.imread("fLzI9.jpg")
# draw gray box around image to detect edge buildings
h,w = img.shape[:2]
cv2.rectangle(img,(0,0),(w-1,h-1), (50,50,50),1)
# convert image to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# define color ranges
low_yellow = (0,28,0)
high_yellow = (27,255,255)
low_gray = (0,0,0)
high_gray = (179,255,233)
# create masks
yellow_mask = cv2.inRange(hsv, low_yellow, high_yellow )
gray_mask = cv2.inRange(hsv, low_gray, high_gray)
# combine masks
combined_mask = cv2.bitwise_or(yellow_mask, gray_mask)
kernel = np.ones((3,3), dtype=np.uint8)
combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_DILATE,kernel)
# findcontours
contours, hier = cv2.findContours(combined_mask,cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# find and draw buildings
for x in range(len(contours)):
# if a contour has not contours inside of it, draw the shape filled
c = hier[0][x][2]
if c == -1:
cv2.drawContours(img,[contours[x]],0,(0,0,255),-1)
# draw the outline of all contours
for cnt in contours:
cv2.drawContours(img,[cnt],0,(0,255,0),2)
# display result
cv2.imshow("Result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
With buildings drawn solid red and all contours as green overlay
Here's a simple approach
Convert image to grayscale and Gaussian blur to smooth edges
Threshold image
Perform Canny edge detection
Find contours and draw contours
Threshold image using cv2.threshold()
Perform Canny edge detection with cv2.Canny()
Find contours using cv2.findContours() and cv2.drawContours()
import cv2
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (3, 3), 0)
thresh = cv2.threshold(blurred, 240 ,255, cv2.THRESH_BINARY_INV)[1]
canny = cv2.Canny(thresh, 50, 255, 1)
cnts = cv2.findContours(canny, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
cv2.drawContours(image,[c], 0, (36,255,12), 2)
cv2.imshow('thresh', thresh)
cv2.imshow('canny', canny)
cv2.imshow('image', image)
cv2.imwrite('thresh.png', thresh)
cv2.imwrite('canny.png', canny)
cv2.imwrite('image.png', image)
cv2.waitKey(0)
I would like to find the contour of the rectangular photograph inside of the object. I've tried using the corner detection feature of OpenCV, but to no avail. I also tried to find all the contours using findContours, and filter out the contours with more (or less) than 4 edges, but this also didn't lead anywhere.
I have a sample scan here.
I have a solution for you, but it involves a lot of steps. Also, it may not generalize that well. It does work pretty good for your image though.
First a grayscale and threshold is made and findContours is used to create a mask of the paper area. That mask is inverted and combined with the original image, which makes the black edges white. A new grayscale and threshold is made on the resulting image, which is then inverted so findContours can find the dark pixels of the photo. A rotated box around the largest contours is selected, which is the area you seek.
I added a little extra, which you may not need, but could be convenient: perspectivewarp is applied to the box, so the area you want is made into a straight rectangle.
There is quite a lot happening, so I advise you to take some time a look at the intermediate steps, to understand what happens.
Result:
Code:
import numpy as np
import cv2
# load image
image = cv2.imread('photo.jpg')
# resize to easily view on screen, remove for final processing
image = cv2.resize(image,None,fx=0.2, fy=0.2, interpolation = cv2.INTER_CUBIC)
### remove outer black edge
# create grayscale
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# perform threshold
retr , mask = cv2.threshold(gray_image, 190, 255, cv2.THRESH_BINARY)
# remove noise
kernel = np.ones((5,5),np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# create emtpy mask
mask_2 = np.zeros(image.shape[:3], dtype=image.dtype)
# find contours
ret, contours, hier = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# draw the found shapes (white, filled in ) on the empty mask
for cnt in contours:
cv2.drawContours(mask_2, [cnt], 0, (255,255,255), -1)
# invert mask and combine with original image - this makes the black outer edge white
mask_inv_2 = cv2.bitwise_not(mask_2)
tmp = cv2.bitwise_or(image, mask_inv_2)
### Select photo - not inner edge
# create grayscale
gray_image2 = cv2.cvtColor(tmp, cv2.COLOR_BGR2GRAY)
# perform threshold
retr, mask3 = cv2.threshold(gray_image2, 190, 255, cv2.THRESH_BINARY)
# remove noise
maskX = cv2.morphologyEx(mask3, cv2.MORPH_CLOSE, kernel)
# invert mask, so photo area can be found with findcontours
maskX = cv2.bitwise_not(maskX)
# findcontours
ret, contours2, hier = cv2.findContours(maskX, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# select the largest contour
largest_area = 0
for cnt in contours2:
if cv2.contourArea(cnt) > largest_area:
cont = cnt
largest_area = cv2.contourArea(cnt)
# find the rectangle (and the cornerpoints of that rectangle) that surrounds the contours / photo
rect = cv2.minAreaRect(cont)
box = cv2.boxPoints(rect)
box = np.int0(box)
print(rect)
#### Warp image to square
# assign cornerpoints of the region of interest
pts1 = np.float32([box[1],box[0],box[2],box[3]])
# provide new coordinates of cornerpoints
pts2 = np.float32([[0,0],[0,450],[630,0],[630,450]])
# determine and apply transformationmatrix
M = cv2.getPerspectiveTransform(pts1,pts2)
result = cv2.warpPerspective(image,M,(630,450))
#draw rectangle on original image
cv2.drawContours(image, [box], 0, (255,0,0), 2)
#show image
cv2.imshow("Result", result)
cv2.imshow("Image", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
I am working on Retinal fundus images.The image consists of a circular retina on a black background. With OpenCV, I have managed to get a contour which surrounds the whole circular Retina. What I need is to crop out the circular retina from the black background.
It is unclear in your question whether you want to actually crop out the information that is defined within the contour or mask out the information that isn't relevant to the contour chosen. I'll explore what to do in both situations.
Masking out the information
Assuming you ran cv2.findContours on your image, you will have received a structure that lists all of the contours available in your image. I'm also assuming that you know the index of the contour that was used to surround the object you want. Assuming this is stored in idx, first use cv2.drawContours to draw a filled version of this contour onto a blank image, then use this image to index into your image to extract out the object. This logic masks out any irrelevant information and only retain what is important - which is defined within the contour you have selected. The code to do this would look something like the following, assuming your image is a grayscale image stored in img:
import numpy as np
import cv2
img = cv2.imread('...', 0) # Read in your image
# contours, _ = cv2.findContours(...) # Your call to find the contours using OpenCV 2.4.x
_, contours, _ = cv2.findContours(...) # Your call to find the contours
idx = ... # The index of the contour that surrounds your object
mask = np.zeros_like(img) # Create mask where white is what we want, black otherwise
cv2.drawContours(mask, contours, idx, 255, -1) # Draw filled contour in mask
out = np.zeros_like(img) # Extract out the object and place into output image
out[mask == 255] = img[mask == 255]
# Show the output image
cv2.imshow('Output', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
If you actually want to crop...
If you want to crop the image, you need to define the minimum spanning bounding box of the area defined by the contour. You can find the top left and lower right corner of the bounding box, then use indexing to crop out what you need. The code will be the same as before, but there will be an additional cropping step:
import numpy as np
import cv2
img = cv2.imread('...', 0) # Read in your image
# contours, _ = cv2.findContours(...) # Your call to find the contours using OpenCV 2.4.x
_, contours, _ = cv2.findContours(...) # Your call to find the contours
idx = ... # The index of the contour that surrounds your object
mask = np.zeros_like(img) # Create mask where white is what we want, black otherwise
cv2.drawContours(mask, contours, idx, 255, -1) # Draw filled contour in mask
out = np.zeros_like(img) # Extract out the object and place into output image
out[mask == 255] = img[mask == 255]
# Now crop
(y, x) = np.where(mask == 255)
(topy, topx) = (np.min(y), np.min(x))
(bottomy, bottomx) = (np.max(y), np.max(x))
out = out[topy:bottomy+1, topx:bottomx+1]
# Show the output image
cv2.imshow('Output', out)
cv2.waitKey(0)
cv2.destroyAllWindows()
The cropping code works such that when we define the mask to extract out the area defined by the contour, we additionally find the smallest horizontal and vertical coordinates which define the top left corner of the contour. We similarly find the largest horizontal and vertical coordinates that define the bottom left corner of the contour. We then use indexing with these coordinates to crop what we actually need. Note that this performs cropping on the masked image - that is the image that removes everything but the information contained within the largest contour.
Note with OpenCV 3.x
It should be noted that the above code assumes you are using OpenCV 2.4.x. Take note that in OpenCV 3.x, the definition of cv2.findContours has changed. Specifically, the output is a three element tuple output where the first image is the source image, while the other two parameters are the same as in OpenCV 2.4.x. Therefore, simply change the cv2.findContours statement in the above code to ignore the first output:
_, contours, _ = cv2.findContours(...) # Your call to find contours
Here's another approach to crop out a rectangular ROI. The main idea is to find the edges of the retina using Canny edge detection, find contours, and then extract the ROI using Numpy slicing. Assuming you have an input image like this:
Extracted ROI
import cv2
# Load image, convert to grayscale, and find edges
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU + cv2.THRESH_BINARY)[1]
# Find contour and sort by contour area
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
# Find bounding box and extract ROI
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
ROI = image[y:y+h, x:x+w]
break
cv2.imshow('ROI',ROI)
cv2.imwrite('ROI.png',ROI)
cv2.waitKey()
This is a pretty simple way. Mask the image with transparency.
Read the image
Make a grayscale version.
Otsu Threshold
Apply morphology open and close to thresholded image as a mask
Put the mask into the alpha channel of the input
Save the output
Input
Code
import cv2
import numpy as np
# load image as grayscale
img = cv2.imread('retina.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# threshold input image using otsu thresholding as mask and refine with morphology
ret, mask = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
kernel = np.ones((9,9), np.uint8)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
# put mask into alpha channel of result
result = img.copy()
result = cv2.cvtColor(result, cv2.COLOR_BGR2BGRA)
result[:, :, 3] = mask
# save resulting masked image
cv2.imwrite('retina_masked.png', result)
Output