8
votes

I'm developing a Python module, with OpenCV, that connects to an RTSP stream to perform some preprocessing on the video (mostly, reducing fps and resolution), and then store it in the file system.

But, even after trying several codecs, looking for similar developments... I always end up with an empty video. I've seen this other thread (cv::VideoWriter yields unreadable video), which may be similar, but was developed on C++.

Has anyone worked on this? I normally use a sample RTSP stream as reference, such as rtsp://freja.hiof.no:1935/rtplive/definst/hessdalen03.stream, and can receive and even watch the stream from VLC correctly.

I've seen quite a lot of threads discussing how to capture video from an RTSP stream, or how to work with VideoWriters and VideoReaders classes and video files, but almost nothing combining the two.

Any help would be highly appreciated :) Thanks!!


Edit 1: Sample code used to store the frames.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import cv2
import numpy

# Test frame.
width, height = 400, 300
width_2, height_2 = int(width / 2), int(height / 2)
frame = numpy.zeros((height, width, 3), numpy.uint8)
cv2.rectangle(frame, (0, 0), (width_2, height_2), (255, 0, 0), cv2.FILLED)
cv2.rectangle(frame, (width_2, height_2), (width, height), (0, 255, 0), cv2.FILLED)

frames = [frame for _ in range(100)]
fps = 25

# Define the codec.
#fourcc = cv2.VideoWriter_fourcc(*'X264')
#fourcc = cv2.VideoWriter_fourcc(*'XVID')
fourcc = cv2.VideoWriter_fourcc(*'MJPG')

# Create VideoWriter object
out = cv2.VideoWriter(filename='video.avi',
                      fourcc=fourcc,
                      apiPreference=cv2.CAP_FFMPEG,
                      fps=float(fps),
                      frameSize=(width, height),
                      isColor=True)

result = 0
for frame in frames:
    result += 0 if out.write(frame) is None else 1
print(result)

out.release()

Edit 2: Solution

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import cv2
import numpy

# Test frame.
width, height = 400, 300
width_2, height_2 = int(width / 2), int(height / 2)

frame1 = numpy.zeros((height, width, 3), numpy.uint8)
cv2.rectangle(frame1, (0, 0), (width_2, height_2), (255, 0, 0), cv2.FILLED)
cv2.rectangle(frame1, (width_2, height_2), (width, height), (0, 255, 0), cv2.FILLED)
cv2.imwrite('frame1.jpg', frame1)

frame2 = numpy.zeros((height, width, 3), numpy.uint8)
cv2.rectangle(frame2, (width_2, 0), (width, height_2), (255, 0, 0), cv2.FILLED)
cv2.rectangle(frame2, (0, height_2), (width_2, height), (0, 255, 0), cv2.FILLED)
cv2.imwrite('frame2.jpg', frame2)

range1 = [frame1 for _ in range(10)]
range2 = [frame2 for _ in range(10)]
frames = range1 + range2 + range1 + range2 + range1
fps = 2

# Define the codec.
fourcc = cv2.VideoWriter_fourcc(*'MJPG')

# Create VideoWriter object
out = cv2.VideoWriter('video.avi', fourcc, float(fps), (width, height))

for frame in frames:
    out.write(frame)

out.release()
1
Are you able to see the stream? I mean with imshow instead of saving it to disk? Are you able to create a video with a dummy image, for example an image all red?You should go step by step, first make sure you can connect and retrieve the images, then focus on saving.... the link you provide looks more a rtmp link than a rtsp, usually port 554 is for rtsp and 1935 for rtmp... - api55
Hi @api55 ! Yes, I can see the frames retrieved from the RTSP stream. In fact, for now I was storing them as jpeg images instead. But storing them as a video should be more efficient. But, as you said, it's likely the saving what is going wrong. I prepared a dummy frame with numpy, and the video still comes out wrong. See code attached, resulting in a video with 240.924 bytes that can't be displayed by VLC. - rafamartinc
Great, now your question has an MCVE. I know that getting the fourcc + extension is most of a time a horrible experience... try first to see what codecs are installed in your machine, instead of a FOURCC pass -1 and a menu should appear and you can select a codec - api55
It returns (on Ubuntu) the message 'OpenCV: FFMPEG: format avi / AVI (Audio Video Interleaved)', and a list with 489 tags such as 'fourcc tag 0x34363248/'H264' codec_id 001B'. For that very one, as an example, it replies 'Could not find encoder for codec id 27: Encoder not found' when trying to use it as fourcc tag, even though it's on the list. - rafamartinc

1 Answers

6
votes

Here's a RTSP stream to video widget. I would recommend creating another thread for obtaining the frames as cv2.VideoCapture.read() is blocking. This can be expensive and cause latency as the main thread has to wait until it has obtained a frame. By putting this operation into a separate thread that just focuses on grabbing frames and processing/saving the frames in the main thread, it dramatically improves performance. You also can experiment with other codecs but using MJPG should be safe since its built into OpenCV. I used my IP camera stream and saved the frames to output.avi. Be sure to change rtsp_stream_link to your own RTSP stream link. :)

Output Video Screenshot

from threading import Thread
import cv2

class RTSPVideoWriterObject(object):
    def __init__(self, src=0):
        # Create a VideoCapture object
        self.capture = cv2.VideoCapture(src)

        # Default resolutions of the frame are obtained (system dependent)
        self.frame_width = int(self.capture.get(3))
        self.frame_height = int(self.capture.get(4))

        # Set up codec and output video settings
        self.codec = cv2.VideoWriter_fourcc('M','J','P','G')
        self.output_video = cv2.VideoWriter('output.avi', self.codec, 30, (self.frame_width, self.frame_height))

        # Start the thread to read frames from the video stream
        self.thread = Thread(target=self.update, args=())
        self.thread.daemon = True
        self.thread.start()

    def update(self):
        # Read the next frame from the stream in a different thread
        while True:
            if self.capture.isOpened():
                (self.status, self.frame) = self.capture.read()

    def show_frame(self):
        # Display frames in main program
        if self.status:
            cv2.imshow('frame', self.frame)

        # Press Q on keyboard to stop recording
        key = cv2.waitKey(1)
        if key == ord('q'):
            self.capture.release()
            self.output_video.release()
            cv2.destroyAllWindows()
            exit(1)

    def save_frame(self):
        # Save obtained frame into video output file
        self.output_video.write(self.frame)

if __name__ == '__main__':
    rtsp_stream_link = 'your stream link!'
    video_stream_widget = RTSPVideoWriterObject(rtsp_stream_link)
    while True:
        try:
            video_stream_widget.show_frame()
            video_stream_widget.save_frame()
        except AttributeError:
            pass