├── .github
└── workflows
│ └── python-publish.yml
├── .gitignore
├── README.md
├── README_CN.md
├── ffmpegcv
├── __init__.py
├── ffmpeg_noblock.py
├── ffmpeg_reader.py
├── ffmpeg_reader_camera.py
├── ffmpeg_reader_cuda.py
├── ffmpeg_reader_noblock.py
├── ffmpeg_reader_pannels.py
├── ffmpeg_reader_qsv.py
├── ffmpeg_reader_stream.py
├── ffmpeg_reader_stream_realtime.py
├── ffmpeg_writer.py
├── ffmpeg_writer_noblock.py
├── ffmpeg_writer_qsv.py
├── ffmpeg_writer_stream_realtime.py
├── stream_info.py
├── version.py
└── video_info.py
└── setup.py
/.github/workflows/python-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will upload a Python Package using Twine when a release is created
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Upload Python Package
10 |
11 | on:
12 | release:
13 | types: [published]
14 |
15 | permissions:
16 | contents: read
17 |
18 | jobs:
19 | deploy:
20 |
21 | runs-on: ubuntu-latest
22 |
23 | steps:
24 | - uses: actions/checkout@v3
25 | - name: Set up Python
26 | uses: actions/setup-python@v3
27 | with:
28 | python-version: '3.x'
29 | - name: Install dependencies
30 | run: |
31 | python -m pip install --upgrade pip
32 | pip install build
33 | - name: Build package
34 | run: python -m build
35 | - name: Publish package
36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
37 | with:
38 | skip-existing: true
39 | user: __token__
40 | password: ${{ secrets.PYPI_API_TOKEN }}
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | *.pyc
3 | /dist
4 | /ffmpegcv.egg-info
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FFMPEGCV is an alternative to OPENCV for video reading&writing.
2 | 
3 | [](https://pypi.org/project/ffmpegcv/)
4 | [](https://pypistats.org/packages/ffmpegcv)
5 | 
7 | 
8 |
9 | English Version | [中文版本](./README_CN.md)
10 |
11 | Here is the Python version of ffmpegcv. For the C++ version, please visit [FFMPEGCV-CPP](https://github.com/chenxinfeng4/ffmpegcv-cpp)
12 |
13 | The ffmpegcv provide Video Reader and Video Witer with ffmpeg backbone, which are faster and powerful than cv2. Integrating ffmpegcv into your deeplearning pipeline is very smooth.
14 |
15 | - The ffmpegcv is api **compatible** to open-cv.
16 | - The ffmpegcv can use **GPU accelerate** encoding and decoding*.
17 | - The ffmpegcv supports much more video **codecs** v.s. open-cv.
18 | - The ffmpegcv supports **RGB** & BGR & GRAY format as you like.
19 | - The ffmpegcv supports fp32 CHW & HWC format shortcut to CUDA memory.
20 | - The ffmpegcv supports **Stream reading** (IP Camera) in low latency.
21 | - The ffmpegcv supports ROI operations.You can **crop**, **resize** and **pad** the ROI.
22 |
23 | In all, ffmpegcv is just similar to opencv api. But it has more codecs and does't require opencv installed at all. It's great for deeplearning pipeline.
24 |
25 |
26 |
27 |
28 |
29 |
30 | ## Functions:
31 | - `VideoWriter`: Write a video file.
32 | - `VideoCapture`: Read a video file.
33 | - `VideoCaptureNV`: Read a video file by NVIDIA GPU.
34 | - `VideoCaptureQSV`: Read a video file by Intel QuickSync Video.
35 | - `VideoCaptureCAM`: Read a camera.
36 | - `VideoCaptureStream`: Read a RTP/RTSP/RTMP/HTTP stream.
37 | - `VideoCaptureStreamRT`: Read a RTSP stream (IP Camera) in real time low latency as possible.
38 | - `noblock`: Read/Write a video file in background using mulitprocssing.
39 | - `toCUDA`: Translate a video/stream as CHW/HWC-float32 format into CUDA device, >2x faster.
40 |
41 | ## Install
42 | You need to download ffmpeg before you can use ffmpegcv.
43 | ```
44 | #1A. LINUX: sudo apt install ffmpeg
45 | #1B. MAC (No NVIDIA GPU): brew install ffmpeg
46 | #1C. WINDOWS: download ffmpeg and add to the path
47 | #1D. CONDA: conda install ffmpeg=6.0.0 #don't use the default 4.x.x version
48 |
49 | #2A. python
50 | pip install ffmpegcv #stable verison
51 | pip install git+https://github.com/chenxinfeng4/ffmpegcv #latest verison
52 |
53 | #2B. recommand only when you want advanced functions. See the toCUDA section
54 | pip install ffmpegcv[cuda]
55 | ```
56 |
57 | ## When should choose `ffmpegcv` other than `opencv`:
58 | - The `opencv` is hard to install. The ffmpegcv only requires `numpy` and `FFmpeg`, works across Mac/Windows/Linux platforms.
59 | - The `opencv` packages too much image processing toolbox. You just want a simple video/camero IO with GPU accessible.
60 | - The `opencv` didn't support profiling `h264`/`h265` and other video writers.
61 | - You want to **crop**, **resize** and **pad** the video/camero ROI.
62 | - You are interested in deeplearning pipeline.
63 | ## Basic example
64 | Read a video by CPU, and rewrite it by GPU.
65 | ```python
66 | vidin = ffmpegcv.VideoCapture(vfile_in)
67 | vidout = ffmpegcv.VideoWriterNV(vfile_out, 'h264', vidin.fps) #NVIDIA-GPU
68 |
69 | with vidin, vidout:
70 | for frame in vidin:
71 | cv2.imshow('image', frame)
72 | vidout.write(frame)
73 | ```
74 |
75 | Read the camera.
76 | ```python
77 | # by device ID
78 | cap = ffmpegcv.VideoCaptureCAM(0)
79 | # by device name
80 | cap = ffmpegcv.VideoCaptureCAM("Integrated Camera")
81 | ```
82 |
83 | Deeplearning pipeline.
84 | ```python
85 | """
86 | —————————— NVIDIA GPU accelerating ⤴⤴ ———————
87 | | |
88 | V V
89 | video -> decode -> crop -> resize -> RGB -> CUDA:CHW float32 -> model
90 | """
91 | cap = ffmpegcv.toCUDA(
92 | ffmpegcv.VideoCaptureNV(file, pix_fmt='nv12', resize=(W,H)),
93 | tensor_format='chw')
94 |
95 | for frame_CHW_cuda in cap:
96 | frame_CHW_cuda = (frame_CHW_cuda - mean) / std
97 | result = model(frame_CHW_cuda)
98 | ```
99 | ## Cross platform
100 |
101 | The ffmpegcv is based on Python+FFmpeg, it can cross platform among `Windows, Linux, Mac, X86, Arm`systems.
102 |
103 | ## GPU Acceleration
104 |
105 | - Support **NVIDIA** card only, test in x86_64 only.
106 | - Works in **Windows**, **Linux** and **Anaconda**.
107 | - Works in the **Google Colab** notebook.
108 | - Infeasible in the **MacOS**. That ffmpeg didn't supports NVIDIA at all.
109 |
110 | > \* The ffmegcv GPU reader is a bit slower than CPU reader, but much faster when use ROI operations (crop, resize, pad).
111 |
112 | ## Codecs
113 |
114 | | Codecs | OpenCV-reader | ffmpegcv-cpu-r | gpu-r | OpenCV-writer | ffmpegcv-cpu-w | gpu-w |
115 | | ----------- | ------------- | ---------------- | ---- | ------------- | ---------------- | ---- |
116 | | h264 | √ | √ | √ | × | √ | √ |
117 | | h265 (hevc) | not sure | √ | √ | × | √ | √ |
118 | | mjpeg | √ | √ | × | √ | √ | × |
119 | | mpeg | √ | √ | × | √ | √ | × |
120 | | others | not sure | ffmpeg -decoders | × | not sure | ffmpeg -encoders | × |
121 |
122 | ## Benchmark
123 | *On the way...(maybe never)*
124 |
125 |
126 | ## Video Reader
127 | ---
128 | The ffmpegcv is just similar to opencv in api.
129 | ```python
130 | # open cv
131 | import cv2
132 | cap = cv2.VideoCapture(file)
133 | while True:
134 | ret, frame = cap.read()
135 | if not ret:
136 | break
137 | pass
138 |
139 | # ffmpegcv
140 | import ffmpegcv
141 | cap = ffmpegcv.VideoCapture(file)
142 | while True:
143 | ret, frame = cap.read()
144 | if not ret:
145 | break
146 | pass
147 | cap.release()
148 |
149 | # alternative
150 | cap = ffmpegcv.VideoCapture(file)
151 | nframe = len(cap)
152 | for frame in cap:
153 | pass
154 | cap.release()
155 |
156 | # more pythonic, recommand
157 | with ffmpegcv.VideoCapture(file) as cap:
158 | nframe = len(cap)
159 | for iframe, frame in enumerate(cap):
160 | if iframe>100: break
161 | pass
162 | ```
163 |
164 | Use GPU to accelerate decoding. It depends on the video codes.
165 | h264_nvcuvid, hevc_nvcuvid ....
166 | ```python
167 | cap_cpu = ffmpegcv.VideoCapture(file)
168 | cap_gpu0 = ffmpegcv.VideoCaptureNV(file) #NVIDIA GPU0
169 | cap_gpu1 = ffmpegcv.VideoCaptureNV(file, gpu=1) #NVIDIA GPU1
170 | cap_qsv = ffmpegcv.VideoCaptureQSV(file) #Intel QSV, experimental
171 | ```
172 |
173 | Use `rgb24` instead of `bgr24`. The `gray` version would be more efficient.
174 | ```python
175 | cap = ffmpegcv.VideoCapture(file, pix_fmt='rgb24') #rgb24, bgr24, gray
176 | ret, frame = cap.read()
177 | plt.imshow(frame)
178 | ```
179 |
180 | ### ROI Operations
181 | You can crop, resize and pad the video. These ROI operation is `ffmpegcv-GPU` > `ffmpegcv-CPU` >> `opencv`.
182 |
183 | **Crop** video, which will be much faster than read the whole canvas. The top-left corner is (0, 0).
184 | ```python
185 | cap = ffmpegcv.VideoCapture(file, crop_xywh=(0, 0, 640, 480))
186 | ```
187 |
188 | **Resize** the video to the given size.
189 | ```python
190 | cap = ffmpegcv.VideoCapture(file, resize=(640, 480))
191 | ```
192 |
193 | **Resize** and keep the aspect ratio with black border **padding**.
194 | ```python
195 | cap = ffmpegcv.VideoCapture(file, resize=(640, 480), resize_keepratio=True)
196 | ```
197 |
198 | **Crop** and then **resize** the video.
199 | ```python
200 | cap = ffmpegcv.VideoCapture(file, crop_xywh=(0, 0, 640, 480), resize=(512, 512))
201 | ```
202 |
203 | ## toCUDA device
204 | ---
205 | The ffmpegcv can translate the video/stream from HWC-uint8 cpu to CHW-float32 in CUDA device. It significantly reduce your cpu load, and get >2x faster than your manually convertion.
206 |
207 | Prepare your environment. The cuda environment is required. The `pycuda` package is required. The `pytorch` package is non-essential.
208 | > nvcc --version # check you've installed NVIDIA CUDA Compiler. Already installed if
209 | > you've installed Tensorflow-gpu or Pytorch-gpu
210 | >
211 | > pip install ffmpegcv[cuda] #auto install pycuda
212 |
213 | ```python
214 | # Read a video file to CUDA device, original
215 | cap = ffmpegcv.VideoCaptureNV(file, pix_fmt='rgb24')
216 | ret, frame_HWC_CPU = cap.read()
217 | frame_CHW_CUDA = torch.from_numpy(frame_HWC_CPU).permute(2, 0, 1).cuda().contiguous().float() # 120fps, 1200% CPU load
218 |
219 | # speed up
220 | cap = toCUDA(ffmpegcv.VideoCapture(file, pix_fmt='yuv420p')) #pix_fmt: 'yuv420p' or 'nv12' only
221 | cap = toCUDA(ffmpegcv.VideoCaptureNV(file, pix_fmt='nv12')) #'nv12' is better for gpu
222 | cap = toCUDA(vid, tensor_format='chw') #tensor format:'chw'(default) or 'hwc', fp32 precision
223 | cap = toCUDA(vid, gpu=1) #choose gpu
224 |
225 | # read to the cuda device
226 | ret, frame_CHW_pycuda = cap.read() #380fps, 200% CPU load, dtype is [pycuda array]
227 | ret, frame_CHW_pycudamem = cap.read_cudamem() #dtype is [pycuda mem_alloc]
228 | ret, frame_CHW_CUDA = cap.read_torch() #dtype is [pytorch tensor]
229 | ret, _ = cap.read_torch(frame_CHW_CUDA) #no copy, but need to specify the output memory
230 |
231 | frame_CHW_pycuda[:] = (frame_CHW_pycuda - mean) / std #normalize
232 | ```
233 |
234 | How can `toCUDA` make it faster in your deeplearning pipeline than `opencv` or `ffmpeg`?
235 | > 1. The opencv/ffmpeg uses the cpu to convert video pix_fmt from original YUV to RGB24, which is slow. The ffmpegcv use the cuda to accelerate pix_fmt convertion.
236 | > 2. Use `yuv420p` or `nv12` can save the cpu load and reduce the memory copy from CPU to GPU.
237 | > 3. The ffmpeg stores the image as HWC format. The ffmpegcv can use HWC & CHW format to accelerate the video reading.
238 |
239 | ## Video Writer
240 | ---
241 |
242 | ```python
243 | # cv2
244 | out = cv2.VideoWriter('outpy.avi',
245 | cv2.VideoWriter_fourcc('M','J','P','G'),
246 | 10,
247 | (w, h))
248 | out.write(frame1)
249 | out.write(frame2)
250 | out.release()
251 |
252 | # ffmpegcv, default codec is 'h264' in cpu 'h265' in gpu.
253 | # frameSize is decided by the size of the first frame.
254 | # use the 'mp4/mkv' instead of 'avi' to avoid the codec outdated.
255 | out = ffmpegcv.VideoWriter('outpy.mp4', None, 10)
256 | out.write(frame1)
257 | out.write(frame2)
258 | out.release()
259 |
260 | # more pythonic
261 | with ffmpegcv.VideoWriter('outpy.mp4', None, 10) as out:
262 | out.write(frame1)
263 | out.write(frame2)
264 | ```
265 |
266 |
267 | Use GPU to accelerate encoding. Such as h264_nvenc, hevc_nvenc.
268 | ```python
269 | out_cpu = ffmpegcv.VideoWriter('outpy.mp4', None, 10)
270 | out_gpu0 = ffmpegcv.VideoWriterNV('outpy.mp4', 'h264', 10) #NVIDIA GPU0
271 | out_gpu1 = ffmpegcv.VideoWriterNV('outpy.mp4', 'hevc', 10, gpu=1) #NVIDIA GPU1
272 | out_qsv = ffmpegcv.VideoWriterQSV('outpy.mp4', 'h264', 10) #Intel QSV, experimental
273 |
274 | ```
275 |
276 | Input image is rgb24 instead of bgr24
277 | ```python
278 | out = ffmpegcv.VideoWriter('outpy.mp4', None, 10, pix_fmt='rgb24')
279 | ```
280 |
281 | Resize the video
282 | ```python
283 | out_resz = ffmpegcv.VideoWriter('outpy.mp4', None, 10, resize=(640, 480)) #Resize
284 | ```
285 | ## Video Reader and Writer
286 | ---
287 | ```python
288 | import ffmpegcv
289 | vfile_in = 'A.mp4'
290 | vfile_out = 'A_h264.mp4'
291 | vidin = ffmpegcv.VideoCapture(vfile_in)
292 | vidout = ffmpegcv.VideoWriter(vfile_out, None, vidin.fps)
293 |
294 | with vidin, vidout:
295 | for frame in vidin:
296 | vidout.write(frame)
297 | ```
298 |
299 | ## Camera Reader
300 | ---
301 | **Experimental feature**. The ffmpegcv offers Camera reader. Which is consistent with VideoFiler reader.
302 |
303 | - The `VideoCaptureCAM` aims to support ROI operations. The Opencv will be general fascinating than ffmpegcv in camera read. **I recommand the opencv in most camera reading case**.
304 | - The ffmpegcv can use name to retrieve the camera device. Use `ffmpegcv.VideoCaptureCAM("Integrated Camera")` is readable than `cv2.VideoCaptureCAM(0)`.
305 | - The `VideoCaptureCAM` will be laggy and dropping frames if your post-process takes long time. The VideoCaptureCAM will buffer the recent frames.
306 | - The `VideoCaptureCAM` is continously working on background even if you didn't read it. **Please release it in time**.
307 | - Works perfect in Windows, not-perfect in Linux and macOS.
308 |
309 | ```python
310 | import cv2
311 | cap = cv2.VideoCapture(0)
312 | while True:
313 | ret, frame = cap.read()
314 | cv2.imshow('frame', frame)
315 | if cv2.waitKey(1) & 0xFF == ord('q'):
316 | break
317 | cap.release()
318 |
319 | # ffmpegcv, in Windows&Linux
320 | import ffmpegcv
321 | cap = ffmpegcv.VideoCaptureCAM(0)
322 | while True:
323 | ret, frame = cap.read()
324 | cv2.imshow('frame', frame)
325 | if cv2.waitKey(1) & 0xFF == ord('q'):
326 | break
327 | cap.release()
328 |
329 | # ffmpegcv use by camera name, in Windows&Linux
330 | cap = ffmpegcv.VideoCaptureCAM("Integrated Camera")
331 |
332 | # ffmpegcv use camera path if multiple cameras conflict
333 | cap = ffmpegcv.VideoCaptureCAM('@device_pnp_\\\\?\\usb#vid_2304&'
334 | 'pid_oot#media#0001#{65e8773d-8f56-11d0-a3b9-00a0c9223196}'
335 | '\\global')
336 |
337 | # ffmpegcv use camera with ROI operations
338 | cap = ffmpegcv.VideoCaptureCAM("Integrated Camera", crop_xywh=(0, 0, 640, 480), resize=(512, 512), resize_keepratio=True)
339 |
340 |
341 | ```
342 |
343 | **List all camera devices**
344 | ```python
345 | from ffmpegcv.ffmpeg_reader_camera import query_camera_devices
346 |
347 | devices = query_camera_devices()
348 | print(devices)
349 | ```
350 | >{0: ('Integrated Camera', '@device_pnp_\\\\?\\usb#vid_2304&pid_oot#media#0001#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\\global'),
351 | 1: ('OBS Virtual Camera', '@device_sw_{860BB310-5D01-11D0-BD3B-00A0C911CE86}\\{A3FCE0F5-3493-419F-958A-ABA1250EC20B}')}
352 |
353 |
354 | **Set the camera resolution, fps, vcodec/pixel-format**
355 |
356 | ```python
357 | from ffmpegcv.ffmpeg_reader_camera import query_camera_options
358 |
359 | options = query_camera_options(0) # or query_camera_options("Integrated Camera")
360 | print(options)
361 | cap = ffmpegcv.VideoCaptureCAM(0, **options[-1])
362 | ```
363 | >[{'camcodec': 'mjpeg', 'campix_fmt': None, 'camsize_wh': (1280, 720), 'camfps': 60.0002}, {'camcodec': 'mjpeg', 'campix_fmt': None, 'camsize_wh': (640, 480), 'camfps': 60.0002}, {'camcodec': 'mjpeg', 'campix_fmt': None, 'camsize_wh': (1920, 1080), 'camfps': 60.0002}, {'camcodec': None, 'campix_fmt': 'yuyv422', 'camsize_wh': (1280, 720), 'camfps': 10}, {'camcodec': None, 'campix_fmt': 'yuyv422', 'camsize_wh': (640, 480), 'camfps': 30}, {'camcodec': None, 'campix_fmt': 'yuyv422', 'camsize_wh': (1920, 1080), 'camfps': 5}]
364 |
365 | **Known issues**
366 | 1. The VideoCaptureCAM didn't give a smooth experience in macOS. You must specify all the camera parameters. And the query_camera_options woun't give any suggestion. That's because the `ffmpeg` cannot list device options using mac native `avfoundation`.
367 | ```python
368 | # The macOS requires full argument.
369 | cap = ffmpegcv.VideoCaptureCAM('FaceTime HD Camera', camsize_wh=(1280,720), camfps=30, campix_fmt='nv12')
370 | ```
371 |
372 | 2. The VideoCaptureCAM cann't list the FPS in linux. Because the `ffmpeg` cound't query the device's FPS using linux native `v4l2` module. However, it's just OK to let the FPS empty.
373 |
374 |
375 | ## Stream Reader (Live streaming, RTSP IP cameras)
376 | **Experimental feature**. The ffmpegcv offers Stream reader. Which is consistent with VideoFiler reader, and more similiar to the camera.
377 | Becareful when using it.
378 |
379 | - Support `RTSP`, `RTP`, `RTMP`, `HTTP`, `HTTPS` streams.
380 | - The `VideoCaptureStream` will be laggy and dropping frames if your post-process takes long time. The VideoCaptureCAM will buffer the recent frames.
381 | - The `VideoCaptureStreamRT` is continously working on background even if you didn't read it. **Please release it in time**.
382 | - **Low latency** RTSP IP camera reader. Batter than opencv.
383 | - *It's still experimental*. Recommand you to use opencv.
384 |
385 |
386 | ```python
387 | # opencv
388 | import cv2
389 | stream_url = 'http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8'
390 | cap = cv2.VideoCapture(stream_url, cv2.CAP_FFMPEG)
391 |
392 | if not cap.isOpened():
393 | print('Cannot open the stream')
394 | exit(-1)
395 |
396 | while True:
397 | ret, frame = cap.read()
398 | if not ret:
399 | break
400 | pass
401 |
402 | # ffmpegcv
403 | import ffmpegcv
404 | cap = ffmpegcv.VideoCaptureStream(stream_url)
405 | while True:
406 | ret, frame = cap.read()
407 | if not ret:
408 | break
409 | pass
410 |
411 | # ffmpegcv, IP Camera Low-latency
412 | # e.g. HIK Vision IP Camera, `101` Main camera stream, `102` the second
413 | stream_url = 'rtsp://admin:PASSWD@192.168.1.xxx:8554/Streaming/Channels/102'
414 | cap = ffmpegcv.VideoCaptureStreamRT(stream_url) # Low latency & recent buffered
415 | cap = ffmpegcv.ReadLiveLast(ffmpegcv.VideoCaptureStreamRT, stream_url) #no buffer
416 | while True:
417 | ret, frame = cap.read()
418 | if not ret:
419 | break
420 | pass
421 | ```
422 |
423 | ## Noblock
424 | A proxy to automatic prepare frames in backgroud, which does not block when reading&writing current frame (multiprocessing). This make your python program more efficient in CPU usage. Up to 2x boost.
425 |
426 | > ffmpegcv.VideoCapture(*args) -> ffmpegcv.noblock(ffmpegcv.VideoCapture, *args)
427 | >
428 | > ffmpegcv.VideoWriter(*args) -> ffmpegcv.noblock(ffmpegcv.VideoWriter, *args)
429 | ```python
430 | #Proxy any VideoCapture&VideoWriter args and kargs
431 | vid_noblock = ffmpegcv.noblock(ffmpegcv.VideoCapture, vfile, pix_fmt='rbg24')
432 |
433 | # this is fast
434 | def cpu_tense(): time.sleep(0.01)
435 | for _ in tqdm.trange(1000):
436 | ret, img = vid_noblock.read() #current img is already buffered, take no time
437 | cpu_tense() #meanwhile, the next img is buffering in background
438 |
439 | # this is slow
440 | vid = ffmpegcv.VideoCapture(vfile, pix_fmt='rbg24')
441 | for _ in tqdm.trange(1000):
442 | ret, img = vid.read() #this read will block cpu, take time
443 | cpu_tense()
444 | ```
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | # FFMPEGCV 读写视频,替代 OPENCV.
2 | 
3 | [](https://pypi.org/project/ffmpegcv/)
4 | [](https://pypistats.org/packages/ffmpegcv)
5 | 
7 | 
8 |
9 | [English Version](./README.md) | 中文版本
10 |
11 | ffmpegcv提供了基于ffmpeg的视频读取器和视频编写器,比cv2更快和更强大。适合深度学习的视频处理。
12 |
13 | - ffmpegcv与open-cv具有**兼容**的API。
14 | - ffmpegcv可以使用**GPU加速**编码和解码。
15 | - ffmpegcv支持比open-cv更多的**视频编码器**。
16 | - ffmpegcv原生支持**RGB**/BGR/灰度像素格式。
17 | - ffmpegcv支持网络**流视频读取** (网线监控相机)。
18 | - ffmpegcv支持ROI(感兴趣区域)操作,可以对ROI进行**裁剪**、**调整大小**和**填充**。
19 | 总之,ffmpegcv与opencv的API非常相似。但它具有更多的编码器,并且不需要安装opencv。
20 | - ffmpegcv支持导出图像帧到CUDA设备。
21 |
22 |
23 |
24 |
25 |
26 | ## 功能:
27 | - `VideoWriter`:写入视频文件。
28 | - `VideoCapture`:读取视频文件。
29 | - `VideoCaptureNV`:使用NVIDIA GPU读取视频文件。
30 | - `VideoCaptureQSV`: 使用Intel集成显卡读取视频文件.
31 | - `VideoCaptureCAM`:读取摄像头。
32 | - `VideoCaptureStream`:读取RTP/RTSP/RTMP/HTTP流。
33 | - `VideoCaptureStreamRT`: 读取RTSP流 (网线监控相机),实时、低延迟。
34 | - `noblock`:在后台读取视频文件(更快),使用多进程。
35 | - `toCUDA`:将图像帧导出到CUDA设备,以 CHW/HWC-float32 格式存储,超过2倍性能提升。
36 |
37 | ## 安装
38 | 在使用ffmpegcv之前,您需要下载`ffmpeg`。
39 | ```
40 | #1A. LINUX: sudo apt install ffmpeg
41 | #1B. MAC: brew install ffmpeg
42 | #1C. WINDOWS: 下载ffmpeg并添加至环境变量的路径中
43 | #1D. CONDA: conda install ffmpeg=6.0.0
44 |
45 | #2. python
46 | pip install ffmpegcv #stable verison
47 | pip install git+https://github.com/chenxinfeng4/ffmpegcv #latest verison
48 | ```
49 |
50 | ## 何时选择 `ffmpegcv` 而不是 `opencv`:
51 | - 安装`opencv`比较困难。ffmpegcv仅需要`numpy`和`FFmpeg`,可以在Mac/Windows/Linux平台上工作。
52 | - `opencv`包含太多的图像处理工具箱,而您只是想使用带GPU支持的简单视频/摄像头输入输出操作。
53 | - `opencv`不支持`h264`/`h265`和其他视频编码器。
54 | - 您想对视频/摄像头的感兴趣区域(ROI)进行**裁剪**、**调整大小**和**填充**操作。
55 |
56 |
57 | ## 基本示例
58 | 通过CPU读取视频,并通过GPU重写视频。
59 | ```python
60 | vidin = ffmpegcv.VideoCapture(vfile_in)
61 | vidout = ffmpegcv.VideoWriterNV(vfile_out, 'h264', vidin.fps) #NVIDIA 显卡
62 |
63 | with vidin, vidout:
64 | for frame in vidin:
65 | cv2.imshow('image', frame)
66 | vidout.write(frame)
67 | ```
68 |
69 | 读取摄像头。
70 | ```python
71 | # 通过设备ID
72 | cap = ffmpegcv.VideoCaptureCAM(0)
73 | # 通过设备名称
74 | cap = ffmpegcv.VideoCaptureCAM("Integrated Camera")
75 | ```
76 |
77 | 深度学习流水线
78 | ```python
79 | """
80 | —————————— NVIDIA GPU 加速 ⤴⤴ ———————
81 | | |
82 | V V
83 | 视频 -> 解码器 -> 裁剪 -> 缩放 -> RGB -> CUDA:CHW float32 -> 模型
84 | """
85 | cap = ffmpegcv.toCUDA(
86 | ffmpegcv.VideoCaptureNV(file, pix_fmt='nv12', resize=(W,H)),
87 | tensor_format='chw')
88 |
89 | for frame_CHW_cuda in cap:
90 | frame_CHW_cuda = (frame_CHW_cuda - mean) / std
91 | result = model(frame_CHW_cuda)
92 | ```
93 |
94 | ## GPU加速
95 | - 仅支持NVIDIA显卡,在 x86_64 上测试。
96 | - 原生支持**Windows**, **Linux**, **Anaconda**。
97 | - 在**Google Colab**上顺利运行。
98 | - 在**MacOS**仅能使用CPU功能,上无法进行GPU加速,因为Mac根本就不支持NVIDIA。
99 |
100 | > 在CPU数量充足的条件下,GPU读取速度可能比CPU读取速度稍慢。在使用感兴趣区域(ROI)操作(裁剪、调整大小、填充)时,GPU优势更凸显。
101 |
102 | ## 编解码器
103 |
104 | | 编解码器 | OpenCV读取器 | ffmpegcv-CPU读取器 | GPU读取器 | OpenCV写入器 | ffmpegcv-CPU写入器 | GPU写入器 |
105 | | ----------- | ------------- | ---------------- | ---- | ------------- | ---------------- | ---- |
106 | | h264 | √ | √ | √ | × | √ | √ |
107 | | h265 (hevc) | 不确定 | √ | √ | × | √ | √ |
108 | | mjpeg | √ | √ | × | √ | √ | × |
109 | | mpeg | √ | √ | × | √ | √ | × |
110 | | 其他 | 不确定 | ffmpeg -decoders | × | 不确定 | ffmpeg -encoders | × |
111 |
112 | ## 基准测试
113 | *正在进行中...(遥遥无期)*
114 |
115 |
116 | ## 视频读取器
117 | ---
118 | ffmpegcv与opencv在API上非常类似。
119 | ```python
120 | # OpenCV
121 | import cv2
122 | cap = cv2.VideoCapture(file)
123 | while True:
124 | ret, frame = cap.read()
125 | if not ret:
126 | break
127 | pass
128 |
129 | # ffmpegcv
130 | import ffmpegcv
131 | cap = ffmpegcv.VideoCapture(file)
132 | while True:
133 | ret, frame = cap.read()
134 | if not ret:
135 | break
136 | pass
137 | cap.release()
138 |
139 | # 另一种写法
140 | cap = ffmpegcv.VideoCapture(file)
141 | nframe = len(cap)
142 | for frame in cap:
143 | pass
144 | cap.release()
145 |
146 | # 更加Pythonic的写法,推荐使用
147 | with ffmpegcv.VideoCapture(file) as cap:
148 | nframe = len(cap)
149 | for iframe, frame in enumerate(cap):
150 | if iframe>100: break
151 | pass
152 | ```
153 |
154 | 使用GPU加速解码。具体取决于视频编码格式。
155 | h264_nvcuvid, hevc_nvcuvid ....
156 | ```python
157 | cap_cpu = ffmpegcv.VideoCapture(file)
158 | cap_gpu = ffmpegcv.VideoCapture(file, codec='h264_cuvid') # NVIDIA GPU0
159 | cap_gpu0 = ffmpegcv.VideoCaptureNV(file) # NVIDIA GPU0
160 | cap_gpu1 = ffmpegcv.VideoCaptureNV(file, gpu=1) # NVIDIA GPU1
161 | cap_qsv = ffmpegcv.VideoCaptureQSV(file) #Intel QSV, 测试中
162 | ```
163 |
164 | 使用`rgb24`代替`bgr24`。`gray`版本会更高效。
165 | ```python
166 | cap = ffmpegcv.VideoCapture(file, pix_fmt='rgb24') # rgb24, bgr24, gray
167 | ret, frame = cap.read()
168 | plt.imshow(frame)
169 | ```
170 |
171 | ### 感兴趣区域(ROI)操作
172 | 您可以对视频进行裁剪、调整大小和填充。这些ROI操作中,`ffmpegcv-GPU` > `ffmpegcv-CPU` >> `opencv` 在性能上。
173 |
174 | **裁剪**视频,比读取整个画布要快得多。
175 | ```python
176 | cap = ffmpegcv.VideoCapture(file, crop_xywh=(0, 0, 640, 480))
177 | ```
178 |
179 | 将视频调整为给定大小的**大小**。
180 | ```python
181 | cap = ffmpegcv.VideoCapture(file, resize=(640, 480))
182 | ```
183 |
184 | **调整大小**并保持宽高比,使用黑色边框进行**填充**。
185 | ```python
186 | cap = ffmpegcv.VideoCapture(file, resize=(640, 480), resize_keepratio=True)
187 | ```
188 |
189 | 对视频进行**裁剪**,然后进行**调整大小**。左上角为坐标 (0,0)。
190 | ```python
191 | cap = ffmpegcv.VideoCapture(file, crop_xywh=(0, 0, 640, 480), resize=(512, 512))
192 | ```
193 |
194 | ## toCUDA 将图像帧快速导出到CUDA设备
195 | ---
196 | ffmpegcv 可以将 HWC-uint8 cpu 中的视频/流转换为 CUDA 设备中的 CHW-float32。它可以显著减少你的 CPU 负载,并比你的手动转换快 2 倍以上。
197 |
198 | 准备环境。你需要具备 cuda 环境,并且安装 pycuda 包。注意,pytorch 包是非必须的。
199 | > nvcc --version # 检查你是否已经安装了 NVIDIA CUDA 编译器
200 | > pip install pycuda # 安装 pycuda
201 |
202 | ```python
203 | # 读取视频到CUDA设备,加速前
204 | cap = ffmpegcv.VideoCaptureNV(file, pix_fmt='rgb24')
205 | ret, frame_HWC_CPU = cap.read()
206 | frame_CHW_CUDA = torch.from_numpy(frame_HWC_CPU).permute(2, 0, 1).cuda().contiguous().float() # 120fps, 1200% CPU 使用率
207 |
208 | # 加速后
209 | cap = toCUDA(ffmpegcv.VideoCapture(file, pix_fmt='yuv420p')) #必须设置, yuv420p 针对 cpu
210 | cap = toCUDA(ffmpegcv.VideoCaptureNV(file, pix_fmt='nv12')) #必须设置, nv12 针对 gpu
211 | cap = toCUDA(vid, tensor_format='chw') #tensor 格式:'chw'(默认) or 'hwc'
212 | cap = toCUDA(vid, gpu=1) #选择 gpu
213 |
214 | ret, frame_CHW_pycuda = cap.read() #380fps, 200% CPU load, [pycuda array]
215 | ret, frame_CHW_pycudamem = cap.read_cudamem() #same as [pycuda mem_alloc]
216 | ret, frame_CHW_CUDA = cap.read_torch() #same as [pytorch tensor]
217 | ret, _ = cap.read_torch(frame_CHW_CUDA) #不拷贝, 但需要提前分配内存
218 |
219 | frame_CHW_pycuda[:] = (frame_CHW_pycuda - mean) / std #归一化
220 | ```
221 |
222 | 为什么在深度学习流水线中使用 toCUDA 会更快?
223 |
224 | > 1. ffmpeg 使用 CPU 将视频像素格式从原始 YUV 转换为 RGB24,这个过程很慢。`toCUDA` 使用 cuda 加速像素格式转换。
225 | > 2. 使用 yuv420p 或 nv12 可以节省 CPU 负载并减少从 CPU 到 GPU 的内存复制。
226 | > 3. ffmpeg 将图像存储为 HWC 格式。ffmpegcv 可以使用 HWC 和 CHW 格式来加速视频存储。
227 |
228 | ## 视频写入器
229 | ---
230 | ```python
231 | # cv2
232 | out = cv2.VideoWriter('outpy.avi',
233 | cv2.VideoWriter_fourcc('M','J','P','G'),
234 | 10,
235 | (w, h))
236 | out.write(frame1)
237 | out.write(frame2)
238 | out.release()
239 |
240 | # ffmpegcv,默认的编码器为'h264'在CPU上,'h265'在GPU上。
241 | # 帧大小由第一帧决定
242 | # 使用 'mp4/mkv' 来替代古老的 'avi' 格式
243 | out = ffmpegcv.VideoWriter('outpy.mp4', None, 10)
244 | out.write(frame1)
245 | out.write(frame2)
246 | out.release()
247 |
248 | # 更加Pythonic的写法
249 | with ffmpegcv.VideoWriter('outpy.mp4', None, 10) as out:
250 | out.write(frame1)
251 | out.write(frame2)
252 | ```
253 |
254 | 使用GPU加速编码。例如h264_nvenc,hevc_nvenc。
255 | ```python
256 | out_cpu = ffmpegcv.VideoWriter('outpy.mp4', None, 10)
257 | out_gpu0 = ffmpegcv.VideoWriterNV('outpy.mp4', 'h264', 10) # NVIDIA GPU0
258 | out_gpu1 = ffmpegcv.VideoWriterNV('outpy.mp4', 'hevc', 10, gpu=1) # NVIDIA GPU1
259 | out_qsv = ffmpegcv.VideoWriterQSV('outpy.mp4', 'h264', 10) #Intel QSV, 测试中
260 | ```
261 |
262 | 输入图像使用rgb24而不是bgr24。
263 | ```python
264 | out = ffmpegcv.VideoWriter('outpy.mp4', None, 10, pix_fmt='rgb24')
265 | ```
266 |
267 | 缩放图像尺寸
268 | ```python
269 | out_resz = ffmpegcv.VideoWriter('outpy.mp4', None, 10, resize=(640, 480))
270 | ```
271 |
272 | ## 视频读取器和写入器
273 | ---
274 | ```python
275 | import ffmpegcv
276 | vfile_in = 'A.mp4'
277 | vfile_out = 'A_h264.mp4'
278 | vidin = ffmpegcv.VideoCapture(vfile_in)
279 | vidout = ffmpegcv.VideoWriter(vfile_out, None, vidin.fps)
280 |
281 | with vidin, vidout:
282 | for frame in vidin:
283 | vidout.write(frame)
284 | ```
285 |
286 | ## 相机读取器
287 | ---
288 | **实验性功能**。ffmpegcv提供了相机读取器。与VideoCapture读取器一致。
289 |
290 | - VideoCaptureCAM旨在支持感兴趣区域(ROI)操作。在相机读取方面,Opencv比ffmpegcv更具吸引力。**对于大多数相机读取情况,我推荐使用Opencv**。
291 | - ffmpegcv可以使用名称检索相机设备,使用`ffmpegcv.VideoCaptureCAM("Integrated Camera")`比使用`cv2.VideoCaptureCAM(0)`更易读。
292 | - 如果后处理时间过长,VideoCaptureCAM将会出现卡顿和丢帧。VideoCaptureCAM会缓冲最近的帧。
293 | - 即使没有读取视频帧,VideoCaptureCAM也会在后台不断工作。**请及时释放资源**。
294 | - 在Windows上表现良好,在Linux和macOS上表现不完美。
295 |
296 | ```python
297 | import cv2
298 | cap = cv2.VideoCapture(0)
299 | while True:
300 | ret, frame = cap.read()
301 | cv2.imshow('frame', frame)
302 | if cv2.waitKey(1) & 0xFF == ord('q'):
303 | break
304 | cap.release()
305 |
306 | # ffmpegcv,在Windows和Linux上
307 | import ffmpegcv
308 | cap = ffmpegcv.VideoCaptureCAM(0)
309 | while True:
310 | ret, frame = cap.read()
311 | cv2.imshow('frame', frame)
312 | if cv2.waitKey(1) & 0xFF == ord('q'):
313 | break
314 | cap.release()
315 |
316 | # ffmpegcv 使用相机名称,在Windows和Linux上
317 | cap = ffmpegcv.VideoCaptureCAM("Integrated Camera")
318 |
319 | # ffmpegcv 使用相机路径(避免多个相机冲突)
320 | cap = ffmpegcv.VideoCaptureCAM('@device_pnp_\\\\?\\usb#vid_2304&'
321 | 'pid_oot#media#0001#{65e8773d-8f56-11d0-a3b9-00a0c9223196}'
322 | '\\global')
323 |
324 | # ffmpegcv 使用具有ROI操作的相机
325 | cap = ffmpegcv.VideoCaptureCAM("Integrated Camera", crop_xywh=(0, 0, 640, 480), resize=(512, 512), resize_keepratio=True)
326 |
327 |
328 | ```
329 |
330 | **列出所有相机设备**
331 | ```python
332 | from ffmpegcv.ffmpeg_reader_camera import query_camera_devices
333 |
334 | devices = query_camera_devices()
335 | print(devices)
336 | ```
337 | >{0: ('Integrated Camera', '@device_pnp_\\\\?\\usb#vid_2304&pid_oot#media#0001#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\\global'),
338 | 1: ('OBS Virtual Camera', '@device_sw_{860BB310-5D01-11D0-BD3B-00A0C911CE86}\\{A3FCE0F5-3493-419F-958A-ABA1250EC20B}')}
339 |
340 |
341 | **设置相机的分辨率、帧率、视频编码/像素格式**
342 |
343 | ```python
344 | from ffmpegcv.ffmpeg_reader_camera import query_camera_options
345 |
346 | options = query_camera_options(0) # 或者 query_camera_options("Integrated Camera")
347 | print(options)
348 | cap = ffmpegcv.VideoCaptureCAM(0, **options[-1])
349 | ```
350 | >[{'camcodec': 'mjpeg', 'campix_fmt': None, 'camsize_wh': (1280, 720), 'camfps': 60.0002}, {'camcodec': 'mjpeg', 'campix_fmt': None, 'camsize_wh': (640, 480), 'camfps': 60.0002}, {'camcodec': 'mjpeg', 'campix_fmt': None, 'camsize_wh': (1920, 1080), 'camfps': 60.0002}, {'camcodec': None, 'campix_fmt': 'yuyv422', 'camsize_wh': (1280, 720), 'camfps': 10}, {'camcodec': None, 'campix_fmt': 'yuyv422', 'camsize_wh': (640, 480), 'camfps': 30}, {'camcodec': None, 'campix_fmt': 'yuyv422', 'camsize_wh': (1920, 1080), 'camfps': 5}]
351 |
352 | **已知问题**
353 | 1. VideoCaptureCAM在macOS上的体验不太流畅。你必须指定所有相机参数。而且query_camera_options不会给出任何建议。这是因为`ffmpeg`无法使用mac本机的`avfoundation`列出设备选项。
354 | ```python
355 | # macOS需要提供完整参数。
356 | cap = ffmpegcv.VideoCaptureCAM('FaceTime HD Camera', camsize_wh=(1280,720), camfps=30, campix_fmt='nv12')
357 | ```
358 |
359 | 2. 在Linux上VideoCaptureCAM无法列出FPS,因为`ffmpeg`无法使用Linux本机的`v4l2`模块查询设备的FPS。不过,让FPS为空也没问题。
360 |
361 | ## 流读取器 (直播流,网络监控摄像头)
362 | **实验性功能**。ffmpegcv提供了流读取器,与VideoFile读取器一致,更类似于相机。
363 |
364 | - 支持`RTSP`、`RTP`、`RTMP`、`HTTP`、`HTTPS`流。
365 | - 如果后处理时间过长,VideoCaptureStream会出现卡顿和丢帧。VideoCaptureCAM会缓冲最近的帧。
366 | - 即使没有读取视频帧,VideoCaptureStream也会在后台不断工作。**请及时释放资源**。
367 | - 这仍然是实验性功能。建议您使用opencv。
368 |
369 | ```python
370 | # opencv
371 | import cv2
372 | stream_url = 'http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8'
373 | cap = cv2.VideoCapture(stream_url, cv2.CAP_FFMPEG)
374 |
375 | if not cap.isOpened():
376 | print('无法打开流')
377 | exit(-1)
378 |
379 | while True:
380 | ret, frame = cap.read()
381 | if not ret:
382 | break
383 | pass
384 |
385 | # ffmpegcv
386 | import ffmpegcv
387 | cap = ffmpegcv.VideoCaptureStream(stream_url)
388 | while True:
389 | ret, frame = cap.read()
390 | if not ret:
391 | break
392 | pass
393 |
394 | # ffmpegcv, 网络监控摄像头
395 | # 例如 海康威视, `101` 主视频流, `102` 子视频流
396 | stream_url = 'rtsp://admin:PASSWD@192.168.1.xxx:8554/Streaming/Channels/102'
397 | cap = ffmpegcv.VideoCaptureStreamRT(stream_url) # 低延迟 & 缓存
398 | cap = ffmpegcv.ReadLiveLast(ffmpegcv.VideoCaptureStreamRT, stream_url) #不缓存
399 | while True:
400 | ret, frame = cap.read()
401 | if not ret:
402 | break
403 | pass
404 | ```
405 |
406 | ## FFmpegReaderNoblock
407 | 更快的读写取视频。利用多进程在后台自动准备帧,这样在读写当前帧时不会阻塞。这使得您的Python程序在CPU使用方面更高效。带来最大翻倍效率提升。
408 |
409 | > ffmpegcv.VideoCapture(*args) -> ffmpegcv.noblock(ffmpegcv.VideoCapture, *args)
410 | >
411 | > ffmpegcv.VideoWriter(*args) -> ffmpegcv.noblock(ffmpegcv.VideoWriter, *args)
412 |
413 | ```python
414 | # 代理任何 VideoCapture&VideoWriter 的参数和kargs
415 | vid_noblock = ffmpegcv.noblock(ffmpegcv.VideoCapture, vfile, pix_fmt='rbg24')
416 |
417 | # 这很快
418 | def cpu_tense(): time.sleep(0.01)
419 | for _ in tqdm.trange(1000):
420 | ret, img = vid_noblock.read() #当前图像已经被缓冲,不会占用时间
421 | cpu_tense() #同时,下一帧在后台缓冲
422 |
423 | # 这很慢
424 | vid = ffmpegcv.VideoCapture(vfile, pix_fmt='rbg24')
425 | for _ in tqdm.trange(2000):
426 | ret, img = vid.read() #此读取将阻塞CPU,占用时间
427 | cpu_tense()
428 | ```
429 |
--------------------------------------------------------------------------------
/ffmpegcv/__init__.py:
--------------------------------------------------------------------------------
1 | from .ffmpeg_reader import FFmpegReader, FFmpegReaderNV
2 | from .ffmpeg_writer import FFmpegWriter, FFmpegWriterNV
3 | from .ffmpeg_reader_camera import FFmpegReaderCAM
4 | from .ffmpeg_reader_stream import FFmpegReaderStream
5 | from .ffmpeg_reader_stream_realtime import FFmpegReaderStreamRT, FFmpegReaderStreamRTNV
6 | from .ffmpeg_writer_stream_realtime import FFmpegWriterStreamRT
7 | from .ffmpeg_reader_qsv import FFmpegReaderQSV
8 | from .ffmpeg_writer_qsv import FFmpegWriterQSV
9 | from .ffmpeg_reader_pannels import FFmpegReaderPannels
10 | from .ffmpeg_noblock import noblock, ReadLiveLast
11 | from .video_info import get_num_NVIDIA_GPUs
12 | import shutil
13 | from subprocess import DEVNULL, check_output
14 |
15 | from .version import __version__
16 |
17 |
18 | def _check():
19 | if not shutil.which("ffmpeg") or not shutil.which("ffprobe"):
20 | raise RuntimeError(
21 | "The ffmpeg is not installed. \n\n"
22 | "Please install ffmpeg via:\n "
23 | "conda install ffmpeg"
24 | )
25 |
26 |
27 | _check()
28 |
29 | _check_nvidia_init = None
30 |
31 |
32 | def _check_nvidia():
33 | global _check_nvidia_init
34 | run = lambda x: check_output(x, shell=True, stderr=DEVNULL)
35 | if _check_nvidia_init is None:
36 | calling_output = run("ffmpeg -h encoder=hevc_nvenc")
37 | if "AVOptions" not in calling_output.decode("utf-8"):
38 | raise RuntimeError(
39 | "The ffmpeg is not compiled with NVENC support.\n\n"
40 | "Please re-compile ffmpeg following the instructions at:\n "
41 | "https://docs.nvidia.com/video-technologies/video-codec-sdk/ffmpeg-with-nvidia-gpu/"
42 | )
43 |
44 | calling_output = run("ffmpeg -h decoder=hevc_cuvid")
45 | if "AVOptions" not in calling_output.decode("utf-8"):
46 | raise RuntimeError(
47 | "The ffmpeg is not compiled with NVENC support.\n\n"
48 | "Please re-compile ffmpeg following the instructions at:\n "
49 | "https://docs.nvidia.com/video-technologies/video-codec-sdk/ffmpeg-with-nvidia-gpu/"
50 | )
51 |
52 | if get_num_NVIDIA_GPUs() == 0:
53 | raise RuntimeError(
54 | "No NVIDIA GPU found.\n\n"
55 | "Please use a NVIDIA GPU card listed at:\n "
56 | "https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new"
57 | )
58 |
59 | _check_nvidia_init = True
60 |
61 | return True
62 |
63 |
64 | def VideoCapture(
65 | file,
66 | codec=None,
67 | pix_fmt="bgr24",
68 | crop_xywh=None,
69 | resize=None,
70 | resize_keepratio=True,
71 | resize_keepratioalign="center",
72 | ) -> FFmpegReader:
73 | """
74 | Alternative to cv2.VideoCapture
75 |
76 | Parameters
77 | ----------
78 | file : str
79 | Path to video file.
80 | codec : str
81 | Codec to use. Optional. Default is `None`.
82 | pix_fmt : str
83 | Pixel format. ['bgr24' | 'rgb24']. Optional. Default is 'bgr24'.
84 | crop_xywh : tuple
85 | Crop the frame. (x, y, width, height). Optional. Default is `None`.
86 | resize : tuple
87 | Resize the video to the given size. Optional. Default is `None`.
88 | resize_keepratio : bool
89 | Keep the aspect ratio and the border is black. Optional. Default is `True`.
90 | resize_keepratioalign : str
91 | Align the image to the `center`, `topleft`, `topright`, `bottomleft` or
92 | `bottomright`. Optional. Default is 'center'.
93 |
94 | Examples
95 | --------
96 | opencv
97 | ```
98 | cap = cv2.VideoCapture(file)
99 | while True:
100 | ret, frame = cap.read()
101 | if not ret:
102 | break
103 | pass
104 | ```
105 |
106 | ffmpegcv
107 | ```
108 | cap = ffmpegcv.VideoCapture(file)
109 | while True:
110 | ret, frame = cap.read()
111 | if not ret:
112 | break
113 | pass
114 | ```
115 |
116 | Or use iterator
117 | ```
118 | cap = ffmpegcv.VideoCapture(file)
119 | for frame in cap:
120 | pass
121 | counts = len(cap)
122 | ```
123 |
124 | Use GPU to accelerate decoding
125 | ```
126 | cap_cpu = ffmpegcv.VideoCapture(file)
127 | cap_gpu = ffmpegcv.VideoCaptureNV(file)
128 | ```
129 |
130 | Use rgb24 instead of bgr24
131 | ```
132 | cap = ffmpegcv.VideoCapture(file, pix_fmt='rgb24')
133 | ```
134 |
135 | Crop video.
136 | ```python
137 | cap = ffmpegcv.VideoCapture(file, crop_xywh=(0, 0, 640, 480))
138 | ```
139 |
140 | Resize the video to the given size
141 | ```
142 | cap = ffmpegcv.VideoCapture(file, resize=(640, 480))
143 | ```
144 |
145 | Resize and keep the aspect ratio with black border
146 | ```
147 | cap = ffmpegcv.VideoCapture(file, resize=(640, 480), resize_keepratio=True)
148 | ```
149 |
150 | Crop and then resize the video.
151 | ```python
152 | cap = ffmpegcv.VideoCapture(file, crop_xywh=(0, 0, 640, 480), resize=(512, 512))
153 | ```
154 | Author: Chenxinfeng 2022-04-16, cxf529125853@163.com
155 | """
156 | return FFmpegReader.VideoReader(
157 | file, codec, pix_fmt, crop_xywh, resize, resize_keepratio, resize_keepratioalign
158 | )
159 |
160 |
161 | VideoReader = VideoCapture
162 |
163 |
164 | def VideoWriter(
165 | file, codec=None, fps=30, pix_fmt="bgr24", bitrate=None, resize=None, preset=None
166 | ) -> FFmpegWriter:
167 | """
168 | Alternative to cv2.VideoWriter
169 |
170 | Parameters
171 | ----------
172 | file : str
173 | Path to video file.
174 | codec : str
175 | Codec to use. Optional. Default is `None` (x264).
176 | fps : number
177 | Frames per second. Optional. Default is 30.
178 | pix_fmt : str
179 | Pixel format of input. ['bgr24' | 'rgb24']. Optional. Default is 'bgr24'.
180 | bitrate : str
181 | Bitrate of output video. Optional. Default is `None`.
182 | resize : tuple
183 | Frame size of output. (width, height). Optional. Default is `None`.
184 | preset : str
185 | Preset of ffmpeg. Optional. Default is `None`.
186 | Examples
187 | --------
188 | opencv
189 | ```
190 | out = cv2.VideoWriter('outpy.avi',
191 | cv2.VideoWriter_fourcc('M','J','P','G'),
192 | 10,
193 | (w, h))
194 | out.write(frame1)
195 | out.write(frame2)
196 | out.release()
197 | ```
198 |
199 | ffmpegcv
200 | ```
201 | out = ffmpegcv.VideoWriter('outpy.avi', None, 10)
202 | out.write(frame1)
203 | out.write(frame2)
204 | out.release()
205 | ```
206 |
207 | frameSize is decided by the size of the first frame
208 | ```
209 | out = ffmpegcv.VideoWriter('outpy.avi', None, 10)
210 | ```
211 |
212 | Use GPU to accelerate encoding
213 | ```
214 | out_cpu = ffmpegcv.VideoWriter('outpy.avi', None, 10)
215 | out_gpu = ffmpegcv.VideoWriter('outpy.avi', 'h264_nvenc', 10)
216 | ```
217 |
218 | Use rgb24 instead of bgr24
219 | ```
220 | out = ffmpegcv.VideoWriter('outpy.avi', None, 10, pix_fmt='rgb24')
221 | out.write(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
222 | ```
223 |
224 | Author: Chenxinfeng 2022-04-16, cxf529125853@163.com
225 | """
226 | return FFmpegWriter.VideoWriter(
227 | file, codec, fps, pix_fmt, bitrate, resize, preset=preset
228 | )
229 |
230 |
231 | def VideoCaptureNV(
232 | file,
233 | pix_fmt="bgr24",
234 | crop_xywh=None,
235 | resize=None,
236 | resize_keepratio=True,
237 | resize_keepratioalign="center",
238 | gpu=0,
239 | ) -> FFmpegReaderNV:
240 | """
241 | `ffmpegcv.VideoCaptureNV` is a gpu version for `ffmpegcv.VideoCapture`.
242 | """
243 | _check_nvidia()
244 | return FFmpegReaderNV.VideoReader(
245 | file, pix_fmt, crop_xywh, resize, resize_keepratio, resize_keepratioalign, gpu
246 | )
247 |
248 |
249 | VideoReaderNV = VideoCaptureNV
250 |
251 |
252 | def VideoCaptureQSV(
253 | file,
254 | pix_fmt="bgr24",
255 | crop_xywh=None,
256 | resize=None,
257 | resize_keepratio=True,
258 | resize_keepratioalign="center",
259 | gpu=0,
260 | ) -> FFmpegReaderQSV:
261 | """
262 | `ffmpegcv.VideoCaptureQSV` is a gpu version for `ffmpegcv.VideoCapture`.
263 | """
264 | return FFmpegReaderQSV.VideoReader(
265 | file, pix_fmt, crop_xywh, resize, resize_keepratio, resize_keepratioalign, gpu
266 | )
267 |
268 |
269 | VideoReaderQSV = VideoCaptureQSV
270 |
271 |
272 | def VideoWriterNV(
273 | file,
274 | codec=None,
275 | fps=30,
276 | pix_fmt="bgr24",
277 | gpu=0,
278 | bitrate=None,
279 | resize=None,
280 | preset=None,
281 | ) -> FFmpegWriterNV:
282 | """
283 | `ffmpegcv.VideoWriterNV` is a gpu version for `ffmpegcv.VideoWriter`.
284 | """
285 | _check_nvidia()
286 | return FFmpegWriterNV.VideoWriter(
287 | file, codec, fps, pix_fmt, gpu, bitrate, resize, preset=preset
288 | )
289 |
290 |
291 | def VideoWriterQSV(
292 | file,
293 | codec=None,
294 | fps=30,
295 | pix_fmt="bgr24",
296 | gpu=0,
297 | bitrate=None,
298 | resize=None,
299 | preset=None,
300 | ) -> FFmpegWriterQSV:
301 | """
302 | `ffmpegcv.VideoWriterQSV` is a gpu version for `ffmpegcv.VideoWriter`.
303 | """
304 | return FFmpegWriterQSV.VideoWriter(
305 | file, codec, fps, pix_fmt, gpu, bitrate, resize, preset=preset
306 | )
307 |
308 |
309 | def VideoWriterStreamRT(
310 | url, pix_fmt="bgr24", bitrate=None, resize=None, preset=None
311 | ) -> FFmpegWriterStreamRT:
312 | return FFmpegWriterStreamRT.VideoWriter(
313 | url, "libx264", pix_fmt, bitrate, resize, preset
314 | )
315 |
316 |
317 | def VideoCaptureCAM(
318 | camname,
319 | pix_fmt="bgr24",
320 | crop_xywh=None,
321 | resize=None,
322 | resize_keepratio=True,
323 | resize_keepratioalign="center",
324 | camsize_wh=None,
325 | camfps=None,
326 | camcodec=None,
327 | campix_fmt=None,
328 | ) -> FFmpegReaderCAM:
329 | """
330 | Alternative to cv2.VideoCapture
331 |
332 | Parameters
333 | ----------
334 | file : see ffmpegcv.VideoReader
335 | codec : see ffmpegcv.VideoReader
336 | pix_fmt : see ffmpegcv.VideoReader
337 | crop_xywh : see ffmpegcv.VideoReader
338 | resize : see ffmpegcv.VideoReader
339 | resize_keepratio : see ffmpegcv.VideoReader
340 | resize_keepratioalign : see ffmpegcv.VideoReader
341 | camsize_wh: tuple or None
342 | Camera resolution (width, height). e.g (800, 600)
343 | camfps: float or None
344 | Camera framerate. e.g. 30.
345 | camcodec: str or None
346 | Camera codec. e.g. 'mjpeg' or 'h264'.
347 | campix_fmt: str or None
348 | Camera pixel format. e.g. 'rgb24' or 'yuv420p'.
349 | Just set one of `camcodec` or `campix_fmt`.
350 | Examples
351 | --------
352 | opencv
353 | ```
354 | cap = cv2.VideoCapture(0)
355 | while True:
356 | ret, frame = cap.read()
357 | if not ret:
358 | break
359 | pass
360 | ```
361 |
362 | ffmpegcv
363 | ```
364 | cap = ffmpegcv.VideoCaptureCAM(0)
365 | while True:
366 | ret, frame = cap.read()
367 | if not ret:
368 | break
369 | pass
370 | ```
371 |
372 | Or use camera name
373 | ```
374 | cap = ffmpegcv.VideoCaptureCAM("Integrated Camera")
375 | ```
376 |
377 | Use full camera parameter
378 | ```
379 | cap = ffmpegcv.VideoCaptureCAM('FaceTime HD Camera',
380 | camsize_wh = (1280,720),
381 | camfps = 30,
382 | campix_fmt = 'nv12')
383 | ```
384 |
385 | Use camera with ROI operations
386 | ```
387 | cap = ffmpegcv.VideoCaptureCAM("Integrated Camera",
388 | crop_xywh = (0, 0, 640, 480),
389 | resize = (512, 512),
390 | resize_keepratio = True)
391 | ```
392 | Author: Chenxinfeng 2023-05-11, cxf529125853@163.com
393 | """
394 | return FFmpegReaderCAM.VideoReader(
395 | camname,
396 | pix_fmt,
397 | crop_xywh,
398 | resize,
399 | resize_keepratio,
400 | resize_keepratioalign,
401 | camsize_wh=camsize_wh,
402 | camfps=camfps,
403 | camcodec=camcodec,
404 | campix_fmt=campix_fmt,
405 | )
406 |
407 |
408 | VideoReaderCAM = VideoCaptureCAM
409 |
410 |
411 | def VideoCaptureStream(
412 | stream_url,
413 | codec=None,
414 | pix_fmt="bgr24",
415 | crop_xywh=None,
416 | resize=None,
417 | resize_keepratio=True,
418 | resize_keepratioalign="center",
419 | timeout=None,
420 | ) -> FFmpegReaderStream:
421 | """
422 | Alternative to cv2.VideoCapture
423 |
424 | Parameters
425 | ----------
426 | stream_url : RTSP, RTP, RTMP, HTTP, HTTPS url
427 | codec : see ffmpegcv.VideoReader
428 | pix_fmt : see ffmpegcv.VideoReader
429 | crop_xywh : see ffmpegcv.VideoReader
430 | resize : see ffmpegcv.VideoReader
431 | resize_keepratio : see ffmpegcv.VideoReader
432 | resize_keepratioalign : see ffmpegcv.VideoReader
433 | timeout : waits in seconds for stream video to connect
434 |
435 | Examples
436 | --------
437 | opencv
438 | ```
439 | stream_url = 'http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8'
440 | cap = cv2.VideoCapture(stream_url, cv2.CAP_FFMPEG)
441 |
442 | if not cap.isOpened():
443 | print('Cannot open the stream')
444 | exit(-1)
445 |
446 | while True:
447 | ret, frame = cap.read()
448 | if not ret:
449 | break
450 | pass
451 | ```
452 |
453 | ffmpegcv
454 | ```
455 | cap = ffmpegcv.VideoCaptureStream(stream_url)
456 | while True:
457 | ret, frame = cap.read()
458 | if not ret:
459 | break
460 | pass
461 | ```
462 |
463 | Author: Chenxinfeng 2023-05-31, cxf529125853@163.com
464 | """
465 | return FFmpegReaderStream.VideoReader(
466 | stream_url,
467 | codec,
468 | pix_fmt,
469 | crop_xywh,
470 | resize,
471 | resize_keepratio,
472 | resize_keepratioalign,
473 | timeout,
474 | )
475 |
476 |
477 | VideoReaderStream = VideoCaptureStream
478 |
479 |
480 | def VideoCaptureStreamRT(
481 | stream_url,
482 | codec=None,
483 | pix_fmt="bgr24",
484 | crop_xywh=None,
485 | resize=None,
486 | resize_keepratio=True,
487 | resize_keepratioalign="center",
488 | gpu=None,
489 | timeout=None,
490 | ) -> FFmpegReaderStreamRT:
491 | if gpu is None:
492 | return FFmpegReaderStreamRT.VideoReader(
493 | stream_url,
494 | codec,
495 | pix_fmt,
496 | crop_xywh,
497 | resize,
498 | resize_keepratio,
499 | resize_keepratioalign,
500 | timeout=timeout,
501 | )
502 | else:
503 | return FFmpegReaderStreamRTNV.VideoReader(
504 | stream_url,
505 | codec,
506 | pix_fmt,
507 | crop_xywh,
508 | resize,
509 | resize_keepratio,
510 | resize_keepratioalign,
511 | gpu=gpu,
512 | timeout=timeout,
513 | )
514 |
515 |
516 | VideoReaderStreamRT = VideoCaptureStreamRT
517 |
518 |
519 | def VideoCapturePannels(
520 | file: str, crop_xywh_l: list, codec=None, pix_fmt="bgr24", resize=None
521 | ):
522 | """
523 | Alternative to cv2.VideoCapture
524 |
525 | Parameters
526 | ----------
527 | file : str
528 | Path to video file.
529 | crop_xywh_l : list of crop_xywh
530 | Crop the frame. [(x0, y0, w0, h0), (x1, y1, w1, h1), ...].
531 | codec : str
532 | Codec to use. Optional. Default is `None`.
533 | pix_fmt : str
534 | Pixel format. ['bgr24' | 'rgb24' | 'gray']. Optional. Default is 'bgr24'.
535 | resize : tuple as (w,h)
536 | Resize the pannels to identical size. Optional. Default is `None`.
537 | Does not keep the ratio.
538 |
539 |
540 | Examples
541 | --------
542 |
543 |
544 | ffmpegcv
545 | ```
546 | w,h = 1280,800
547 | cap = ffmpegcv.VideoCapturePannels(file,
548 | [[0,0,w,h], [w,0,w,h],[0,h,w,h], [w,h,w,h]])
549 | while True:
550 | ret, frame_pannels = cap.read()
551 | if not ret:
552 | break
553 | print(frame_pannels.shape) # shape=(4,h,w,3)
554 | ```
555 |
556 | Author: Chenxinfeng 2024-02-15, cxf529125853@163.com
557 | """
558 |
559 | return FFmpegReaderPannels.VideoReader(
560 | file,
561 | crop_xywh_l,
562 | codec,
563 | pix_fmt,
564 | resize,
565 | )
566 |
567 |
568 | VideoReaderPannels = VideoCapturePannels
569 |
570 |
571 | def toCUDA(vid: FFmpegReader, gpu: int = 0, tensor_format: str = "chw") -> FFmpegReader:
572 | """
573 | Convert frames to CUDA tensor float32 in 'chw' or 'hwc' format.
574 | """
575 | from ffmpegcv.ffmpeg_reader_cuda import FFmpegReaderCUDA
576 |
577 | return FFmpegReaderCUDA(vid, gpu, tensor_format)
578 |
--------------------------------------------------------------------------------
/ffmpegcv/ffmpeg_noblock.py:
--------------------------------------------------------------------------------
1 | from .ffmpeg_reader_noblock import FFmpegReaderNoblock
2 | from .ffmpeg_writer_noblock import FFmpegWriterNoblock
3 | from typing import Callable
4 | import ffmpegcv
5 | import threading
6 | import numpy as np
7 | import queue
8 |
9 |
10 | def noblock(fun:Callable, *v_args, **v_kargs):
11 | readerfuns = (ffmpegcv.VideoCapture, ffmpegcv.VideoCaptureNV)
12 | writerfuns = (ffmpegcv.VideoWriter, ffmpegcv.VideoWriterNV)
13 |
14 | if fun in readerfuns:
15 | proxyfun = FFmpegReaderNoblock(fun, *v_args, **v_kargs)
16 | elif fun in writerfuns:
17 | proxyfun = FFmpegWriterNoblock(fun, *v_args, **v_kargs)
18 | else:
19 | raise ValueError('The function is not supported as a Reader or Writer')
20 |
21 | return proxyfun
22 |
23 |
24 | class ReadLiveLast(threading.Thread, ffmpegcv.FFmpegReader):
25 | def __init__(self, fun, *args, **kvargs):
26 | threading.Thread.__init__(self)
27 | ffmpegcv.FFmpegReader.__init__(self)
28 | self.vid = vid = fun(*args, **kvargs)
29 | props_name = ['width', 'height', 'fps', 'count', 'codec', 'ffmpeg_cmd',
30 | 'size', 'pix_fmt', 'out_numpy_shape', 'iframe',
31 | 'duration', 'origin_width', 'origin_height']
32 | for name in props_name:
33 | setattr(self, name, getattr(vid, name, None))
34 |
35 | self.img = np.zeros(self.out_numpy_shape, dtype=np.uint8)
36 | self.ret = True
37 | self._isopen = True
38 | self._q = queue.Queue(maxsize=1) # synchronize new frame
39 | self._lock = threading.Lock()
40 | self.start()
41 |
42 | def read(self):
43 | if self.ret:
44 | self._q.get() # if reading too freq, then wait until new frame
45 | self.iframe += 1
46 | return self.ret, self.img
47 |
48 | def release(self):
49 | with self._lock:
50 | self._isopen = False
51 | self.vid.release()
52 |
53 | def run(self):
54 | while True:
55 | with self._lock:
56 | if self._isopen:
57 | self.ret, self.img = self.vid.read()
58 | else:
59 | break
60 | if not self._q.full():
61 | self._q.put(None)
62 | if not self.ret:
63 | break
--------------------------------------------------------------------------------
/ffmpegcv/ffmpeg_reader.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import pprint
3 | import warnings
4 | import os
5 | import sys
6 | import select
7 | from .video_info import (
8 | run_async_reader as run_async,
9 | get_info,
10 | get_num_NVIDIA_GPUs,
11 | decoder_to_nvidia,
12 | release_process,
13 | )
14 |
15 |
16 | def get_videofilter_cpu(
17 | originsize: list,
18 | pix_fmt: str,
19 | crop_xywh: list,
20 | resize: list,
21 | resize_keepratio: bool,
22 | resize_keepratioalign: str,
23 | ):
24 | """
25 | ONGONING: common filter for video/cam/stream capture.
26 | """
27 | assert pix_fmt in ["rgb24", "bgr24", "yuv420p", "yuvj420p", "nv12", "gray"]
28 | origin_width, origin_height = originsize
29 | if crop_xywh:
30 | crop_w, crop_h = crop_xywh[2:]
31 | if not all([n % 2 == 0] for n in crop_xywh):
32 | print("Warning 'crop_xywh' would be replaced into even numbers")
33 | crop_xywh = [int(n//2*2) for n in crop_xywh]
34 | assert crop_w <= origin_width and crop_h <= origin_height
35 | x, y, w, h = crop_xywh
36 | cropopt = f"crop={w}:{h}:{x}:{y}"
37 | else:
38 | crop_w, crop_h = origin_width, origin_height
39 | cropopt = ""
40 |
41 | crop_wh = (crop_w, crop_h)
42 | if resize is None or tuple(resize) == crop_wh:
43 | scaleopt = ""
44 | padopt = ""
45 | final_size_wh = crop_wh
46 | else:
47 | final_size_wh = (dst_width, dst_height) = resize
48 | assert all([n % 2 == 0] for n in resize), "'resize' must be even number"
49 | if not resize_keepratio:
50 | scaleopt = f"scale={dst_width}x{dst_height}"
51 | padopt = ""
52 | else:
53 | re_width, re_height = crop_w / (crop_h / dst_height), dst_height
54 | if re_width > dst_width:
55 | re_width, re_height = dst_width, crop_h / (crop_w / dst_width)
56 | re_width, re_height = int(re_width), int(re_height)
57 | scaleopt = f"scale={re_width}x{re_height}"
58 | if resize_keepratioalign is None:
59 | resize_keepratioalign = "center"
60 | paddings = {
61 | "center": ((dst_width - re_width) // 2, (dst_height - re_height) // 2,),
62 | "topleft": (0, 0),
63 | "topright": (dst_width - re_width, 0),
64 | "bottomleft": (0, dst_height - re_height),
65 | "bottomright": (dst_width - re_width, dst_height - re_height),
66 | }
67 | assert (
68 | resize_keepratioalign in paddings
69 | ), 'resize_keepratioalign must be one of "center"(mmpose), "topleft"(mmdetection), "topright", "bottomleft", "bottomright"'
70 | xpading, ypading = paddings[resize_keepratioalign]
71 | padopt = f"pad={dst_width}:{dst_height}:{xpading}:{ypading}:black"
72 |
73 | pix_fmtopt = "extractplanes=y" if pix_fmt == "gray" else ""
74 | if any([cropopt, scaleopt, padopt, pix_fmtopt]):
75 | filterstr = ",".join(x for x in [cropopt, scaleopt, padopt, pix_fmtopt] if x)
76 | filteropt = f"-vf {filterstr}"
77 | else:
78 | filteropt = ""
79 | return crop_wh, final_size_wh, filteropt
80 |
81 |
82 | def get_videofilter_gpu(
83 | originsize: list,
84 | pix_fmt: str,
85 | crop_xywh: list,
86 | resize: list,
87 | resize_keepratio: bool,
88 | resize_keepratioalign: str,
89 | ):
90 | assert pix_fmt in ["rgb24", "bgr24", "yuv420p", "yuvj420p", "nv12", "gray"]
91 | origin_width, origin_height = originsize
92 | if crop_xywh:
93 | crop_w, crop_h = crop_xywh[2:]
94 | assert all([n % 2 == 0] for n in crop_xywh), "'crop_xywh' must be even number"
95 | assert crop_w <= origin_width and crop_h <= origin_height
96 | x, y, w, h = crop_xywh
97 | top, bottom, left, right = (
98 | y,
99 | origin_height - (y + h),
100 | x,
101 | origin_width - (x + w),
102 | ) # crop length
103 | cropopt = f"-crop {top}x{bottom}x{left}x{right}"
104 | else:
105 | crop_w, crop_h = origin_width, origin_height
106 | cropopt = ""
107 |
108 | crop_wh = (crop_w, crop_h)
109 | filteropt = ""
110 | scaleopt = ""
111 | if resize is None or tuple(resize) == crop_wh:
112 | final_size_wh = crop_wh
113 | else:
114 | final_size_wh = (dst_width, dst_height) = resize
115 | assert all([n % 2 == 0] for n in resize), "'resize' must be even number"
116 | if not resize_keepratio:
117 | scaleopt = f"-resize {dst_width}x{dst_height}"
118 | else:
119 | re_width, re_height = crop_w / (crop_h / dst_height), dst_height
120 | if re_width > dst_width:
121 | re_width, re_height = dst_width, crop_h / (crop_w / dst_width)
122 | re_width, re_height = int(re_width), int(re_height)
123 | scaleopt = f"-resize {re_width}x{re_height}"
124 | if resize_keepratioalign is None:
125 | resize_keepratioalign = "center"
126 | paddings = {
127 | "center": ((dst_width - re_width) // 2, (dst_height - re_height) // 2,),
128 | "topleft": (0, 0),
129 | "topright": (dst_width - re_width, 0),
130 | "bottomleft": (0, dst_height - re_height),
131 | "bottomright": (dst_width - re_width, dst_height - re_height),
132 | }
133 | assert (
134 | resize_keepratioalign in paddings
135 | ), 'resize_keepratioalign must be one of "center"(mmpose), "topleft"(mmdetection), "topright", "bottomleft", "bottomright"'
136 | xpading, ypading = paddings[resize_keepratioalign]
137 | padopt = f"pad={dst_width}:{dst_height}:{xpading}:{ypading}:black"
138 | filteropt = f"-vf {padopt}"
139 |
140 | if pix_fmt == "gray":
141 | if filteropt:
142 | filteropt = f"{filteropt},extractplanes=y"
143 | else:
144 | filteropt = f"-vf extractplanes=y"
145 |
146 | return crop_wh, final_size_wh, [cropopt, scaleopt, filteropt]
147 |
148 |
149 | def get_outnumpyshape(size_wh: list, pix_fmt: str) -> tuple:
150 | width, height = size_wh
151 | assert (not pix_fmt == "yuv420p") or (
152 | height % 2 == 0 and width % 2 == 0
153 | ), "yuv420p must be even"
154 | out_numpy_shape = {
155 | "rgb24": (height, width, 3),
156 | "bgr24": (height, width, 3),
157 | "yuv420p": (int(height * 1.5), width),
158 | "yuvj420p": (int(height * 1.5), width),
159 | "nv12": (int(height * 1.5), width),
160 | "gray": (height, width, 1),
161 | }[pix_fmt]
162 | return out_numpy_shape
163 |
164 |
165 | class FFmpegReader:
166 | def __init__(self):
167 | self.filename:str = ''
168 | self.iframe:int = -1
169 | self.width:int = None
170 | self.height:int = None
171 | self.size = (None, None)
172 | self.waitInit:bool = True
173 | self.process = None
174 | self._isopen:bool = True
175 | self.debug:bool = False
176 | self.fps:float = None
177 | self.out_numpy_shape = (None, None, None)
178 |
179 | def __repr__(self):
180 | props = pprint.pformat(self.__dict__).replace("{", " ").replace("}", " ")
181 | return f"{self.__class__}\n" + props
182 |
183 | def __enter__(self):
184 | return self
185 |
186 | def __exit__(self, type, value, traceback):
187 | self.release()
188 |
189 | def __len__(self):
190 | return self.count
191 |
192 | def __iter__(self):
193 | return self
194 |
195 | def __next__(self):
196 | ret, img = self.read()
197 | if ret:
198 | return img
199 | else:
200 | raise StopIteration
201 |
202 | @staticmethod
203 | def VideoReader(
204 | filename,
205 | codec,
206 | pix_fmt,
207 | crop_xywh,
208 | resize,
209 | resize_keepratio,
210 | resize_keepratioalign,
211 | ):
212 | assert os.path.exists(filename) and os.path.isfile(
213 | filename
214 | ), f"{filename} not exists"
215 |
216 | vid = FFmpegReader()
217 | vid.filename = filename
218 | videoinfo = get_info(filename)
219 | vid.origin_width = videoinfo.width
220 | vid.origin_height = videoinfo.height
221 | vid.fps = videoinfo.fps
222 | vid.count = videoinfo.count
223 | vid.duration = videoinfo.duration
224 | vid.pix_fmt = pix_fmt
225 | vid.codec = videoinfo.codec
226 |
227 | if codec is not None:
228 | warnings.warn(
229 | "The 'codec' parameter is auto detected and will be removed "
230 | "in future versions. Please refrain from using this parameter.",
231 | DeprecationWarning
232 | )
233 |
234 | (
235 | (vid.crop_width, vid.crop_height),
236 | (vid.width, vid.height),
237 | filteropt,
238 | ) = get_videofilter_cpu(
239 | (vid.origin_width, vid.origin_height),
240 | pix_fmt,
241 | crop_xywh,
242 | resize,
243 | resize_keepratio,
244 | resize_keepratioalign,
245 | )
246 | vid.size = (vid.width, vid.height)
247 |
248 | vid.ffmpeg_cmd = (
249 | f"ffmpeg -loglevel error "
250 | f' -vcodec {vid.codec} -r {vid.fps} -i "{filename}" '
251 | f" {filteropt} -pix_fmt {pix_fmt} -r {vid.fps} -f rawvideo pipe:"
252 | )
253 | vid.out_numpy_shape = get_outnumpyshape(vid.size, pix_fmt)
254 | return vid
255 |
256 | def read(self):
257 | if self.waitInit:
258 | self.process = run_async(self.ffmpeg_cmd)
259 | self.waitInit = False
260 |
261 | in_bytes = self.process.stdout.read(np.prod(self.out_numpy_shape))
262 | # check if ffmpeg process error
263 | # if self.process.stderr.readable():
264 | # print('---a')
265 | # data = self.process.stderr.read()
266 | # sys.stderr.buffer.write(data)
267 | # print('---f')
268 |
269 | if not in_bytes:
270 | self.release()
271 | return False, None
272 | self.iframe += 1
273 | img = np.frombuffer(in_bytes, np.uint8).reshape(self.out_numpy_shape)
274 |
275 | return True, img
276 |
277 | def isOpened(self):
278 | return self._isopen
279 |
280 | def release(self):
281 | self._isopen = False
282 | release_process(self.process, forcekill=True)
283 |
284 | def close(self):
285 | return self.release()
286 |
287 |
288 | class FFmpegReaderNV(FFmpegReader):
289 | def _get_opts(
290 | vid,
291 | videoinfo,
292 | crop_xywh,
293 | resize,
294 | resize_keepratio,
295 | resize_keepratioalign,
296 | isgray,
297 | ):
298 | vid.origin_width = videoinfo.width
299 | vid.origin_height = videoinfo.height
300 | vid.fps = videoinfo.fps
301 | vid.count = videoinfo.count
302 | vid.duration = videoinfo.duration
303 | vid.width, vid.height = vid.origin_width, vid.origin_height
304 | vid.codec = videoinfo.codec
305 | assert vid.origin_height % 2 == 0, "height must be even"
306 | assert vid.origin_width % 2 == 0, "width must be even"
307 | if crop_xywh:
308 | crop_w, crop_h = crop_xywh[2:]
309 | vid.width, vid.height = crop_w, crop_h
310 | x, y, w, h = crop_xywh
311 | top, bottom, left, right = (
312 | y,
313 | vid.origin_height - (y + h),
314 | x,
315 | vid.origin_width - (x + w),
316 | ) # crop length
317 | cropopt = f"-crop {top}x{bottom}x{left}x{right}"
318 | else:
319 | crop_w, crop_h = vid.origin_width, vid.origin_height
320 | cropopt = ""
321 |
322 | vid.crop_width, vid.crop_height = crop_w, crop_h
323 |
324 | if resize is None or tuple(resize) == (vid.crop_width, vid.crop_height):
325 | scaleopt = ""
326 | filteropt = ""
327 | else:
328 | vid.width, vid.height = dst_width, dst_height = resize
329 | if not resize_keepratio:
330 | scaleopt = f"-resize {dst_width}x{dst_height}"
331 | filteropt = ""
332 | else:
333 | re_width, re_height = crop_w / (crop_h / dst_height), dst_height
334 | if re_width > dst_width:
335 | re_width, re_height = dst_width, crop_h / (crop_w / dst_width)
336 | re_width, re_height = int(re_width), int(re_height)
337 | scaleopt = f"-resize {re_width}x{re_height}"
338 | if resize_keepratioalign is None:
339 | resize_keepratioalign = "center"
340 | paddings = {
341 | "center": (
342 | (dst_width - re_width) // 2,
343 | (dst_height - re_height) // 2,
344 | ),
345 | "topleft": (0, 0),
346 | "topright": (dst_width - re_width, 0),
347 | "bottomleft": (0, dst_height - re_height),
348 | "bottomright": (dst_width - re_width, dst_height - re_height),
349 | }
350 | assert (
351 | resize_keepratioalign in paddings
352 | ), 'resize_keepratioalign must be one of "center"(mmpose), "topleft"(mmdetection), "topright", "bottomleft", "bottomright"'
353 | xpading, ypading = paddings[resize_keepratioalign]
354 | padopt = f"pad={dst_width}:{dst_height}:{xpading}:{ypading}:black"
355 | filteropt = f"-vf {padopt}"
356 |
357 | if isgray:
358 | if filteropt:
359 | filteropt = f"{filteropt},extractplanes=y"
360 | else:
361 | filteropt = f"-vf extractplanes=y"
362 |
363 | vid.size = (vid.width, vid.height)
364 | return cropopt, scaleopt, filteropt
365 |
366 | @staticmethod
367 | def VideoReader(
368 | filename,
369 | pix_fmt,
370 | crop_xywh,
371 | resize,
372 | resize_keepratio,
373 | resize_keepratioalign,
374 | gpu,
375 | ):
376 | assert os.path.exists(filename) and os.path.isfile(
377 | filename
378 | ), f"{filename} not exists"
379 | assert pix_fmt in ["rgb24", "bgr24", "yuv420p", "yuvj420p", "nv12", "gray"]
380 | numGPU = get_num_NVIDIA_GPUs()
381 | assert numGPU > 0, "No GPU found"
382 | gpu = int(gpu) % numGPU if gpu is not None else 0
383 | assert (
384 | resize is None or len(resize) == 2
385 | ), "resize must be a tuple of (width, height)"
386 | videoinfo = get_info(filename)
387 | vid = FFmpegReaderNV()
388 | vid.filename = filename
389 | isgray = pix_fmt == "gray"
390 | cropopt, scaleopt, filteropt = vid._get_opts(
391 | videoinfo,
392 | crop_xywh,
393 | resize,
394 | resize_keepratio,
395 | resize_keepratioalign,
396 | isgray,
397 | )
398 | vid.codecNV = decoder_to_nvidia(vid.codec)
399 |
400 | vid.ffmpeg_cmd = (
401 | f"ffmpeg -loglevel error -hwaccel cuda -hwaccel_device {gpu} "
402 | f' -vcodec {vid.codecNV} {cropopt} {scaleopt} -r {vid.fps} -i "{filename}" '
403 | f" {filteropt} -pix_fmt {pix_fmt} -r {vid.fps} -f rawvideo pipe:"
404 | )
405 |
406 | vid.pix_fmt = pix_fmt
407 | vid.out_numpy_shape = get_outnumpyshape(vid.size, pix_fmt)
408 | return vid
409 |
--------------------------------------------------------------------------------
/ffmpegcv/ffmpeg_reader_camera.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import pprint
3 | from .video_info import run_async, release_process
4 | import re
5 | import subprocess
6 | from threading import Thread
7 | from queue import Queue
8 | import sys
9 | import os
10 | from ffmpegcv.ffmpeg_reader import get_videofilter_cpu, get_outnumpyshape
11 |
12 |
13 | class platform:
14 | win = 0
15 | linux = 1
16 | mac = 2
17 | other = 3
18 |
19 |
20 | if sys.platform.startswith("linux"):
21 | this_os = platform.linux
22 | elif sys.platform.startswith("win32"):
23 | this_os = platform.win
24 | elif sys.platform.startswith("darwin"):
25 | this_os = platform.mac
26 | else:
27 | this_os = platform.other
28 |
29 |
30 | def _query_camera_divices_mac() -> dict:
31 | # run the command 'ffmpeg -f avfoundation -list_devices true -i "" '
32 | command = 'ffmpeg -hide_banner -f avfoundation -list_devices true -i ""'
33 | process = subprocess.Popen(
34 | command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
35 | )
36 | stdout, stderr = process.communicate()
37 |
38 | # parse the output into a dictionary
39 | lines = stderr.decode("utf-8").split("AVFoundation audio devices:")[0].split("\n")
40 | id_device_map = dict()
41 | device_id_pattern = re.compile(r"\[[^\]]*?\] \[(\d*)\]")
42 | device_name_pattern = re.compile(r".*\] (.*)")
43 | for line in lines[1:-1]:
44 | device_id = int(re.search(device_id_pattern, line).group(1))
45 | device_name = re.search(device_name_pattern, line).group(1)
46 | id_device_map[device_id] = (device_name, device_id)
47 | return id_device_map
48 |
49 |
50 | def _query_camera_divices_win() -> dict:
51 | command = "ffmpeg -hide_banner -list_devices true -f dshow -i dummy"
52 | process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
53 | stdout, stderr = process.communicate()
54 | dshowliststr = stderr.decode("utf-8")
55 | dshowliststr = dshowliststr.split("DirectShow audio devices")[0]
56 | pattern = re.compile(r'\[*?\] *"([^"]*)"')
57 | matches = pattern.findall(dshowliststr)
58 | alternative_pattern = re.compile(r'Alternative name "(.*)"')
59 | alternative_names = alternative_pattern.findall(dshowliststr)
60 | assert len(matches) == len(alternative_names)
61 | id_device_map = {
62 | i: device for i, device in enumerate(zip(matches, alternative_names))
63 | }
64 | if len(id_device_map) == 0:
65 | print("No camera divice found")
66 | return id_device_map
67 |
68 |
69 | def _query_camera_divices_linux() -> dict:
70 | "edit from https://github.com/p513817/python-get-cam-name/blob/master/get_cam_name.py"
71 | root = "/sys/class/video4linux"
72 | cam_info = []
73 |
74 | for index in sorted([file for file in os.listdir(root)]):
75 | # Get Camera Name From /sys/class/video4linux/