In the picture below I have found and drawn the contour(green) of my object. I would like to get the contour coordinates as a numpy array, so I have used
array = np.vstack(contours).squeeze(). This gives me the coordinates of the entire contour in an array.
My question: is it possible to append only the contour coordinates(two green lines) inside the ROI(red square) into an array?
I know I can find contour inside a ROI separately, but I'm interested in doing it this way since the plan is to have several ROIs, and I dont want to find contours seperatly for each ROI. Any help is much appreciated.
Image with the contour
Original picture:
Here is the code if anyone is interested:
import numpy as np
import cv2
img = cv2.imread('image.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, 0)
inv = 255 - thresh
im2, contours, hierarchy = cv2.findContours(inv, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img, contours, -1, (0,255,0), 2)
array = np.vstack(contours).squeeze()
x, y, w, h= 150, 50, 500 ,150
roi = img[y:(y+h),x:(x+w)]
cv2.rectangle(img,(x,y), (x+w, y+h), (0,0,255), 1)
cv2.imshow('image', img)
cv2.waitKey(0)
Related
So I've been trying to make bounding boxes around a couple of fruits that I made in paint. I'm a total beginner to opencv so I watched a couple tutorials and the code that I typed made, makes contours around the object and using that I create bounding boxes. However it makes way too many tiny contours and bounding boxes. For example, heres the initial picture:
and heres the picture after my code runs:
However this is what I want:
Heres my code:
import cv2
import numpy as np
img = cv2.imread(r'C:\Users\bob\Desktop\coursera\coursera-2021\project2\fruits.png')
grayscaled = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(grayscaled, (7,7), 1)
canny = cv2.Canny(blur, 50, 50)
img1 = img.copy()
all_pics = []
contours, Hierarchy = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02*peri, True)
x, y, w, h = cv2.boundingRect(approx)
bob = img [y:y+h, x:x+w]
all_pics.append((x, bob))
cv2.rectangle(img1, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.imshow("title", img1)
cv2.waitKey(0)
I think FloodFill in combination with FindContours can help you:
import os
import cv2
import numpy as np
# Read original image
dir = os.path.abspath(os.path.dirname(__file__))
im = cv2.imread(dir+'/'+'im.png')
# convert image to Grayscale and Black/White
gry = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
cv2.imwrite(dir+'/im_1_grayscale.png',gry)
bw=cv2.threshold(gry, 127, 255, cv2.THRESH_BINARY)[1]
cv2.imwrite(dir+'/im_2_black_white.png',bw)
# Use floodfill to identify outer shape of objects
imFlood = bw.copy()
h, w = bw.shape[:2]
mask = np.zeros((h+2, w+2), np.uint8)
cv2.floodFill(imFlood, mask, (0,0), 0)
cv2.imwrite(dir+'/im_3_floodfill.png',imFlood)
# Combine flood filled image with original objects
imFlood[np.where(bw==0)]=255
cv2.imwrite(dir+'/im_4_mixed_floodfill.png',imFlood)
# Invert output colors
imFlood=~imFlood
cv2.imwrite(dir+'/im_5_inverted_floodfill.png',imFlood)
# Find objects and draw bounding box
cnts, _ = cv2.findContours(imFlood, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
for cnt in cnts:
peri = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02*peri, True)
x, y, w, h = cv2.boundingRect(approx)
bob = im[y:y+h, x:x+w]
cv2.rectangle(im, (x, y), (x+w, y+h), (0, 255, 0), 2)
# Save final image
cv2.imwrite(dir+'/im_6_output.png',im)
Grayscale and black/white:
Add flood fill:
Blend original objects with the flood filled version:
Invert the colors of blended version:
Final output:
his guys answer was just what I was looking for. If anyone runs through this problem try this:
Draw contours around objects with OpenCV
since I'm trying to enhance my skills with OpenCV in Python, I would like to know what's the best way of extracting a specific gray tone out of a image with mostly dark colors.
To start of, I created a test image in order to test different methods with OpenCV:
Lets say I want to extract a specific color in this image and add a border to it. For now I chose the gray rectangle in the middle with the color (33, 33, 34 RGB), see following:
(Here's the image without the red border in order you want to test your ideas: https://i.stack.imgur.com/Zf8Vb.png)
This is what I've tried so far, but it's not quite working:
img = cv2.imread(path) #Read input image
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # Convert from BGR to HSV color space
saturation_plane = hsv[:, :, 1] # all black/white/gray pixels are zero, and colored pixels are above zero
_, thresh = cv2.threshold(saturation_plane, 8, 255, cv2.THRESH_BINARY) # Apply threshold on s
contours = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # draw all contours
contours = contours[0] if len(contours) == 2 else contours[1]
result = img.copy()
for contour in contours:
(x, y, w, h) = cv2.boundingRect(contour) # compute the bounding box for the contour
if width is equal to the width of the rectangle i want to extract:
draw contour
What if the size of the rectangle is not fixed, so that I won't be able to detect it through its width/height? Moreover, is it better to convert the image into a gray scale instead of HSV? I'm just new to it and I would like to hear your way of achieving this.
Thanks in advance.
In case the specific color is known, you may start with gray = np.all(img == (34, 33, 33), 2).
The result is a logical matrix with True where BGR = (34, 33, 33), and False where it is not.
Note: OpenCV color ordering is BGR and not RGB.
Convert the logical matrix to uint8: gray = gray.astype(np.uint8)*255
Use findContours on gray image.
Converting the image to HSV in not going useful in case you want to find the blue rectangle, but not a gray rectangle with very specific RGB values.
The following code finds the contour with maximum size with color (33, 33, 34 RGB):
import numpy as np
import cv2
# Read input image
img = cv2.imread('rectangles.png')
# Gel all pixels in the image - where BGR = (34, 33, 33), OpenCV colors order is BGR not RGB
gray = np.all(img == (34, 33, 33), 2) # gray is a logical matrix with True where BGR = (34, 33, 33).
# Convert logical matrix to uint8
gray = gray.astype(np.uint8)*255
# Find contours
cnts = cv2.findContours(gray, 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)
# Draw green rectangle for testing
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), thickness = 2)
# Show result
cv2.imshow('gray', gray)
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Result:
gray:
img:
In case you don't know the specific color of the mostly dark colors, you may find all contours, and search for the one with the lowest gray value:
import numpy as np
import cv2
# Read input image
img = cv2.imread('rectangles.png')
# Convert from BGR to Gray
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Apply threshold on gray
_, thresh = cv2.threshold(gray, 8, 255, cv2.THRESH_BINARY)
# 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
min_level = 255
min_c = []
#Iterate contours, and find the darkest:
for c in cnts:
x, y, w, h = cv2.boundingRect(c)
# Ignore contours that are very thin (like edges)
if w > 5 and h > 5:
level = gray[y+h//2, x+w//2] # Get gray level of center pixel
if level < min_level:
# Update min_level abd min_c
min_level = level
min_c = c
x, y, w, h = cv2.boundingRect(min_c)
# 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.waitKey(0)
cv2.destroyAllWindows()
Result:
I'm using OpenCV 4 - python 3 - to find an specific area in a black & white image.
This area is not a 100% filled shape. It may hame some gaps between the white lines.
This is the base image from where I start processing:
This is the rectangle I expect - made with photoshop -:
Results I got with hough transform lines - not accurate -
So basically, I start from the first image and I expect to find what you see in the second one.
Any idea of how to get the rectangle of the second image?
I'd like to present an approach which might be computationally less expensive than the solution in fmw42's answer only using NumPy's nonzero function. Basically, all non-zero indices for both axes are found, and then the minima and maxima are obtained. Since we have binary images here, this approach works pretty well.
Let's have a look at the following code:
import cv2
import numpy as np
# Read image as grayscale; threshold to get rid of artifacts
_, img = cv2.threshold(cv2.imread('images/LXSsV.png', cv2.IMREAD_GRAYSCALE), 0, 255, cv2.THRESH_BINARY)
# Get indices of all non-zero elements
nz = np.nonzero(img)
# Find minimum and maximum x and y indices
y_min = np.min(nz[0])
y_max = np.max(nz[0])
x_min = np.min(nz[1])
x_max = np.max(nz[1])
# Create some output
output = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
cv2.rectangle(output, (x_min, y_min), (x_max, y_max), (0, 0, 255), 2)
# Show results
cv2.imshow('img', img)
cv2.imshow('output', output)
cv2.waitKey(0)
cv2.destroyAllWindows()
I borrowed the cropped image from fmw42's answer as input, and my output should be the same (or most similar):
Hope that (also) helps!
In Python/OpenCV, you can use morphology to connect all the white parts of your image and then get the outer contour. Note I have modified your image to remove the parts at the top and bottom from your screen snap.
import cv2
import numpy as np
# read image as grayscale
img = cv2.imread('blackbox.png')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# threshold
_,thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY)
# apply close to connect the white areas
kernel = np.ones((75,75), np.uint8)
thresh = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# get contours (presumably just one around the outside)
result = img.copy()
contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]
for cntr in contours:
x,y,w,h = cv2.boundingRect(cntr)
cv2.rectangle(result, (x, y), (x+w, y+h), (0, 0, 255), 2)
# show thresh and result
cv2.imshow("thresh", thresh)
cv2.imshow("Bounding Box", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
# save resulting images
cv2.imwrite('blackbox_thresh.png',thresh)
cv2.imwrite('blackbox_result.png',result)
Input:
Image after morphology:
Result:
Here's a slight modification to #fmw42's answer. The idea is connect the desired regions into a single contour is very similar however you can find the bounding rectangle directly since there's only one object. Using the same cropped input image, here's the result.
We can optionally extract the ROI too
import cv2
# Grayscale, threshold, and dilate
image = cv2.imread('3.png')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Connect into a single contour and find rect
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
dilate = cv2.dilate(thresh, kernel, iterations=1)
x,y,w,h = cv2.boundingRect(dilate)
ROI = original[y:y+h,x:x+w]
cv2.rectangle(image, (x, y), (x+w, y+h), (36, 255, 12), 2)
cv2.imshow('image', image)
cv2.imshow('ROI', ROI)
cv2.waitKey()
i want to count the number of objects in images using python
this is my code but it returns only 1
i'm using contours to draw circle on the objects
import cv2
from matplotlib import pyplot as plt
import numpy as np
kernel = np.ones((5,5), np.uint8)
img = cv2.imread('/home/mfp/Desktop/images/coins.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
mean = cv2.blur(gray , (7,7))
closing = cv2.morphologyEx(mean, cv2.MORPH_CLOSE, kernel)
opening = cv2.morphologyEx(closing, cv2.MORPH_OPEN, kernel)
ret, thresh = cv2.threshold(opening, 127, 255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
im2, contours, hierarchy =cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
i =0
for cnt in contours:
cv2.drawContours(img, [cnt], 0, (0, 0, 255), 3)
x, y, w, h = cv2.boundingRect(cnt)
if x>30 and x<60 and y>40 and y<80:
i=i+1
#cv2.rectangle(img, (x,y), (x+w,y+h), (0,0,255), 2)
elif x>60 and x<120 and y>80 and y<160:
i=i+1
#cv2.rectangle(img, (x,y), (x+w,y+h), (0,0,255), 2)
print(i)
cv2.imshow('Threshold', thresh)
cv2.imshow('Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Check the result of your open and close functions to see if your objects are touching each other as a result of this process. This might cause them to be connected and hence identified as one whole contour. Also, sort the contours obtained based on area before you draw contours or do anything else. It helps avoid choosing too small/too large contours that might be detected. Need your input sample image to tell any further!
Im trying to find contours in a specific area of the image. Is it possible to just show the contours inside the ROI and not the contours in the rest of the image? I read in another similar post that I should use a mask, but I dont think I used it correctly. Im new to openCV and Python, so any help is much appriciated.
import numpy as np
import cv2
cap = cv2.VideoCapture('size4.avi')
x, y, w, h= 150, 50, 400 ,350
roi = (x, y, w, h)
while(True):
ret, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, 0)
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
roi = cv2.rectangle(frame, (x,y), (x+w, y+h), (0,0,255), 2)
mask = np.zeros(roi.shape,np.uint8)
cv2.drawContours(mask, contours, -1, (0,255,0), 3)
cv2.imshow('img', frame)
Since you claim to be a novice, I have worked out a solution along with an illustration.
Consider the following to be your original image:
Assume that the following region in red is your region of interest (ROI), where you would like to find your contours:
First, construct an image of black pixels of the same size. It MUST BE OF SAME size:
black = np.zeros((img.shape[0], img.shape[1], 3), np.uint8) #---black in RGB
Now to form the mask and highlight the ROI:
black1 = cv2.rectangle(black,(185,13),(407,224),(255, 255, 255), -1) #---the dimension of the ROI
gray = cv2.cvtColor(black,cv2.COLOR_BGR2GRAY) #---converting to gray
ret,b_mask = cv2.threshold(gray,127,255, 0) #---converting to binary image
Now mask the image above with your original image:
fin = cv2.bitwise_and(th,th,mask = mask)
Now use cv2.findContours() to find contours in the image above.
Then use cv2.drawContours() to draw contours on the original image. You will finally obtain the following:
There might be better methods as well, but this was done so as to get you aware of the bitwise AND operation availabe in OpenCV which is exclusively used for masking
For setting a ROI in Python, one uses standard NumPy indexing such as in this example.
So, to select the right ROI, you don't use the cv2.rectangle function (that is for drawing a rectangle), but you do this instead:
_, thresh = cv2.threshold(gray, 127, 255, 0)
roi = thresh[x:(x+w), y:(y+h)]
im2, contours, hierarchy = cv2.findContours(roi, cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)