14

How can I pipe openCV images to ffmpeg (running ffmpeg as a subprocess)? (I am using spyder/anaconda)

I am reading frames from a video file and do some processing on each frame.

import cv2   
cap = cv2.VideoCapture(self.avi_path)
img = cap.read()
gray = cv2.cvtColor(img[1], cv2.COLOR_BGR2GRAY)
bgDiv=gray/vidMed #background division

then, to pipe the processed frame to ffmpeg, I found this command in a related question:

sys.stdout.write( bgDiv.tostring() )

next, I am trying to run ffmpeg as a subprocess:

cmd='ffmpeg.exe -f rawvideo -pix_fmt gray -s 2048x2048 -r 30 -i - -an -f avi -r 30 foo.avi'
sp.call(cmd,shell=True)

(this also from the mentioned post) However, this fills my IPython console with cryptic hieroglyphs and then crashes it. any advice?

ultimately, I would like to pipe out 4 streams and have ffmpeg encode those 4 streams in parallel.

jlarsch
  • 2,217
  • 4
  • 22
  • 44
  • Is there a reason why you wouldn't want to use OpenCV VideoWriter interface to write out your video? I don't think you can pipe out 4 streams at once. – mirosval Dec 09 '15 at 07:26
  • 1
    I was hoping this would be faster for saving the video. I noticed that my openCV read-write example above using VideoWriter took about twice as long as a pure ffmpeg encoding of the same input video (granted, the ffmpeg encoding didn't include the background division - which is why I gave openCV a try in the first place). I was hoping to use named pipes... – jlarsch Dec 09 '15 at 07:57

3 Answers3

24

I had similar problem once. I opened an issue on Github, turns out it may be a platform issue.

Related to your question, you can as well pipe OpenCV images to FFMPEG. Here's a sample code:

# This script copies the video frame by frame
import cv2
import subprocess as sp

input_file = 'input_file_name.mp4'
output_file = 'output_file_name.mp4'

cap = cv2.VideoCapture(input_file)
ret, frame = cap.read()
height, width, ch = frame.shape

ffmpeg = 'FFMPEG'
dimension = '{}x{}'.format(width, height)
f_format = 'bgr24' # remember OpenCV uses bgr format
fps = str(cap.get(cv2.CAP_PROP_FPS))

command = [ffmpeg,
        '-y',
        '-f', 'rawvideo',
        '-vcodec','rawvideo',
        '-s', dimension,
        '-pix_fmt', 'bgr24',
        '-r', fps,
        '-i', '-',
        '-an',
        '-vcodec', 'mpeg4',
        '-b:v', '5000k',
        output_file ]

proc = sp.Popen(command, stdin=sp.PIPE, stderr=sp.PIPE)

while True:
    ret, frame = cap.read()
    if not ret:
        break
    proc.stdin.write(frame.tostring())

cap.release()
proc.stdin.close()
proc.stderr.close()
proc.wait()
Ekrem Doğan
  • 674
  • 6
  • 13
  • I am getting an error: pipe.proc.stdin.write(frame.tostring()) AttributeError: 'Popen' object has no attribute 'proc – jlarsch Dec 09 '15 at 10:24
  • When I change your code to `pipe.stdin.write(frame.tostring())`, I get another error: `pipe.stdin.write(frame.tostring()) IOError: [Errno 22] Invalid argumen` – jlarsch Dec 09 '15 at 10:30
  • This is python 2.7 code. What version is your python? – Ekrem Doğan Dec 09 '15 at 11:06
  • I had tried these changes already. With this code I am getting: `IOError: [Errno 22] Invalid argument`. – jlarsch Dec 09 '15 at 11:57
  • @jlarsch yes, you are right. There was a mistake in ffmpeg command, fixed it now. I just tested this code on my machine, it works just fine! – Ekrem Doğan Dec 09 '15 at 12:16
  • How many frames can you process in this way? My python hangs once I reach about 1000 frames of 1 megapixels each. CPU usage drops to baseline. – jlarsch Dec 09 '15 at 14:15
  • the hanging can be prevented by omitting `stderr=sp.PIPE` – jlarsch Dec 09 '15 at 14:22
  • @jlarsch Python runs on only one physical CPU. If you want to process your images one way or other, your program will most likely be bounded by CPU. If you don't process images, only replicating them (many copies) will be bounded by I/O and process may be speed up using Python threads. – Ekrem Doğan Dec 09 '15 at 14:40
  • 1
    For future users who stumble upon this answer. While @Ekrem Doğan answer works for Python 2, similar code can be found for Python 3 with better comments and explanation [here][1]. [1]: https://stackoverflow.com/a/61281547/2127561 – Rakshit Kothari Feb 17 '22 at 14:11
6

I'm Kind of late, But my powerful VidGear Python Library automates the process of pipelining OpenCV frames into FFmpeg on any platform with its WriteGear API's Compression Mode. OP, You can implement your answer as follows:

# import libraries
from vidgear.gears import WriteGear
import cv2

output_params = {"-s":"2048x2048", "-r":30} #define FFmpeg tweak parameters for writer

stream = cv2.VideoCapture(0) #Open live webcam video stream on first index(i.e. 0) device

writer = WriteGear(output_filename = 'Output.mp4', compression_mode = True, logging = True, **output_params) #Define writer with output filename 'Output.mp4' 

# infinite loop
while True:
    
    (grabbed, frame) = stream.read()
    # read frames

    # check if frame empty
    if not is grabbed:
        #if True break the infinite loop
        break
    

    # {do something with frame here}
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # write a modified frame to writer
    writer.write(gray) 
       
    # Show output window
    cv2.imshow("Output Frame", frame)

    key = cv2.waitKey(1) & 0xFF
    # check for 'q' key-press
    if key == ord("q"):
        #if 'q' key-pressed break out
        break

cv2.destroyAllWindows()
# close output window

stream.release()
# safely close video stream
writer.close()
# safely close writer

Source: https://abhitronix.github.io/vidgear/latest/gears/writegear/compression/usage/#using-compression-mode-with-opencv

You can check out VidGear Docs for more advanced applications and features.

Hope that helps!

abhiTronix
  • 1,248
  • 13
  • 17
  • Even a bit later... op is going opposite direction. Can your api send cv input to a Youtube live stream? Example of the WriteGear usage? – dbmitch Jun 29 '20 at 04:46
  • @dbmitch Thanks, answer updated. Can you send cv input to a Youtube live stream with FFmpeg directly? If yes, then WriteGear can do it, if not, than the answer is no. – abhiTronix Jun 29 '20 at 06:22
  • Okay - I'll give it a shot - but what would I gain with WriteGear if I can do it – dbmitch Jun 29 '20 at 21:19
  • effortlessness and flexibility. – abhiTronix Jun 30 '20 at 03:09
  • A good example would be better - WriteGear requires an output filename - how would you use to send it to a stream? No examples on your github. Can you use your ffmpeg command with a cv2 stream? – dbmitch Jun 30 '20 at 18:38
  • Your example uses cv stream but specifies a filename for output. Docs say that's required. So I assume you can't specify an output stream url in ffmpeg parameters? Do we just replace output.mp4 with the receiving stream url? – dbmitch Jul 02 '20 at 21:21
  • Your lib does NOT seem to support audio write-out! – Thư Sinh Nov 07 '21 at 18:26
  • @ThưSinh I beg to differ, See https://abhitronix.github.io/vidgear/latest/gears/writegear/compression/usage/#using-compression-mode-with-live-audio-input and https://abhitronix.github.io/vidgear/latest/help/writegear_ex/#using-writegears-compression-mode-to-add-external-audio-file-input-to-video-frames – abhiTronix Nov 08 '21 at 04:17
  • @dbmitch See https://abhitronix.github.io/vidgear/latest/help/writegear_ex/#using-writegears-compression-mode-for-youtube-live-streaming – abhiTronix Nov 08 '21 at 04:18
0

You can use this pkg. ffmpegcv has Reader and Writer in ffmpeg backbone, similar to cv2.

#!pip install ffmpegcv
import ffmpegcv
vfile_in = 'A.mp4'
vfile_out = 'A_h264.mp4'
vidin = ffmpegcv.VideoCapture(vfile_in)
w, h = vidin.width, vidin.height
vidout = ffmpegcv.VideoWriter(vfile_out, 'h264_nvenc', vidin.fps, (w, h))

for frame in vidin:
    vidout.write(frame)

vidin.release()
vidout.release()