使用 FFmpeg、python 和 opencv 显示流
Display stream with FFmpeg, python and opencv
情况:
我有一台连接到 raspberry pi 的 basler 相机,我正在尝试使用 FFmpg 将其传输到我的 windows PC 中的 tcp 端口,以监控相机前面发生的事情。
有用的东西:
我设法在 raspberry pi 上设置了一个 python 脚本,它负责记录帧,将它们提供给管道并将它们流式传输到 tcp 端口。从那个端口,我可以使用 FFplay 显示流。
我的问题:
FFplay 非常适合快速轻松地测试您前进的方向是否正确,但我想从流中“读取”每一帧,进行一些处理,然后使用 opencv 显示流。那个,我还做不到。
最低限度地表示,这是我在 raspberry pi 方面使用的代码:
command = ['ffmpeg',
'-y',
'-i', '-',
'-an',
'-c:v', 'mpeg4',
'-r', '50',
'-f', 'rtsp',
'-rtsp_transport',
'tcp','rtsp://192.168.1.xxxx:5555/live.sdp']
p = subprocess.Popen(command, stdin=subprocess.PIPE)
while camera.IsGrabbing(): # send images as stream until Ctrl-C
grabResult = camera.RetrieveResult(100, pylon.TimeoutHandling_ThrowException)
if grabResult.GrabSucceeded():
image = grabResult.Array
image = resize_compress(image)
p.stdin.write(image)
grabResult.Release()
在我的 PC 上,如果我在终端上使用以下 FFplay 命令,它会工作并实时显示流:
ffplay -rtsp_flags listen rtsp://192.168.1.xxxx:5555/live.sdp?tcp
在我的 PC 上,如果我使用以下 python 脚本,流会开始,但它在 cv2.imshow
函数中失败,因为我不确定如何解码它:
import subprocess
import cv2
command = ['C:/ffmpeg/bin/ffmpeg.exe',
'-rtsp_flags', 'listen',
'-i', 'rtsp://192.168.1.xxxx:5555/live.sdp?tcp?',
'-']
p1 = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
while True:
frame = p1.stdout.read()
cv2.imshow('image', frame)
cv2.waitKey(1)
有谁知道我需要在这两个脚本中更改什么才能让我工作?
提前感谢您的任何提示。
您可以从 p1.stdout
中读取解码帧,将其转换为 NumPy 数组,并对其进行整形。
更改command
以获取rawvideo
格式和BGR像素格式的解码帧:
command = ['C:/ffmpeg/bin/ffmpeg.exe',
'-rtsp_flags', 'listen',
'-i', 'rtsp://192.168.1.xxxx:5555/live.sdp?tcp?',
'-f', 'image2pipe', # Use image2pipe demuxer
'-pix_fmt', 'bgr24', # Set BGR pixel format
'-vcodec', 'rawvideo', # Get rawvideo output format.
'-']
从p1.stdout
读取原始视频帧:
raw_frame = p1.stdout.read(width*height*3)
将读取的字节转换为 NumPy 数组,并将其整形为视频帧尺寸:
frame = np.fromstring(raw_frame, np.uint8)
frame = frame.reshape((height, width, 3))
现在您可以显示调用 cv2.imshow('image', frame)
的框架。
该解决方案假定您预先知道视频帧大小(width
和 height
)。
下面的代码示例包括使用 cv2.VideoCapture
读取 width
和 height
的部分,但我不确定它是否适用于您的情况(由于 '-rtsp_flags', 'listen'
。(如果它确实有效,您可以尝试使用 OpenCV 而不是 FFmpeg 进行捕获)。
以下代码是使用 public RTSP 流进行测试的完整“工作示例”:
import cv2
import numpy as np
import subprocess
# Use public RTSP Stream for testing
in_stream = 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov'
if False:
# Read video width, height and framerate using OpenCV (use it if you don't know the size of the video frames).
# Use public RTSP Streaming for testing:
cap = cv2.VideoCapture(in_stream)
framerate = cap.get(5) #frame rate
# Get resolution of input video
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# Release VideoCapture - it was used just for getting video resolution
cap.release()
else:
# Set the size here, if video frame size is known
width = 240
height = 160
command = ['C:/ffmpeg/bin/ffmpeg.exe',
#'-rtsp_flags', 'listen', # The "listening" feature is not working (probably because the stream is from the web)
'-rtsp_transport', 'tcp', # Force TCP (for testing)
'-max_delay', '30000000', # 30 seconds (sometimes needed because the stream is from the web).
'-i', in_stream,
'-f', 'image2pipe',
'-pix_fmt', 'bgr24',
'-vcodec', 'rawvideo', '-an', '-']
# Open sub-process that gets in_stream as input and uses stdout as an output PIPE.
p1 = subprocess.Popen(command, stdout=subprocess.PIPE)
while True:
# read width*height*3 bytes from stdout (1 frame)
raw_frame = p1.stdout.read(width*height*3)
if len(raw_frame) != (width*height*3):
print('Error reading frame!!!') # Break the loop in case of an error (too few bytes were read).
break
# Convert the bytes read into a NumPy array, and reshape it to video frame dimensions
frame = np.fromstring(raw_frame, np.uint8)
frame = frame.reshape((height, width, 3))
# Show video frame
cv2.imshow('image', frame)
cv2.waitKey(1)
# Wait one more second and terminate the sub-process
try:
p1.wait(1)
except (sp.TimeoutExpired):
p1.terminate()
cv2.destroyAllWindows()
示例框架(仅供娱乐):
更新:
使用FFprobe读取宽度和高度:
当您事先不知道视频分辨率时,您可以使用 FFprobe 获取信息。
这是使用 FFprobe 读取 width
和 height
的代码示例:
import subprocess
import json
# Use public RTSP Stream for testing
in_stream = 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov'
probe_command = ['C:/ffmpeg/bin/ffprobe.exe',
'-loglevel', 'error',
'-rtsp_transport', 'tcp', # Force TCP (for testing)]
'-select_streams', 'v:0', # Select only video stream 0.
'-show_entries', 'stream=width,height', # Select only width and height entries
'-of', 'json', # Get output in JSON format
in_stream]
# Read video width, height using FFprobe:
p0 = subprocess.Popen(probe_command, stdout=subprocess.PIPE)
probe_str = p0.communicate()[0] # Reading content of p0.stdout (output of FFprobe) as string
p0.wait()
probe_dct = json.loads(probe_str) # Convert string from JSON format to dictonary.
# Get width and height from the dictonary
width = probe_dct['streams'][0]['width']
height = probe_dct['streams'][0]['height']
情况: 我有一台连接到 raspberry pi 的 basler 相机,我正在尝试使用 FFmpg 将其传输到我的 windows PC 中的 tcp 端口,以监控相机前面发生的事情。
有用的东西: 我设法在 raspberry pi 上设置了一个 python 脚本,它负责记录帧,将它们提供给管道并将它们流式传输到 tcp 端口。从那个端口,我可以使用 FFplay 显示流。
我的问题: FFplay 非常适合快速轻松地测试您前进的方向是否正确,但我想从流中“读取”每一帧,进行一些处理,然后使用 opencv 显示流。那个,我还做不到。
最低限度地表示,这是我在 raspberry pi 方面使用的代码:
command = ['ffmpeg',
'-y',
'-i', '-',
'-an',
'-c:v', 'mpeg4',
'-r', '50',
'-f', 'rtsp',
'-rtsp_transport',
'tcp','rtsp://192.168.1.xxxx:5555/live.sdp']
p = subprocess.Popen(command, stdin=subprocess.PIPE)
while camera.IsGrabbing(): # send images as stream until Ctrl-C
grabResult = camera.RetrieveResult(100, pylon.TimeoutHandling_ThrowException)
if grabResult.GrabSucceeded():
image = grabResult.Array
image = resize_compress(image)
p.stdin.write(image)
grabResult.Release()
在我的 PC 上,如果我在终端上使用以下 FFplay 命令,它会工作并实时显示流:
ffplay -rtsp_flags listen rtsp://192.168.1.xxxx:5555/live.sdp?tcp
在我的 PC 上,如果我使用以下 python 脚本,流会开始,但它在 cv2.imshow
函数中失败,因为我不确定如何解码它:
import subprocess
import cv2
command = ['C:/ffmpeg/bin/ffmpeg.exe',
'-rtsp_flags', 'listen',
'-i', 'rtsp://192.168.1.xxxx:5555/live.sdp?tcp?',
'-']
p1 = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
while True:
frame = p1.stdout.read()
cv2.imshow('image', frame)
cv2.waitKey(1)
有谁知道我需要在这两个脚本中更改什么才能让我工作?
提前感谢您的任何提示。
您可以从 p1.stdout
中读取解码帧,将其转换为 NumPy 数组,并对其进行整形。
更改
command
以获取rawvideo
格式和BGR像素格式的解码帧:command = ['C:/ffmpeg/bin/ffmpeg.exe', '-rtsp_flags', 'listen', '-i', 'rtsp://192.168.1.xxxx:5555/live.sdp?tcp?', '-f', 'image2pipe', # Use image2pipe demuxer '-pix_fmt', 'bgr24', # Set BGR pixel format '-vcodec', 'rawvideo', # Get rawvideo output format. '-']
从
p1.stdout
读取原始视频帧:raw_frame = p1.stdout.read(width*height*3)
将读取的字节转换为 NumPy 数组,并将其整形为视频帧尺寸:
frame = np.fromstring(raw_frame, np.uint8) frame = frame.reshape((height, width, 3))
现在您可以显示调用 cv2.imshow('image', frame)
的框架。
该解决方案假定您预先知道视频帧大小(width
和 height
)。
下面的代码示例包括使用 cv2.VideoCapture
读取 width
和 height
的部分,但我不确定它是否适用于您的情况(由于 '-rtsp_flags', 'listen'
。(如果它确实有效,您可以尝试使用 OpenCV 而不是 FFmpeg 进行捕获)。
以下代码是使用 public RTSP 流进行测试的完整“工作示例”:
import cv2
import numpy as np
import subprocess
# Use public RTSP Stream for testing
in_stream = 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov'
if False:
# Read video width, height and framerate using OpenCV (use it if you don't know the size of the video frames).
# Use public RTSP Streaming for testing:
cap = cv2.VideoCapture(in_stream)
framerate = cap.get(5) #frame rate
# Get resolution of input video
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
# Release VideoCapture - it was used just for getting video resolution
cap.release()
else:
# Set the size here, if video frame size is known
width = 240
height = 160
command = ['C:/ffmpeg/bin/ffmpeg.exe',
#'-rtsp_flags', 'listen', # The "listening" feature is not working (probably because the stream is from the web)
'-rtsp_transport', 'tcp', # Force TCP (for testing)
'-max_delay', '30000000', # 30 seconds (sometimes needed because the stream is from the web).
'-i', in_stream,
'-f', 'image2pipe',
'-pix_fmt', 'bgr24',
'-vcodec', 'rawvideo', '-an', '-']
# Open sub-process that gets in_stream as input and uses stdout as an output PIPE.
p1 = subprocess.Popen(command, stdout=subprocess.PIPE)
while True:
# read width*height*3 bytes from stdout (1 frame)
raw_frame = p1.stdout.read(width*height*3)
if len(raw_frame) != (width*height*3):
print('Error reading frame!!!') # Break the loop in case of an error (too few bytes were read).
break
# Convert the bytes read into a NumPy array, and reshape it to video frame dimensions
frame = np.fromstring(raw_frame, np.uint8)
frame = frame.reshape((height, width, 3))
# Show video frame
cv2.imshow('image', frame)
cv2.waitKey(1)
# Wait one more second and terminate the sub-process
try:
p1.wait(1)
except (sp.TimeoutExpired):
p1.terminate()
cv2.destroyAllWindows()
示例框架(仅供娱乐):
更新:
使用FFprobe读取宽度和高度:
当您事先不知道视频分辨率时,您可以使用 FFprobe 获取信息。
这是使用 FFprobe 读取 width
和 height
的代码示例:
import subprocess
import json
# Use public RTSP Stream for testing
in_stream = 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov'
probe_command = ['C:/ffmpeg/bin/ffprobe.exe',
'-loglevel', 'error',
'-rtsp_transport', 'tcp', # Force TCP (for testing)]
'-select_streams', 'v:0', # Select only video stream 0.
'-show_entries', 'stream=width,height', # Select only width and height entries
'-of', 'json', # Get output in JSON format
in_stream]
# Read video width, height using FFprobe:
p0 = subprocess.Popen(probe_command, stdout=subprocess.PIPE)
probe_str = p0.communicate()[0] # Reading content of p0.stdout (output of FFprobe) as string
p0.wait()
probe_dct = json.loads(probe_str) # Convert string from JSON format to dictonary.
# Get width and height from the dictonary
width = probe_dct['streams'][0]['width']
height = probe_dct['streams'][0]['height']