I need some help with a project. My intention is to crop videos of sonographies with OpenCV and python in order to process them further. The features I am looking for are:
Loop through all available videos in a folder
find the contours and crop
export each video with one fixed size and resolution
Now i am a bit stuck on the contour finding and cropping part. I would like OpenCV to automatically recognize a bounding box around the shape of the sonography, knowing that all videos have the particular conus shape. Besides, it would be great if the non-relevant clutter could be removed. Can you help me? Attached you can find one original frame of the videos and the desired result.
import cv2
import numpy as np
cap = cv2.VideoCapture('video.mjpg')
# (x, y, w, h) = cv2.boundingRect(c)
# cv2.rectangle(frame, (x,y), (x+w, y+h), (0, 255, 0), 20)
# roi = frame[y:y+h, x:x+w]
while True:
ret, frame = cap.read()
# (height, width) = frame.shape[:2]
sky = frame[0:100, 0:200]
cv2.imshow('Video', sky)
if cv2.waitKey(1) == 27:
exit(0)
For the first frame of video; you can use this to detect the bounding-box of the image and then you can crop it or whatever you want :)
import sys
import cv2
import numpy as np
# Load our image
dir = sys.path[0]
org = cv2.imread(dir+'/im.png')
im=org.copy()
H,W=im.shape[:2]
# Convert image to grayscale
im=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
# remove noise
im=cv2.GaussianBlur(im,(21,21),21)
im=cv2.erode(im,np.ones((5,5)))
# remove horizantal line
im=cv2.GaussianBlur(im,(5,0),21)
blr=im.copy()
# make binary image
im=cv2.threshold(im,5,255,cv2.THRESH_BINARY)[1]
# draw black border around image to better detect blobs:
cv2.rectangle(im,(0,0),(W,H),0,thickness=W//25)
bw=im.copy()
# Invert the black and white colors
im=~im
# Find contours and sort them by width
cnts, _ = cv2.findContours(im, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnts.sort(key=lambda x: cv2.boundingRect(x)[2],reverse=True)
# Change the type and channels of image copies
im=cv2.cvtColor(im,cv2.COLOR_GRAY2BGR)
bw=cv2.cvtColor(bw,cv2.COLOR_GRAY2BGR)
blr=cv2.cvtColor(blr,cv2.COLOR_GRAY2BGR)
# Find the second biggest blob
x, y, w, h = cv2.boundingRect(cnts[1])
cv2.rectangle(org, (x, y), (x+w, y+h), (128, 0, 255), 10)
cv2.rectangle(im, (x, y), (x+w, y+h), (128, 255, 0), 10)
print(x,y,w,h)
# Save final result
top=np.hstack((blr,bw))
btm=np.hstack((im,org))
cv2.imwrite(dir+'/img_.png',np.vstack((top,btm)))
Bounding-Box area:
133 25 736 635
Cut and save the final image:
org = cv2.imread(dir+'/im.png')
cv2.imwrite(dir+'/img_.png',org[y:y+h,x:x+w])
Related
I am making an OpenCV Face Recognizer that draws a bounding box around the faces it detects from an image it has read. I am using a cascade classifier (haarcascades)
It shows the picture, not in grayscale (full color) and will not draw the bounding boxes. Not sure what I did, I am new to this.
Here is the code:
import cv2
# Load image
image = cv2.imread("/home/tyler/Downloads/PythonProjects/VN5anAL3_400x400.jpg")
# Convert image to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Define the cascade classifier
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
# Detect faces
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
# Draw bounding boxes
for (x, y, w, h) in faces:
cv2.rectangle(image, (x, y), (x + w, y + h), (255, 0, 0), 2)
# Show the image
cv2.imshow("faces", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
You are almost there, but you are drawing the bounding boxes on the grayscale image instead of the original image. Instead of using the gray variable, use the image variable in the following line to draw the bounding boxes:
cv2.rectangle(image, (x, y), (x + w, y + h), (255, 0, 0), 2)
Also, you may want to check if the cascade classifier is loaded correctly by adding the following line before the detection:
if face_cascade.empty():
raise Exception("Failed to load cascade classifier.")
I am new to Python and OpenCV. My task is to perform some operations on the ROI of an image and then adding back that image to the original image. How could I achieve this? For example, I want to change the colour of the ROI image and then add it back. My code is given below:
for (i,c) in enumerate(contours_from_left_to_right):
cv2.drawContours(duplicate_img, [c], -1, (0,0,255), 3)
cent_moment = cv2.moments(c)
centroid_x = int(cent_moment['m10'] / cent_moment['m00'])
centroid_y = int(cent_moment['m01'] / cent_moment['m00'])
cv2.putText(duplicate_img, str(i+1), (centroid_x, centroid_y), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow('Contours from Left to Right', duplicate_img)
cv2.waitKey(0)
(x, y, w, h) = cv2.boundingRect(c)
print("Top-Left Corner=",(x,y), "width= ",w,"height =",h)
ROI = roi_img[y:y+h, x:x+w]
cv2.imwrite("ROI_{}.png".format(image_number), ROI)
image_number += 1
When you selectyour ROI,you will have (x,y). After you complete your image processing on the ROI image, you can save the image and use pillow to paste the roi image back to original image follows the coordinate of the ROI.
from PIL import Image, ImageDraw, ImageFilter
#test
im1 = Image.open(args["image"]) #Original Image
im2 = Image.open("ROI.tif") #ROI Image after process
back_im = im1.copy()
back_im.paste(im2, (ROIRegion[region][0][0], ROIRegion[region][0][1])) #(x, Y) coordinate
back_im.save('replace.tif', quality=95) #save image
As a whole I am hoping to access ip camera using OpenCV, crop and adjust their image properties (saturation, contrast, brightness) and then output the result as a new stream.
I have very little knowledge of python/opencv and am doing my best to piece this together from what I can find.
I have been able to access the mjpeg stream however every way of cropping I have found seems to fail. The code below seems the most promising but I am open to alternative methods.
I have achieved the result i'm after using Max MSP and Syphon however my hope is that using OpenCV i will be able to make this completely web based and accessible.
I am hoping to avoid splitting the stream into individual jpegs but if that is the only way to achieve what i'm after please let me know.
Any and all guidance is greatly appreciated.
import cv2
import numpy as np
cap = cv2.VideoCapture('http://89.29.108.38:80/mjpg/video.mjpg')
(x, y, w, h) = cv2.boundingRect(c)
cv2.rectangle(frame, (x,y), (x+w, y+h), (0, 255, 0), 20)
roi = frame[y:y+h, x:x+w]
while True:
ret, frame = cap.read()
cv2.imshow('Video', frame)
if cv2.waitKey(1) == 27:
exit(0)
Traceback (most recent call last):
File "mjpeg-crop.py", line 6, in <module>
(x, y, w, h) = cv2.boundingRect(c)
NameError: name 'c' is not defined
Too much for a comment, but try this to get started:
import cv2
import numpy as np
cap = cv2.VideoCapture('http://89.29.108.38:80/mjpg/video.mjpg')
# (x, y, w, h) = cv2.boundingRect(c)
# cv2.rectangle(frame, (x,y), (x+w, y+h), (0, 255, 0), 20)
# roi = frame[y:y+h, x:x+w]
while True:
ret, frame = cap.read()
# (height, width) = frame.shape[:2]
sky = frame[0:100, 0:200]
cv2.imshow('Video', sky)
if cv2.waitKey(1) == 27:
exit(0)
I'm hoping to be able to isolate a small rectangular section of the area that is returned by a haar cascade (the cascade I'm using detects faces, so for example I would like to be able to isolate just the forehead within a given face). I know that training it specifically to detect the area I want is an option, but I'm hoping that it is easy to specify an arbitrary area within the face (for example, the top 20% of the rectangle). I include the code I'm using below:
import cv2
import numpy as py
from matplotlib import pyplot as plt
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
cap = cv2.VideoCapture("resources/video/EXAMPLE.mp4")
while True:
ret, img = cap.read()
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.3, 9)
for (x,y,w,h) in faces:
cv2.rectangle(img, (x,y), (x+w, y+h), (255,0,0), 2)
cv2.imshow('img',img)
k = cv2.waitKey(30) & 0xff
if k == 27:
break
cap.release()
cap.destroyAllWindows()
Is there a way to manipulate/gain info about the pixels in "faces"? Any help/pointers would be hugely appreciated.
basicly you can divide h with 3 to getting forehead:
for (x,y,w,h) in faces:
cv2.rectangle(img, (x,y), (x+w, int(y+h/3)), (255,0,0), 2)
but if you want to getting optimized results you can use landmark detection
I am working on a project which ask me to detect text area in an image. This is the result I achieved until now using the code below.
Original Image
Result
The code is the following:
import cv2
import numpy as np
# read and scale down image
img = cv2.pyrDown(cv2.imread('C:\\Users\\Work\\Desktop\\test.png', cv2.IMREAD_UNCHANGED))
# threshold image
ret, threshed_img = cv2.threshold(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY),
127, 255, cv2.THRESH_BINARY)
# find contours and get the external one
image, contours, hier = cv2.findContours(threshed_img, cv2.RETR_TREE,
cv2.CHAIN_APPROX_SIMPLE)
# with each contour, draw boundingRect in green
# a minAreaRect in red and
# a minEnclosingCircle in blue
for c in contours:
# get the bounding rect
x, y, w, h = cv2.boundingRect(c)
# draw a green rectangle to visualize the bounding rect
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), thickness=1, lineType=8, shift=0)
# get the min area rect
#rect = cv2.minAreaRect(c)
#box = cv2.boxPoints(rect)
# convert all coordinates floating point values to int
#box = np.int0(box)
# draw a red 'nghien' rectangle
#cv2.drawContours(img, [box], 0, (0, 0, 255))
# finally, get the min enclosing circle
#(x, y), radius = cv2.minEnclosingCircle(c)
# convert all values to int
#center = (int(x), int(y))
#radius = int(radius)
# and draw the circle in blue
#img = cv2.circle(img, center, radius, (255, 0, 0), 2)
print(len(contours))
cv2.drawContours(img, contours, -1, (255, 255, 0), 1)
cv2.namedWindow('contours', 0)
cv2.imshow('contours', img)
while(cv2.waitKey()!=ord('q')):
continue
cv2.destroyAllWindows()
As you can see, this can do more than I need. Look for commented parts if you need more.
By the way, what I need is to bound every text area in a single rectangle not (near) every char which the script is finding. Filter the single number or letter and to round everything in a single box.
For example, the first sequence in a box, the second in another one and so on.
I searched a bit and I found something about "filter rectangle area". I don't know if it is useful for my purpose.
Tooked a look also at some of the first result on Google but most of them don't work very well. I guess the code need to be tweaked a bit but I am a newbie in OpenCV world.
Solved using the following code.
import cv2
# Load the image
img = cv2.imread('image.png')
# convert to grayscale
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# smooth the image to avoid noises
gray = cv2.medianBlur(gray,5)
# Apply adaptive threshold
thresh = cv2.adaptiveThreshold(gray,255,1,1,11,2)
thresh_color = cv2.cvtColor(thresh,cv2.COLOR_GRAY2BGR)
# apply some dilation and erosion to join the gaps - change iteration to detect more or less area's
thresh = cv2.dilate(thresh,None,iterations = 15)
thresh = cv2.erode(thresh,None,iterations = 15)
# Find the contours
contours,hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
# For each contour, find the bounding rectangle and draw it
for cnt in contours:
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
cv2.rectangle(thresh_color,(x,y),(x+w,y+h),(0,255,0),2)
# Finally show the image
cv2.imshow('img',img)
cv2.imshow('res',thresh_color)
cv2.waitKey(0)
cv2.destroyAllWindows()
Parameters that need to be modified to obtain the result below is numbers of iterations in erode and dilate functions.
Lower values will create more bounding rectangles around (nearly) every digit/character.
Result