0

I have a tello ryze drone, which has got a camera onboard. I am connecting to the drone in order to receive the video over wifi. My system is windows 10 and I am using python 2.7.

I am receiving a h264 bytecode and i use the libh264 decoder from tello in order to get the frames of the video, which i also display in my UI. What i need to do is to save this video as a file on my computer and i have problems with this. I am able to make a snapshot with opencv and save it as an image, this isn't a problem. But making a video isn't working for some reason. I read many posts here like this one but they dont work out for me. I am getting either an error or i get a very small video file, which doesnt open. My Frames are List of List with RGB values like:

[[255,200,100][55,200,100][25,20,100]]

Here is my code for a better understanding in order to help me

This is the UI Part(i just copy here the needed code only):

def videoLoop(self):
try:
    # start the thread that get GUI image and draw skeleton
    time.sleep(0.5)
    self.sending_command_thread.start()

    while not self.stopEvent.is_set():
        system = platform.system()

        # read the frame for GUI show
        self.frame = self.drone.read()

        if self.frame is None or self.frame.size == 0:
            continue

            # transfer the format from frame to image
        image = Image.fromarray(self.frame)

        # we found compatibility problem between Tkinter,PIL and Macos,and it will
        # sometimes result the very long preriod of the "ImageTk.PhotoImage" function,
        # so for Macos,we start a new thread to execute the _updateGUIImage function.
        if system == "Windows" or system == "Linux":
            self.refreshUI(image)

        else:
            thread_tmp = threading.Thread(target=self.refreshUI, args=(image,))
            thread_tmp.start()
            time.sleep(0.03)

except RuntimeError as e:
    print("[INFO] caught a RuntimeError")

def refreshUI(self, image):

    image = ImageTk.PhotoImage(image)
    # if the imagePanel none ,we need to initial it
    if self.imagePanel is None:
        self.imagePanel = tki.Label(image=image)
        self.imagePanel.image = image
        self.imagePanel.pack(side="left", fill="both",
                             expand="yes", padx=10, pady=10)
    # otherwise, simply update the imagePanel
    else:
        self.imagePanel.configure(image=image)
        self.imagePanel.image = image
def takeSnapshot(self):

    # grab the current timestamp and use it to construct the filename
    ts = datetime.datetime.now()
    filename = "{}.jpg".format(ts.strftime("%d-%m-%Y_%H-%M-%S"))

    p = os.path.sep.join((self.screenShotPath, filename))

    # save the file
    cv2.imwrite(p, cv2.cvtColor(self.frame, cv2.COLOR_RGB2BGR))
    print("[INFO] saved {}".format(filename))

Below you can find the Tello code (only the needed part too):

    def read(self):
    """Return the last frame from camera."""
    if self.is_freeze:
        return self.last_frame
    else:
        return self.frame

def video_freeze(self, is_freeze=True):
    """Pause video output -- set is_freeze to True"""
    self.is_freeze = is_freeze
    if is_freeze:
        self.last_frame = self.frame

def _receive_video_thread(self):
    """
    Listens for video streaming (raw h264) from the Tello.

    Runs as a thread, sets self.frame to the most recent frame Tello captured.

    """
    packet_data = b''
    while True:
        try:
            res_string, ip = self.socket_video.recvfrom(2048)

            packet_data += res_string
            # end of frame
            if len(res_string) != 1460:
                for frame in self._h264_decod(packet_data):
                    self.frame = frame
                packet_data = b''

        except socket.error as exc:
            print(("Caught exception sock.error : %s" % exc))

def _h264_decod(self, packet_data):
    """
    decode raw h264 format data from Tello

    :param packet_data: raw h264 data array

    :return: a list of decoded frame
    """
    res_frame_list = []
    frames = self.decoder.decode(packet_data)

    for framedata in frames:
        (frame, w, h, ls) = framedata
        if frame is not None:
            # print ('frame size %i bytes, w %i, h %i, linesize %i' % (len(frame), w, h, ls))
            frame = np.frombuffer(frame, dtype=np.ubyte, count=len(frame))
            frame = (frame.reshape((h, ls // 3, 3)))
            frame = frame[:, :w, :]
            res_frame_list.append(frame)

    return res_frame_list

It would be very kind of you if someone could help me write a method like this pseudocode:

    def saveVideo(self, frame_or_whatever_i_need_here):
        out = cv2.VideoWriter('output.avi_or_other_format', -1, 20.0, (640,480))
        out.write(frame_or_whatever_i_need_here)
        out.release()

Edit 1: i found an option making a video out of my snapshots, this means that i coud make snapshot over a thread and later save them to a video. That would be an option. The problem is, that it would consume too much space in the disk. The workaround link is here

paokdev
  • 5
  • 1
  • 5
  • _I am using python 2.7_ Why? _I am getting either an error_ Please share the entire error message, then. – AMC Feb 25 '20 at 17:19
  • @AMC because of the libh264decoder from telloryze, which isn't compatible with python 3 – paokdev Feb 26 '20 at 08:31
  • @nathancy No I didn't before but I did now. Is that possible in your code example to use ip and port instead of a link? If so that could be the solution I need – paokdev Feb 26 '20 at 08:45
  • @nathancy when trying rtsp_stream_link = '0.0.0.0:11111' i got following error: (415) cv::VideoWriter::open VIDEOIO(CV_IMAGES): raised OpenCV exception: (-5:Bad argument) CAP_IMAGES: can't find starting number (in the name of file): output.avi in function 'cv::icvExtractPattern' – paokdev Feb 26 '20 at 08:55
  • @paokdev no I dont think its possible to use IP and port with VideoCapture. Try insert your stream link into VLC player, if it works there it should work with OpenCV – nathancy Feb 26 '20 at 21:00
  • @nathancy the problem was, that I have been getting the video over the wifi from the drone, in which I had to connect in order to communicate with the drone and there wasn't a DNS name for the drone IP and port. But I resolved the problem though thank you very much for your comments and help – paokdev Feb 27 '20 at 08:44

1 Answers1

0

I found the solution, so i will post it here if someone needs the same thing. I used the following blog and modified the code to do my work, you can find the post here

self.frame = None
self.frame_array = []

 def videoLoop(self):
    try:
        # start the thread that get GUI image and draw skeleton
        time.sleep(0.5)
        self.sending_command_thread.start()

        while not self.stopEvent.is_set():
            system = platform.system()

            # read the frame for GUI show
            self.frame = self.drone.read()
            self.frame_array.append(self.frame)

            if self.frame is None or self.frame.size == 0:
                continue

                # transfer the format from frame to image
            image = Image.fromarray(self.frame)


            if system == "Windows" or system == "Linux":
                self.refreshUI(image)

            else:
                thread_tmp = threading.Thread(target=self.refreshUI, args=(image,))
                thread_tmp.start()
                time.sleep(0.03)

    except RuntimeError as e:
        print("[INFO] caught a RuntimeError")

 def convert_frames_to_video(self, pathOut, fps):

    size = (self.videoWidth, self.videoHeight)
    out = cv2.VideoWriter(pathOut, cv2.VideoWriter_fourcc(*'DIVX'), fps, size)

    for i in range(len(self.frame_array)):
        # writing to a image array
        out.write(self.frame_array[i])
    out.release()

 def onClose(self):

    print("[INFO] closing...")
    self.convert_frames_to_video('video.avi', 25.0)
    self.stopEvent.set()
    del self.drone
    self.root.quit()
paokdev
  • 5
  • 1
  • 5