import os import torch from torch.utils.data import DataLoader from torchvision import transforms from typing import Optional, Tuple from PIL import Image import time from pathlib import Path from auto_media_publisher.utils.media_input_output import VideoReader, VideoWriter, ImageSequenceReader, ImageSequenceWriter from .robustvideomatting.model import MattingNetwork import uuid VARIANT_RESNET50 ="resnet50" CHECKPOINT_RESNET50 ="./models/rvm_resnet50.pth" VARIANT_MOBILENETV3 ="mobilenetv3" CHECKPOINT_MOBILENETV3 ="./models/rvm_mobilenetv3.pth" class RobustVideoMattingConverter: """ 视频抠像转换器 封装原来的 convert_video 逻辑,支持 video / png_sequence 输出 """ def __init__(self, precision:str = 'high', device: str = "cuda"): self.device = device if precision=='high': self.model = MattingNetwork(VARIANT_RESNET50).eval().to(device) self.model.load_state_dict(torch.load(CHECKPOINT_RESNET50, map_location=device)) else: self.model = MattingNetwork(VARIANT_MOBILENETV3).eval().to(device) self.model.load_state_dict(torch.load(CHECKPOINT_MOBILENETV3, map_location=device)) self.model = torch.jit.script(self.model) self.model = torch.jit.freeze(self.model) @staticmethod def auto_downsample_ratio(h: int, w: int) -> float: """自动计算 downsample_ratio""" return min(512 / max(h, w), 1) def video_matting(self, input: str, bg_video: Optional[str] = None, bg_image: Optional[str] = None, output_video: Optional[str] = None, output_alpha: Optional[str] = None, output_fore: Optional[str] = None): assert any([output_video, output_alpha, output_fore]), 'Must provide at least one output.' if not os.path.exists(input): print("input file is NOT exist.") return False if bg_video and not os.path.exists(bg_video): print("input background video is NOT exist.") return False if bg_image and not os.path.exists(bg_image): print("input background image is NOT exist.") return False return self._video_matting_core( input_source = input, input_resize = None, downsample_ratio = None, output_type = 'video', background_video = bg_video, background_jpg = bg_image, with_audio = True, output_composition = output_video, output_alpha = output_alpha, output_foreground = output_fore, output_video_mbps = None, dtype =torch.float32) def _video_matting_core(self, input_source: str, input_resize: Optional[Tuple[int, int]] = None, downsample_ratio: Optional[float] = None, output_type: str = 'video', background_video: Optional[str] = None, background_jpg: Optional[str] = None, with_audio: bool = True, output_composition: Optional[str] = None, output_alpha: Optional[str] = None, output_foreground: Optional[str] = None, output_video_mbps: Optional[float] = None, dtype: Optional[torch.dtype] = torch.float32): assert downsample_ratio is None or (0 < downsample_ratio <= 1), 'Downsample ratio must be between 0 and 1.' assert any([output_composition, output_alpha, output_foreground]), 'Must provide at least one output.' assert output_type in ['video', 'png_sequence'], 'Only support video or png_sequence.' success = False # 初始化 transform if input_resize is not None: transform = transforms.Compose([ transforms.Resize(input_resize[::-1]), transforms.ToTensor() ]) else: transform = transforms.ToTensor() # 读取输入源 if os.path.isfile(input_source): source = VideoReader(input_source, transform) else: source = ImageSequenceReader(input_source, transform) reader = DataLoader(source, batch_size=1, pin_memory=True, num_workers=0) # 初始化输出 if output_type == 'video': frame_rate = source.frame_rate if isinstance(source, VideoReader) else 30 output_video_mbps = 1 if output_video_mbps is None else output_video_mbps if output_composition: if with_audio: writer_com = VideoWriter(output_composition, frame_rate, source.width,source.height,int(output_video_mbps * 1_000_000),input_source) else: writer_com = VideoWriter(output_composition, frame_rate, source.width,source.height,int(output_video_mbps * 1_000_000)) if output_alpha: writer_pha = VideoWriter(output_alpha, frame_rate, source.width,source.height,int(output_video_mbps * 1_000_000)) if output_foreground: writer_fgr = VideoWriter(output_foreground, frame_rate,source.width,source.height, int(output_video_mbps * 1_000_000)) else: # png_sequence if output_composition: writer_com = ImageSequenceWriter(output_composition, 'png') if output_alpha: writer_pha = ImageSequenceWriter(output_alpha, 'png') if output_foreground: writer_fgr = ImageSequenceWriter(output_foreground, 'png') # 背景设置 background_video_reader = None if output_composition and output_type == 'video': if background_video: background_video_reader = VideoReader(background_video, transform=transforms.ToTensor()) elif background_jpg: bgr_IMG = Image.open(background_jpg) bgr = transforms.ToTensor()(bgr_IMG).to(self.device) else: # 使用 0,200,0 而不是 0,255,0 避免过度曝光 bgr = torch.tensor([0, 200, 0], device=self.device, dtype=dtype).div(255).view(1, 1, 3, 1, 1) # 开始推理 self.model = self.model.eval() try: with torch.no_grad(): rec = [None] * 4 current_bg_idx = 1 bg_increasing = True for src in reader: if downsample_ratio is None: downsample_ratio = self.auto_downsample_ratio(*src.shape[2:]) src = src.to(self.device, dtype, non_blocking=True).unsqueeze(0) fgr, pha, *rec = self.model(src, *rec, downsample_ratio) # 前景 if output_foreground: writer_fgr.write(fgr[0]) if output_alpha: writer_pha.write(pha[0]) # 合成图 if output_composition: if output_type == 'video': if background_video: tmp_bgr = background_video_reader[current_bg_idx].to(self.device) if bg_increasing: current_bg_idx += 1 if current_bg_idx >= len(background_video_reader): current_bg_idx = len(background_video_reader) - 2 bg_increasing = False else: current_bg_idx -= 1 if current_bg_idx < 1: current_bg_idx = 1 bg_increasing = True com = fgr * pha + tmp_bgr * (1 - pha) else: com = fgr * pha + bgr * (1 - pha) else: fgr = fgr * pha.gt(0) com = torch.cat([fgr, pha], dim=-3) writer_com.write(com[0]) # process finished. success = True finally: # Clean up if output_composition is not None: writer_com.close() ''' time.sleep(0.5) # 确保文件释放,Windows 上非常必要 if success and with_audio and os.path.exists(output_composition) : out_dir = os.path.dirname(output_composition) base_name = os.path.basename(output_composition) audio_output_path = os.path.join(out_dir, f"audio_{base_name}") os.system(f'ffmpeg -i {output_composition} -i {input_source} -c copy ' f'-map 0:v:0 -map 1:a:0 {audio_output_path}') ''' if output_alpha: writer_pha.close() if output_foreground: writer_fgr.close() return success