My problem here is that when I extracting a video into a frame using opencv, sometimes the frame that I get will flip up which happened to me for both my machine(window) and VM(ubuntu) But some of the video I tested, frames are not flip. So, I wonder what factor or what should be changed/added in my code to make the extract fixed without a flip
def extract_frame(video,folder):
global fps
os.mkdir('./green_frame/{folder}/'.format(folder=folder))
vidcap = cv2.VideoCapture(video)
success,image = vidcap.read()
fps = vidcap.get(cv2.CAP_PROP_FPS)
count = 0
success = True
while success: #os.path.join(pathOut,(name+'.png'))
cv2.imwrite(os.path.join('./green_frame/{folder}/'.format(folder=folder),"frame%d.png" % count), image)
success,image = vidcap.read()
print('Read a new frame: ', success)
count += 1
This is the example of frame I get from this code.
Which my orginal video that I used is upside down like this:
So, in my case, what I have to changed to make it not flip like my first picture. Is it relate to the resolution or framerate of the video? I tested with a 1280x720 resolution video and all of the frame extracted are flipped upside down but a frame from video with a 568x320 is normal
Thank you
Edit:
So, I look at the information of the video and I found out that in the metadata, it has rotate 180 for the video that extract to an upside down frame
But when I check with a normal video that produced a non upside-down frame, it does not have rotate:180
So from this, how can I deal with a video that has a rotation?
Update
This problem can now be solved by simply updating to OpenCV v4.5 and above.
If upgrading is a problem follow the old answer, below.
For anyone still looking into this, I was just stuck on the same problem. Turns out some Android phones and iPhones take images/frames in landscape and convert them on the fly according to the exif 'rotate' tag to display the images/frames.
Weird design choice in OpenCV is that cv2.imread(img_file) already reads the image in correct orientation by reading the image's rotate tag, but the cv2.VideoStream's read() method does not do this.
So, to fix this I used ffmpeg to read the 'rotate' tag and rotate the video frame to its correct orientation.(Big thanks to the comments above, for pointing me in the right direction 👍)
Following is the code:
Make sure you have ffmpeg for python. (pip install ffmpeg-python)
Create a method to check if rotation is required by the video_file:
import ffmpeg
def check_rotation(path_video_file):
# this returns meta-data of the video file in form of a dictionary
meta_dict = ffmpeg.probe(path_video_file)
# from the dictionary, meta_dict['streams'][0]['tags']['rotate'] is the key
# we are looking for
rotateCode = None
if int(meta_dict['streams'][0]['tags']['rotate']) == 90:
rotateCode = cv2.ROTATE_90_CLOCKWISE
elif int(meta_dict['streams'][0]['tags']['rotate']) == 180:
rotateCode = cv2.ROTATE_180
elif int(meta_dict['streams'][0]['tags']['rotate']) == 270:
rotateCode = cv2.ROTATE_90_COUNTERCLOCKWISE
return rotateCode
Create a method to correct the rotation of the frame in video file:
def correct_rotation(frame, rotateCode):
return cv2.rotate(frame, rotateCode)
Finally, do this in your main loop:
# open a pointer to the video file stream
vs = cv2.VideoCapture(video_path)
# check if video requires rotation
rotateCode = check_rotation(video_path)
# loop over frames from the video file stream
while True:
# grab the frame from the file
grabbed, frame = vs.read()
# if frame not grabbed -> end of the video
if not grabbed:
break
# check if the frame needs to be rotated
if rotateCode is not None:
frame = correct_rotation(frame, rotateCode)
# now your logic can start from here
Hope this helps 🍻
Sometimes the following will solve the problem of opening some videos upside-down.
cap = cv2.VideoCapture(path, apiPreference=cv2.CAP_MSMF)
When I recently ran into this issue, I found that all that was required was to update to OpenCV v4.5:
This is the related Github issue: https://github.com/opencv/opencv/issues/15499
Here is the commit: https://github.com/opencv/opencv/commit/f0271e54d90b3af62301f531f5f00995b00d7cd6
The rotate tag is optional so the check_rotation will fail,
This code fix it:
def check_rotation(path_video_file):
# this returns meta-data of the video file in form of a dictionary
meta_dict = ffmpeg.probe(path_video_file)
# from the dictionary, meta_dict['streams'][0]['tags']['rotate'] is the key
# we are looking for
rotate_code = None
rotate = meta_dict.get('streams', [dict(tags=dict())])[0].get('tags', dict()).get('rotate', 0)
return round(int(rotate) / 90.0) * 90
I would just do this in your frame processing loop:
frame = cv2.flip(frame,0)
The 0 flips vertically, see Open CV documentation for more info.
Related
Im trying to develop a simple code using opencv and python. My idea is the following:
I have a video with a moving object(free falling or parabolic) and I've managed to separate the video in frames. What I need (and I'm a total newby in this aaaand have little time) is to extract coordinates of the object frame by frame. So the idea is to click on the moving object, and get the (x, y) coordinate and the number of frame and open the next frame so I can do the same. So basically something with clicks over the foto and extracting the data in a csv and showing the next frame. Thus I could study its movement through space with its velocity and acelerations and stuff.
Haven't written any code yet.
Thanks in advance.
Look at docs example with using mouse input in opencv w python:
mouse example - opencv
You can define callback reading the click coordinates:
def get_clic_point(event,x,y,flags,param):
if event == cv.EVENT_LBUTTONDBLCLK: # on left double click
print(x,y) # here goes Your sepcific code, using x, y as click coordinates
In main loop, You need to create window and supply it with callback method
cv.namedWindow('main_win')
cv.setMouseCallback('main_win',get_clic_point)
Then using window name (in this case 'main_win') as Your window handle, You can
show image, calling cv.imshow('main_win',img), where img is loaded image.
You can write simple loop like this:
cv.namedWindow('main_win')
cv.setMouseCallback('main_win',get_clic_point)
images = []
# read images to this list
# ...
i = 0
while(1):
cv.imshow('main_win',images[i])
k = cv.waitKey(1) & 0xFF
if k == ord('n'): # n for next
# change image
i = i + 1
i = i % len(images)
elif k == 27:
break
cv.destroyAllWindows()
then just substitiute call back with desired logic
Currently I am using 2017_08_31_0121.mp4 as my video which is a 21-second long video and once I break it into frames, I get 504 frames. This means that frame per second is set to 24. I want to change the number of frames but I do not know which part of the following code is responsible for setting frame per second.
Questions:
I thought for a long time that the default FPS is 25 but now I have 24, can you please let me know where the default FPS is set and what it is?
If I want to use a custom FPS let's say 10, how can I modify the following code to do it?
import cv2
vidcap = cv2.VideoCapture('/content/2017_08_31_0121.mp4')
success, image = vidcap.read()
count = 0
while success:
if count<10:
id = f'00{count}'
elif count < 100:
id = f'0{count}'
else:
id = count
cv2.imwrite(f"./new_frames/frame{id}.jpg", image) # save frame as JPEG file
success, image = vidcap.read()
count += 1
You need to know how "video" works.
Video consists of keyframes and P/B-frames. Keyframes are complete images on their own. To decode a P/B-frame, preceding frames need to be decoded first. Some video files consist only of keyframes ("intra"). Some video files consist of a keyframe every ~0.1-10 seconds, and only P/B-frames in between.
You can skip around in a video, but you can only directly skip to keyframes. If you wanted to skip to a non-keyframe, you'd have to first skip to the preceding keyframe, and then decode each following frame, until you're at the destination.
Ideas I would recommend that you not follow:
ffmpeg -i INPUT -r 3 OUTPUT would read the entire video, and duplicate/drop frames as needed to achieve 3 frames per second, while maintaining the "speed" of what you see in the video. It would also have to re-encode the result. That's only a sensible option if you need to read the same video repeatedly in that frame rate.
Involving GNU Parallel with ffmpeg would be pointless because ffmpeg itself runs its decoding and encoding in parallel (for most codecs), using all available CPU.
Here is what you can do:
use the methods grab and retrieve of VideoCapture. Call grab repeatedly. This does the minimal work to decode a frame and advance in the video. Call retrieve for the frame you actually want. This does the rest of the work and it will give you that frame as an array.
You would have to check the video's fps value using vidcap.get(cv.CAP_PROP_FPS) and then count along and decide if you only need to grab, or both grab and retrieve.
import numpy as np
import cv2 as cv
vidcap = cv2.VideoCapture('/content/2017_08_31_0121.mp4')
assert vidcap.isOpened()
fps_in = vidcap.get(cv.CAP_PROP_FPS)
fps_out = 3
index_in = -1
index_out = -1
while True:
success = vidcap.grab()
if not success: break
index_in += 1
out_due = int(index_in / fps_in * fps_out)
if out_due > index_out:
success, frame = vidcap.retrieve()
if not success: break
index_out += 1
# do something with `frame`
I am using the following code to animate gifs on tkinter.
class gif:
def __init__(self,filename):
fIndex = 0
global frames
frames = []
global lastFrame
while True:#infinite loop
try:#executes following code if possible
part = 'gif -index {}'.format(fIndex)#takes an index of a frame of gif
frame = PhotoImage(file=filename,format=part)
print(str(fIndex))
#converts each frame to a PhotoImage
except:#once the last frame is reached, this is executed
lastFrame = fIndex - 1#last frame index saved
break #while loop ends
frames.append(frame) #photoimage of frame added to list
fIndex += 1 #index incremented
def animate(self,fNumber):#function to animate gif
if fNumber > lastFrame: #if the last frame is reached,
fNumber = 0 #index changed to 0 so it plays again
canvas.create_image(700,350,image=frames[fNumber],anchor=CENTER)
master.after(20,self.animate,fNumber+1)#each frame played with
#20 milliseconds in between
matr1x = gif('spinningmatrix.gif')
matr1x.animate(0)
this works for some of my gifs but not all of them. For the ones that don't work, when I print fIndex it only displays 0 and 1 and only the first frame is displayed on the GUI. the ones that do work have more indexes displayed. just wondering why some don't work and why it only gives me 0 and 1 as the indexes. any help would be appreciated.
edit: I split the gifs into frames online and for 2 of the gifs that work properly, my program gives the correct number of frames. for one that works but is a bit off, the program says 3 frames but online says 20. for the other 2 that don't work at all, the number of frames differ very much online.
Here is the code for the same, have a look at it. In this, below code I am creating a Motion Detector and with this I will be recording the timings of when the various objects appeared and disappeared for which I am using dataframe.
The issue with this is that the program executes but when the output is displayed in the form of a Window, it freezes when I try to terminate.
import cv2, pandas
from datetime import datetime
first_frame = None
status_list = [None,None]
times = []
df = pandas.DataFrame(columns=["Start", "End"]) #Dataframe to store the time values during which object detection and movement appears.
video = cv2.VideoCapture(0)#VideoCapture object is used to record video using web cam
while True:
#check is a bool data type, returns true if VideoCapture object is read
check,frame = video.read()
status = 0
gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) # For converting the frame color to gray scale
gray = cv2.GaussianBlur(gray,(21,21),0) # For converting the gray scale frame to GaussianBlur
if first_frame is None:
first_frame = gray # used to store the first image/frame of the video
continue
delta_frame = cv2.absdiff(first_frame,gray)#calculates the difference between first and other frames
thresh_delta = cv2.threshold(delta_frame,30,255,cv2.THRESH_BINARY)[1]
thresh_delta = cv2.dilate(thresh_delta,None,iterations=0) #Provides threshold value, so if the difference is <30 it will turn to black otherwise if >30 pixels will turn to white
(_,cnts,_) = cv2.findContours(thresh_delta.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) #Define the contour area,i.e. adding borders
#Removing noises and shadows, any part which is greater than 1000 pixels will be converted to white
for contour in cnts:
if cv2.contourArea(contour) < 1000:
continue
status = 1 #change in status when the object is detected
#Creating a rectangular box around the object in frame
(x,y,w,h) = cv2.boundingRect(contour)
cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),3)
#list of status for every frame
status_list.append(status)
status_list=status_list[-2:]
#Record datetime in a list when change occurs
if status_list[-1]==1 and status_list[-2]==0:
times.append(datetime.now())
if status_list[-1]==0 and status_list[-2]==1:
times.append(datetime.now())
cv2.imshow('frame',frame)
#cv2.imshow('Capturing',gray)
#cv2.imshow('delta',delta_frame)
#cv2.imshow('thresh',thresh_delta)
key = cv2.waitKey(1) #changing the frame after 1 millisecond
#Used for terminating the loop once 'q' is pressed
if key == ord('q'):
break
print(status_list)
print(times)
for i in range(0,len(times),2):
df = df.append({"Start":times[i],"End":times[i+1]},ignore_index=True)
df.to_csv("Times.csv")
video.release()
cv2.destroyAllWindows #will be closing all the windows
Try this:
if cv2.waitKey(1) & 0xFF == ord('q'):
break
For a brief explanation about what "& 0xFF" is, see: What's 0xFF for in cv2.waitKey(1)?
It is basically a bit mask that takes the portion of 'waitKey' value(32 bit) that can be compared to ASCII (8 bit).
You can try this.
k = cv2.waitKey(1) & 0xFF
if k == 27 :
break
where k can be any ASCII code on the keyboard ASCII code help
In this case 27 is 'Esc' button.
However, the problem you're having where it is freezing, have you tried continuously pressing any button besides q? I had a similar problem when I was testing out OpenCV's tutorial code here
but i figured it out by playing around and then realizing i needed to hold down any button besides the 'exit' button.
I had faced the same issue bcz of this piece of code from geeksforgeeks
Please chck the last line of the code:
cv2.destroyAllWindows #will be closing all the windows
Add parenthesis, it shud b:
cv2.destroyAllWindows()
Please try this, rest looks fine
Well you can also pass a particular frame that you want to close like:
cv2.destroyAllWindows("frame")
I need to compare 2 videos to check whether they are the same.
So I am planning to do the following:
Split both the videos into individual frames
Compare each frame with the corresponding frame of the ref video using Python Image Lib
Count the number of different frames to decide whether they are same.
I would like to know whether there is any function in Python to help me with the first step, i.e., to split the video into individual frames. I do not want to use ffmpeg to do the splitting.
Thanks in advance for the help
You can use opencv
import cv2
video_capture = cv2.VideoCapture("rtsp://admin:admin#192.168.0.94:554/stream3")
while True:
# get frame by frame
ret, frame = video_capture.read()
cv2.imwrite('pic.png',frame)
cv2.imshow('Video', frame)
Try this:
currentFrame = 0
while(True):
# Capture frame-by-frame
ret, frame = cap.read()
# Saves image of the current frame in jpg file
name = './data/frame' + str(currentFrame) + '.jpg'
print ('Creating...' + name)
cv2.imwrite(name, frame)
# To stop duplicate images
currentFrame += 1
# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()
#you can use this method