How to show the biggest rectangle in OpenCV Haar classifier - python

I have already trained positive and negative images on side view of a car using haar cascade object detection, now when i use cascade xml file to predict car in the images i get multiple rectangles.
Now
1)why am i getting multiple rectangle around my object.
2)How to show only the largest rectangle detected in image
Output Image
This is the type of output that i am getting on every image
Code
car_cascade = cv2.CascadeClassifier('data/cascade.xml')
img = cv2.imread('test/46.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cars = car_cascade.detectMultiScale(gray, 1.3, 5)
for (x,y,w,h) in cars:
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Piglet's answer will help you set a threshold for the minimum / maximum size, but if you wanted to find the largest bounding box in the image, you could do something like this:
areas = [w*h for x,y,w,h in cars]
i_biggest = np.argmax(areas)
biggest = cars[i_biggest]
Here, we're doing the following:
Calculating all bounding box areas using list comprehension
Finding the index of areas with the largest value, storing in i_biggest
Using this index to extract the biggest (largest area) rectangle from cars

As the function name alread suggests cv2.CascadeClassifier.detectMultiScale and the documentation says:
Detects objects of different sizes in the input image
Also from the documentation:
Python: cv2.CascadeClassifier.detectMultiScale(image[, scaleFactor[,
minNeighbors[, flags[, minSize[, maxSize]]]]]) → objects
minSize – Minimum possible object size. Objects smaller than that are
ignored.
So either you filter the list of resulting rectangles by size or you prevent small objects by setting the minSize parameter.

Related

How to crop an image from screenshot with the use of OpenCV?

I recently began studying image processing and took a task where I need to crop an image from mobile Instagram screenshot via use of OpenCV. I need to find edges of the image with contours and crop, but I'm not sure how to do this correctly.
I've tried to look up some examples like these:
How to crop biggest rectangle out of an image
https://www.quora.com/How-can-I-detect-an-object-from-static-image-and-crop-it-from-the-image-using-openCV
How to detect edge and crop an image in Python
How to crop rectangular shapes in an image using Python
But I'm still don't understand how to do it in my case.
Basically I have images like these:
https://imgur.com/a/VbwCdkO
and
https://imgur.com/a/Mm69i35
And the result should be like this:
https://imgur.com/a/Bq6Zjw0
https://imgur.com/a/AhzOkWS
Screenshots used need to be only from mobile version of Instagram and it can be assumed that they are always of rectangular shape
And if there are more than one image like here:
https://imgur.com/a/avv8Wvv
Then only one of the two is cropped (which one doesn't matter).
For example:
https://imgur.com/a/a4KnRKC
Thanks!
One of the prominent feature in your snapshot images is the white background color. Everything appears on top of it, even that user image. So we will try to segment out the background which would leave us with smaller components such as Instagram icon, likes, etc. Then we will pick the largest element assuming that the user image is the largest element present on the screen. Then we will simply find the cv2.boundingRect() of the largest contour and crop the snapshot accordingly as:
import cv2
import numpy as np
img = cv2.imread("/path/to/img.jpg")
white_lower = np.asarray([230, 230, 230])
white_upper = np.asarray([255, 255, 255])
mask = cv2.inRange(img, white_lower, white_upper)
mask = cv2.bitwise_not(mask)
Now we fill find contours in this mask and select the largest one.
im, cnt, hierarchy = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
largest_contour = max(cnt, key=lambda x:cv2.contourArea(x))
bounding_rect = cv2.boundingRect(largest_contour)
cropped_image = img[bounding_rect[1]: bounding_rect[1]+bounding_rect[3],
bounding_rect[0]:bounding_rect[0]+bounding_rect[2]]

Edge detection on dim edges using Python

I want to find dim edges using Python.
Input images (100 X 100) :
It consists of several horizontal boards: top, middle, bottom.
I want to find middle board bounding box like:
I used several edge detection methods (prewitt_x, sobel_x, cv2.findContours) but cannot detect well.
Because edge btw black region and board region is dim.
How can I find bounding box like red box?
Code below is example using prewitt_x and cv2.findContours:
import cv2
import numpy as np
img = cv2.imread('my_dir/my_img.bmp',0)
# prewitts_x
kernelx = np.array([[1,1,1],[0,0,0],[-1,-1,-1]])
img_prewittx = cv2.filter2D(img, -1, kernelx)
img_prewittx_gray = cv2.cvtColor(img_prewittx, cv2.COLOR_BGR2GRAY)
cv2.imwrite('my_outdir/my_outimg.bmp',img_prewittx)
# cv2.findContours
image, contours, hierarchy = cv2.findContours(img_prewittx_gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
rects = [cv2.boundingRect(cnt) for cnt in contours]
print(rects)
In fact, I don't want to use slower one like Canny detector.
Help me :)
My suggestion:
use a simple edge detection filter such as Prewitt
project horizontally (sum of the pixels in every row)
analyze the resulting profile to detect the regions of low/high activity and delimit the desired slabs.
You can also try the maximum along rows instead of the sum.
But don't expect miracles, this is a hard problem.

Rectangular bounding boxes around objects in monochrome images in python?

I have a set of two monochrome images [attached] where I want to put rectangular bounding boxes for both the persons in each image. I understand that cv2.dilate may help, but most of the examples I see are focusing on detecting one rectangle containing the maximum pixel intensities, so essentially they put one big rectangle in the image. I would like to have two separate rectangles.
UPDATE:
This is my attempt:
import numpy as np
import cv2
im = cv2.imread('splinet.png',0)
print im.shape
kernel = np.ones((50,50),np.uint8)
dilate = cv2.dilate(im,kernel,iterations = 10)
ret,thresh = cv2.threshold(im,127,255,0)
im3,contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
plt.imshow(im,cmap='Greys_r')
#plt.imshow(im3,cmap='Greys_r')
for i in range(0, len(contours)):
if (i % 2 == 0):
cnt = contours[i]
#mask = np.zeros(im2.shape,np.uint8)
#cv2.drawContours(mask,[cnt],0,255,-1)
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(im,(x,y),(x+w,y+h),(255,255,0),5)
plt.imshow(im,cmap='Greys_r')
cv2.imwrite(str(i)+'.png', im)
cv2.destroyAllWindows()
And the output is attached below: As you see, small boxes are being made and its not super clear too.
The real problem in your question lies in selection of the optimal threshold from the monochrome image.
In order to do that, calculate the median of the gray scale image (the second image in your post). The threshold level will be set 33% above this median value. Any value below this threshold will be binarized.
This is what I got:
Now performing morphological dilation followed by contour operations you can highlight your region of interest with a rectangle.
Note:
Never set a manual threshold as you did. Threshold can vary for different images. Hence always opt for a threshold based on the median of the image.

Python: Extract contours/boundries into a new image using OpenCV

I have a binary image with alot of blobs in different shapes. I use python and OpenCV and I got rotated bounding boxes and "more exact" contours. Here is the code. Mult2 is a binary image and "gray" is the same image with a different name (bad, i know).
# "rect" gives bounding box centroid coordinates, width/length, angle
ret, contours, hierarchy = cv2.findContours(gray,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
if 5<cv2.contourArea(cnt)<50000:
# Draw smaller objects with rectangular boxes (note that I actually draw all objects and not only the small ones atm)
rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)
cv2.drawContours(mult2,[box],0,(0,0,255),2)
if 1000<cv2.contourArea(cnt)<500000:
# Draw bigger objects with help from perimeter lenght ish something
epsilon = 0.004*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
cv2.drawContours(mult2,[approx],0,(255,0,0),2)
And the result is This image
In this example both the bounding boxes and the contours are drawn, but that does not matter atm. What I want is to draw, or extract, the bounding boxes (in blue), so that the result is that the bounding boxes "replaces" the blobs. I want to do be able to do the same thing with the contours (in red) and place them in the same image (so for a certain blob I want either the bounding box or the contour). How do I do this?
Edit: It cannot be seen here, but in the original binary image there are places where the bounding boxes overlaps each other. So for example one blob can have a big U-shape and sometimes there exist a blob within the U so to speak. In that case I want the contour from the U and the bounding box of the blob inside. How to separate those is already solved, my problem is to extract them. Thank you

Detecting an approaching object

I read this blog post where he uses a Laser and a Webcam to estimated the distance of the cardboard from the Webcam.
I had another idea about that. I don't want to calculate the distance from the webcam.
I want to check if an object is approaching the webcam. The algorithm, according to me, will be something like:
Detect the object in the webcam feed.
If the object is approaching the webcam it'll grow larger and larger in the video feed.
Use this data for further calculations.
Since I want to detect random objects, I am using the findContours() method to find the contours in the video feed. Using that, I will at least have the outlines of the objects in the video feed. The source code is:
import numpy as np
import cv2
vid=cv2.VideoCapture(0)
ans, instant=vid.read()
average=np.float32(instant)
cv2.accumulateWeighted(instant, average, 0.01)
background=cv2.convertScaleAbs(average)
while(1):
_,f=vid.read()
imgray=cv2.cvtColor(f, cv2.COLOR_BGR2GRAY)
ret, thresh=cv2.threshold(imgray,127,255,0)
diff=cv2.absdiff(f, background)
cv2.imshow("input", f)
cv2.imshow("Difference", diff)
if cv2.waitKey(5)==27:
break
cv2.destroyAllWindows()
The output is:
I am stuck here. I have the contours stored in an array. What do I do with it when the size increases? How do I proceed?
One trouble here is recognising and differentiating the moving objects from other stuff in the video feed. An approach might be to let the camera 'learn' what the background looks like with no object. Then you can constantly compare its input against this background. One way to get the background is to use a running average.
Any difference greater than a small threshold means there is a moving object. If you constantly display this difference, you basically have a motion tracker. The size of the objects is simply the sum of all the non-zero (thresholded) pixels, or their bounding rectangles. You can track this size and use it to guess whether the object is moving closer or further. Morphological operations can help group the contours into one cohesive object.
Since it will be tracking ANY movement, if there are two objects, they will be counted together. Here is where you can use the contours to find and track individual objects, e.g. using the contour bounds or centroids. You could also possibly separate them by colour.
Here are some results using this strategy (the grey blob is my hand):
It actually did a fairly good job of guessing which way my hand was moving.
Code:
import cv2
import numpy as np
AVERAGE_ALPHA = 0.2 # 0-1 where 0 never adapts, and 1 instantly adapts
MOVEMENT_THRESHOLD = 30 # Lower values pick up more movement
REDUCED_SIZE = (400, 600)
MORPH_KERNEL = np.ones((10, 10), np.uint8)
def reduce_image(input_image):
"""Make the image easier to deal with."""
reduced = cv2.resize(input_image, REDUCED_SIZE)
reduced = cv2.cvtColor(reduced, cv2.COLOR_BGR2GRAY)
return reduced
# Initialise
vid = cv2.VideoCapture(0)
average = None
old_sizes = np.zeros(20)
size_update_index = 0
while (True):
got_frame, frame = vid.read()
if got_frame:
# Reduce image
reduced = reduce_image(frame)
if average is None: average = np.float32(reduced)
# Get background
cv2.accumulateWeighted(reduced, average, AVERAGE_ALPHA)
background = cv2.convertScaleAbs(average)
# Get thresholded difference image
movement = cv2.absdiff(reduced, background)
_, threshold = cv2.threshold(movement, MOVEMENT_THRESHOLD, 255, cv2.THRESH_BINARY)
# Apply morphology to help find object
dilated = cv2.dilate(threshold, MORPH_KERNEL, iterations=10)
closed = cv2.morphologyEx(dilated, cv2.MORPH_CLOSE, MORPH_KERNEL)
# Get contours
contours, _ = cv2.findContours(closed, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(closed, contours, -1, (150, 150, 150), -1)
# Find biggest bounding rectangle
areas = [cv2.contourArea(c) for c in contours]
if (areas != list()):
max_index = np.argmax(areas)
max_cont = contours[max_index]
x, y, w, h = cv2.boundingRect(max_cont)
cv2.rectangle(closed, (x, y), (x+w, y+h), (255, 255, 255), 5)
# Guess movement direction
size = w*h
if size > old_sizes.mean():
print "Towards"
else:
print "Away"
# Update object size
old_sizes[size_update_index] = size
size_update_index += 1
if (size_update_index) >= len(old_sizes): size_update_index = 0
# Display image
cv2.imshow('RaptorVision', closed)
Obviously this needs more work in terms of identifying, selecting and tracking the objects etc (at the moment it does horribly if there is something else moving in the background). There are also many parameters to vary and tweak (the ones set are what worked well for my system). I'll leave that up to you though.
Some links:
background extraction
motion tracking
If you want to get a bit more high-tech with the background removal, have a look here:
wallflower
Detect the object in the webcam feed.
If the object is approaching the webcam it'll grow larger and larger in the video feed.
Use this data for further calculations.
Good idea.
If you want to use the contour detection approach, you could do it the following way:
You have a series of Images I1, I2, ... In
Do a contour detection on each one. C1, C2, ..., Cn (Contour is a set of points in OpenCV)
Take a large enough sample on your Image i and i+1: S_i \leq C_i, i \in 1...n
Check for all points in your sample for the nearest point on i+1. Then you trajectorys for all your points.
Check if this trajectorys point mostly outwards (tricky part ;)
If they appear outwards for a suffiecent number of frames your contour got bigger.
Alternative you could try to prune the points that are not part of the correct contour and work with a covering rectangle. It's very easy to check the size that way, but i don't knwo how easy it will be to choose the "correct" points.

Categories