peak-end-net / modules /rawvideo_util.py
multimodalart's picture
multimodalart HF Staff
Upload folder using huggingface_hub
26c4860 verified
Raw
History Blame Contribute Delete
7.07 kB
#这段代码的原理在于通过 OpenCV 读取视频帧,然后使用 torchvision 转换帧图像以便能够在 PyTorch 中使用进行后续处理,如视频特征提取、模型训练等用途。
import os
import sys
import ctypes
# 在导入 cv2 之前,先抑制 ffmpeg 底层的 stderr 日志输出
# 这些 warning(如 "Invalid NAL unit size"、"partial file")来自 ffmpeg C 库,
# 无法通过 Python logging 或 OpenCV API 控制,只能在 C 层面重定向 stderr
os.environ["OPENCV_FFMPEG_LOGLEVEL"] = "-8" # AV_LOG_QUIET
os.environ["OPENCV_LOG_LEVEL"] = "ERROR"
os.environ["OPENCV_THREAD_COUNT"] = "1"
import torch as th
import numpy as np
from PIL import Image
# pytorch=1.7.1
from torchvision.transforms import Compose, Resize, CenterCrop, ToTensor, Normalize
# pip install opencv-python
import cv2
# 限制 OpenCV 线程数,避免多进程分布式训练时线程爆炸
cv2.setNumThreads(1)
def _suppress_ffmpeg_stderr():
"""
通过 C 层面重定向 stderr 的 fd 到 /dev/null,
彻底抑制 ffmpeg 底层解码器输出的 warning 日志。
仅在 Linux 上生效。
"""
try:
devnull_fd = os.open(os.devnull, os.O_WRONLY)
# 保存原始 stderr fd
original_stderr_fd = os.dup(2)
# 将 fd 2 (stderr) 重定向到 /dev/null
os.dup2(devnull_fd, 2)
os.close(devnull_fd)
return original_stderr_fd
except OSError:
return None
def _restore_stderr(original_stderr_fd):
"""恢复原始的 stderr"""
if original_stderr_fd is not None:
try:
os.dup2(original_stderr_fd, 2)
os.close(original_stderr_fd)
except OSError:
pass
#RawVideoExtractorCV2 类用于从视频中提取帧并将其转化为 PyTorch 张量
class RawVideoExtractorCV2():
def __init__(self, centercrop=False, size=224, framerate=-1, ):
self.centercrop = centercrop
self.size = size
self.framerate = framerate
self.transform = self._transform(self.size)
#构造用于图像预处理的变换序列,包括调整大小、中心裁剪、转换为 RGB 模式、转换为张量、以及归一化。
def _transform(self, n_px):
return Compose([
Resize(n_px, interpolation=Image.BICUBIC),
CenterCrop(n_px),
lambda image: image.convert("RGB"),
ToTensor(),
Normalize((0.48145466, 0.4578275, 0.40821073), (0.26862954, 0.26130258, 0.27577711)),
])
def video_to_tensor(self, video_file, preprocess, sample_fp=0, start_time=None, end_time=None):
if start_time is not None or end_time is not None:
assert isinstance(start_time, int) and isinstance(end_time, int) \
and start_time > -1 and end_time > start_time
assert sample_fp > -1
# 抑制 ffmpeg 底层 C 库的 stderr warning 输出
saved_stderr = _suppress_ffmpeg_stderr()
cap = cv2.VideoCapture(video_file)
if not cap.isOpened():
cap.release()
_restore_stderr(saved_stderr)
return {'video': th.zeros(1)}
frameCount = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
fps = int(cap.get(cv2.CAP_PROP_FPS))
if fps == 0 or frameCount == 0:
cap.release()
_restore_stderr(saved_stderr)
return {'video': th.zeros(1)}
# 确定采样范围(帧索引)
if start_time is not None:
start_frame = int(start_time * fps)
end_frame = min(int(end_time * fps), frameCount)
else:
start_frame = 0
end_frame = frameCount
total_frames_in_range = end_frame - start_frame
if total_frames_in_range <= 0:
cap.release()
_restore_stderr(saved_stderr)
return {'video': th.zeros(1)}
# 计算需要采样的总帧数:每秒 sample_fp 帧
if sample_fp > 0:
total_duration_sec = total_frames_in_range / fps
target_num_frames = max(1, int(total_duration_sec * sample_fp))
else:
target_num_frames = total_frames_in_range
# 均匀采样帧索引,避免逐帧遍历
if target_num_frames >= total_frames_in_range:
frame_indices = list(range(start_frame, end_frame))
else:
frame_indices = np.linspace(start_frame, end_frame - 1, num=target_num_frames, dtype=int).tolist()
# 按顺序读取帧(顺序读取比随机 seek 快得多)
images = []
frame_indices_sorted = sorted(set(frame_indices))
# 使用顺序读取策略:设置到起始帧,然后顺序读取
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_indices_sorted[0])
current_frame_pos = frame_indices_sorted[0]
target_set = set(frame_indices_sorted)
for target_idx in frame_indices_sorted:
# 如果需要跳帧,用 seek(仅在跳跃较大时)
if target_idx > current_frame_pos:
skip_count = target_idx - current_frame_pos
if skip_count > fps:
# 跳跃较大时用 seek
cap.set(cv2.CAP_PROP_POS_FRAMES, target_idx)
else:
# 跳跃较小时顺序跳过(比 seek 快)
for _ in range(skip_count):
cap.grab()
ret, frame = cap.read()
if not ret:
break
current_frame_pos = target_idx + 1
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
images.append(preprocess(Image.fromarray(frame_rgb).convert("RGB")))
cap.release()
_restore_stderr(saved_stderr)
if len(images) > 0:
video_data = th.tensor(np.stack(images))
else:
video_data = th.zeros(1)
return {'video': video_data}
def get_video_data(self, video_path, start_time=None, end_time=None):
image_input = self.video_to_tensor(video_path, self.transform, sample_fp=self.framerate, start_time=start_time, end_time=end_time)
return image_input
def process_raw_data(self, raw_video_data):
tensor_size = raw_video_data.size()
tensor = raw_video_data.view(-1, 1, tensor_size[-3], tensor_size[-2], tensor_size[-1])
return tensor
def process_frame_order(self, raw_video_data, frame_order=0):
# 0: ordinary order; 1: reverse order; 2: random order.
if frame_order == 0:
pass
elif frame_order == 1:
reverse_order = np.arange(raw_video_data.size(0) - 1, -1, -1)
raw_video_data = raw_video_data[reverse_order, ...]
elif frame_order == 2:
random_order = np.arange(raw_video_data.size(0))
np.random.shuffle(random_order)
raw_video_data = raw_video_data[random_order, ...]
return raw_video_data
# An ordinary video frame extractor based CV2
# An ordinary video frame extractor based CV2
RawVideoExtractor = RawVideoExtractorCV2