4

I am creating Color Video(RGB) using OpenCV in my application and generated video file needs to be uploaded to server. Color video file size is large enough to create bottleneck while uploading to server in the current bandwidth available. So, i tried to reduce the file size by converting it to grayscale video in the opencv. Please find below the OpenCV implementation of my current work:

cap = cv2.VideoCapture(RGB_video_filepath)
    fps = cap.get(cv2.CAP_PROP_FPS)
    print("Input Video FPS: ".format(fps))
    outputfilepath = "gray_video_output.avi"

    mjpg_forcc = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')
    divx_forcc = cv2.VideoWriter_fourcc(*'DIVX')
    xvid_forcc = cv2.VideoWriter_fourcc(*'XVID')
    fmpp4_codec = cv2.VideoWriter_fourcc('F','M','P','4')
    mp4v_codec = cv2.VideoWriter_fourcc(*'MP4V')
    vid_writer = cv2.VideoWriter(outputfilepath, mjpg_codec, 2, (640, 480), 0)

    while cv2.waitKey(1) < 0:
        # get frame from the video
        hasFrame, frame = cap.read()


        # Stop the program if reached end of video
        if not hasFrame:
            print("Done processing !!!")
            print("Output file is stored as ", outputfilepath)
            break

        gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        vid_writer.write(gray_frame)
        print("Frame shape: {} {}".format(frame_count, frame.shape))
        cv2.imshow("Camera frame", frame)
        cv2.waitKey(1)

    print("Total frames: {}".format(frame_count))
    vid_writer.release()
    cap.release()

Using above workflow, i created the GRAY scale video, but i found that video file sizes are almost same (RGB video file size : 25 MB, Gray scale video size: 23 MB).

After digging into OpenCV, i found that OpenCV copies the grayscale(single channel) frame 3 times and writes into video as 3 channel although OpenCV uses FFMPEG for video file writing on Linux based OS.

I tried to convert the same RGB video file to Grayscale video file using FFMPEG as below:

ffmpeg -i inputvideofile -vf hue=s=0 outputvideofile

Here, i kept the Hue and saturation channel to be empty and surprisingly RGB video file(25 MB) gets converted to gray scale with file size reduced to 6 MB.

**I am curious to know if we can achieve the video file size reduction by converting RGB to Gray scale using OpenCV on the fly? **

Any help/update is appreciated. Thanks!!

flamelite
  • 2,654
  • 3
  • 22
  • 42
  • 1
    There may be a correct way to do it, but you could start `ffmpeg` (with your effective parameters) as a subprocess at the start with its input connected via a pipe to the output of your Python program. You just then `write()` the raw frame to `stdout` and `ffmpeg` will make your video and you get multi-processing for free since the video compression will be in a separate process. I did something similar with `CImg` rather than `OpenCV` and in C, but you get the idea... https://stackoverflow.com/a/46710797/2836621 – Mark Setchell Jan 27 '20 at 08:48
  • Show the full log from your ffmpeg command. – llogan Jan 27 '20 at 19:09
  • @llogan, ffmpeg is giving information on the input file and output file in multiple lines. I am not sure if that is required to describe the problem here. – flamelite Jan 28 '20 at 08:37
  • @MarkSetchell your approach seems correct and it can also be implemented using ffmpeg-python module as answered iDilip – flamelite Jan 28 '20 at 08:39
  • @flamelite That's what I was looking for, but there are always other questions to answer. – llogan Jan 28 '20 at 18:05

1 Answers1

3

OpenCV uses FFMPEG. Try below code to save numpy array of each frame as video using ffmpeg-python and compare the size.

import numpy as np
import cv2
import ffmpeg

def save_video(cap,saving_file_name,fps=33.0):

    if cap.isOpened():
        ret, frame = cap.read()
        if ret:
            i_width,i_height = frame.shape[1],frame.shape[0]

    process = (
    ffmpeg
        .input('pipe:',format='rawvideo', pix_fmt='rgb24',s='{}x{}'.format(i_width,i_height))
        .output(saved_video_file_name,pix_fmt='yuv420p',vcodec='libx264',r=fps,crf=37)
        .overwrite_output()
        .run_async(pipe_stdin=True)
    )

    return process

if __name__=='__main__':

    cap = cv2.VideoCapture(0,cv2.CAP_DSHOW)
    cap.set(3,1920)
    cap.set(4,1080)
    saved_video_file_name = 'output.avi'
    process = save_video(cap,saved_video_file_name)

    while(cap.isOpened()):
        ret, frame = cap.read()
        if ret==True:
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # Convert frame/img to gray
            frame = cv2.flip(frame,0)
            process.stdin.write(
                cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    .astype(np.uint8)
                    .tobytes()
                    )

            cv2.imshow('frame',frame)
            if cv2.waitKey(1) & 0xFF == ord('q'):
                process.stdin.close()
                process.wait()
                cap.release()
                cv2.destroyAllWindows()
                break
        else:
            process.stdin.close()
            process.wait()
            cap.release()
            cv2.destroyAllWindows()
            break
Dilip Lilaramani
  • 1,126
  • 13
  • 28
  • If I'm not wrong, your main purpose is to reduce the video file size and thats what above code is doing it. First its capturing the video from camera and then saving it after some processing using ffmpeg library. To make image gray, you can use this - img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) – Dilip Lilaramani Jan 27 '20 at 12:15
  • Okay, I have added the line to convert image to grayscale just after reading frame. One loop inside save_video function to get the frame size and it runs for one frame only and then break the loop. Second loop is running in main function on each frame which is using ffmpeg object. Hope that clears your doubt. – Dilip Lilaramani Jan 27 '20 at 13:09
  • I implemented the above approach with some changes in the ffmpeg video file writer initialisation and gray file conversion. It seems to reduce the file size but ffmpeg is consuming high cpu resource. – flamelite Jan 28 '20 at 08:43
  • After a quick research i found this link https://stackoverflow.com/a/42301503/5545458 which says ffmpeg is reencoding the frame while writing which can be disabled using "-c:v copy -c:a copy" parameter. I am not sure how to integrate this ffmpeg command line argument with ffmpeg-python module. – flamelite Jan 28 '20 at 08:47
  • @flamelite You could maybe try setting `pix_fmt=gray` rather than `rgb24` and just sending the greyscale down the pipe to `ffmpeg` which should allow you to remove the `-vf hue=s=0` which should reduce the CPU load, maybe. – Mark Setchell Jan 28 '20 at 08:51
  • 1
    @iDilip I am removing my earlier suggestions and upvoting your improved answer. – Mark Setchell Jan 28 '20 at 09:09
  • @flamelite can you please accept as this answer if it worked for you. – Dilip Lilaramani Jan 28 '20 at 10:33
  • Thanks @iDilip for the answer. It works for me, but i am still looking to optimise ffmpeg frame writing function because it is taking most of my device CPU. – flamelite Jan 29 '20 at 05:41