mirror of
https://github.com/facefusion/facefusion.git
synced 2026-07-15 06:27:21 +02:00
300470e7c7
* 3.7.0 (#1175) * mark as next, introduce dynamic scale for face debugger * use latest onnxruntime * update within Gradio 5 * Remove system memory limit (#986) * remove system memory limit from ui * remove system memory limit from args.py * flatten the face store * prevent countless importlib.import_module calls * remove --onnxruntime from install.py * remove --onnxruntime from install.py * resolve static inference providers to fix macos (#1127) * resolve static inference providers to fix macos * fix lint * restore old behaviour * restore old behaviour * handle ghost and uniface as well * adjust condition for ghost and uniface * fix Gradio gallery styles * remove face store (#1132) * fix dataflow in streamer * Face selector auto mode (#1137) * introduce face selector auto mode * introduce face selector auto mode * introduce face selector auto mode * correct way is to pass source_vision_frames * make the world a better place * fix dataflow in faceswapper, no read of files withing inner methods (#1148) * fix dataflow in faceswapper, no read of files withing inner methods * fix lint * adjust code more * adjust code more * bring back the face store but for source and reference only (#1149) * bring back the face store but for source and reference only * fix ci * minor improvement * guard for tobytes() * drop condition in select_faces() * Replace CONFIG_PARSER global with @lru_cache (#1147) * remove global config_parser * fix import order * remove lambda * remove unused block * optimize app context detection * decouple common modules from core (#1152) * decouple common modules from core * remove that nonsense * remove that nonsense * minor adjustment to workflows * Tag HEVC output as hvc1 and move moov atom to the front (#1153) * Tag HEVC output as hvc1 and move moov atom to the front ffmpeg defaults HEVC in MP4 to the 'hev1' sample entry and leaves the moov atom at the tail. Apple players (QuickTime, Finder QuickLook) refuse to decode 'hev1' and stall reading a tail-placed moov on large files, so hevc_nvenc / libx265 renders cannot be previewed on macOS. - add ffmpeg_builder.set_video_tag(): emit `-tag:v hvc1` for every HEVC encoder (libx265, hevc_nvenc, hevc_amf, hevc_qsv, hevc_videotoolbox). Applied in merge_video where the encoder is known; `-c:v copy` in the audio mux / concat steps preserves the tag. - add ffmpeg_builder.set_faststart(): emit `-movflags +faststart`, applied in restore_audio / replace_audio / concat_video which write the final output. H.264 and other codecs are left untouched. Verified on a real hevc_nvenc render: hev1 hung QuickLook (no thumbnail); after the patch the file is hvc1 with a front-placed moov and QuickLook generates a thumbnail. * Restrict hvc1 tag and faststart to quicktime containers Gate set_video_tag / set_faststart on the output container format (m4v, mov, mp4) via get_file_format(), so non-quicktime muxers no longer receive -tag:v hvc1 / -movflags +faststart. Trim test_set_video_tag to a single positive and negative assertion. Addresses review on #1153. * Move hvc1 tag and faststart gates into ffmpeg_builder Rename set_video_tag / set_faststart to conditional_* and push the container-format gate (m4v, mov, mp4) inside the builders, keeping ffmpeg.py free of inline conditionals. Matches the set_image_quality pattern. Addresses review on #1153. * post cleanup after merge * Pack target frames (#1158) * pack target frames * add todos * add todos, resolve todos * resolve todos * change names * revert to single target frame for select faces * fix lint * return empty frame * get() have no default * Fix trim (#1162) * fix trim * fix trim * rename ffmpeg builder method * rename to temp_frame_set and temp_frame_pattern --------- Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com> Co-authored-by: Harisreedhar <46858047+harisreedhar@users.noreply.github.com> * Implement face tracker (#1163) * add face tracker * change get_nearest_track_face -> get_nearest_track_index * create face_creator.py and move methods around * add type FaceTrack * naming * remove iou test, don't belong there * fix spaces * rename to interpolate_points * rename to find_best_face_track * just track_faces * cleanp * previous next naming * remove >= and >= * rename * remove helper from test and use face from source.jpg * make get_anchor_indices more readable * track_faces() call before and is forwarded to select_faces * change to interpolate_faces * rename methods * rename methods * rename variables * remove dtype * move face_anlyser -> face_creator * claenup face_creator.py * move tests to dedicated test face detector * move tracking inside select_faces * simplify face_tracker (#1165) * minor renaming * improve face_tracker test (#1166) * improve face_tracker test * cleanup * Add target frame amount (#1167) * introduce --target-frame-amount * add ui * make track_faces conditional * update choices.py * fix [] * rename component file to frame_process.py * fix track preview (#1168) * introduce face origin (#1169) * add guard to prevent failure * show and hide voice extractor according to lip syncer * rename average_face_coordinates to average_face_geometry * use static faces for select_faces() * face store with lock (#1171) * face store with lock * face store with lock * remove refill color from bbox * adjust tests and handle frame_position proper way * enforce similar naming * introduce face tracker score * introduce face tracker score * fix/audio-trim-alignment (#1173) * fix audio offset * fix audio offset * remove reference_frame_number check --------- Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com> * reduce face tracker score from 0 to 0.5 * mark as 3.7.0 * make face tracker stateless --------- Co-authored-by: Harisreedhar <46858047+harisreedhar@users.noreply.github.com> Co-authored-by: kazuki nakai <kazuki.nakai@agiletec.net> Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com> * update preview * fix wording * fix wording * last minute change to frame distribution --------- Co-authored-by: Harisreedhar <46858047+harisreedhar@users.noreply.github.com> Co-authored-by: kazuki nakai <kazuki.nakai@agiletec.net> Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com>
399 lines
15 KiB
Python
399 lines
15 KiB
Python
import math
|
|
from functools import lru_cache
|
|
from typing import Dict, List, Optional, Tuple
|
|
|
|
import cv2
|
|
import numpy
|
|
from cv2.typing import Size
|
|
|
|
from facefusion.common_helper import is_windows
|
|
from facefusion.filesystem import get_file_extension, is_image, is_video
|
|
from facefusion.media_helper import restrict_trim_frame
|
|
from facefusion.thread_helper import thread_lock, thread_semaphore
|
|
from facefusion.types import ColorMode, Duration, Fps, Mask, Orientation, Resolution, Scale, VisionFrame
|
|
from facefusion.video_manager import get_video_capture
|
|
|
|
|
|
def read_static_images(image_paths : List[str], color_mode : ColorMode = 'rgb') -> List[VisionFrame]:
|
|
vision_frames = []
|
|
|
|
if image_paths:
|
|
for image_path in image_paths:
|
|
vision_frames.append(read_static_image(image_path, color_mode))
|
|
return vision_frames
|
|
|
|
|
|
@lru_cache(maxsize = 64)
|
|
def read_static_image(image_path : str, color_mode : ColorMode = 'rgb') -> Optional[VisionFrame]:
|
|
return read_image(image_path, color_mode)
|
|
|
|
|
|
def read_image(image_path : str, color_mode : ColorMode = 'rgb') -> Optional[VisionFrame]:
|
|
if is_image(image_path):
|
|
flag = cv2.IMREAD_COLOR
|
|
|
|
if color_mode == 'rgba':
|
|
flag = cv2.IMREAD_UNCHANGED
|
|
|
|
if is_windows():
|
|
image_buffer = numpy.fromfile(image_path, dtype = numpy.uint8)
|
|
return cv2.imdecode(image_buffer, flag)
|
|
return cv2.imread(image_path, flag)
|
|
return None
|
|
|
|
|
|
def write_image(image_path : str, vision_frame : VisionFrame) -> bool:
|
|
if image_path:
|
|
if is_windows():
|
|
image_file_extension = get_file_extension(image_path)
|
|
_, vision_frame = cv2.imencode(image_file_extension, vision_frame)
|
|
vision_frame.tofile(image_path)
|
|
return is_image(image_path)
|
|
return cv2.imwrite(image_path, vision_frame)
|
|
return False
|
|
|
|
|
|
def detect_image_resolution(image_path : str) -> Optional[Resolution]:
|
|
if is_image(image_path):
|
|
image = read_image(image_path)
|
|
height, width = image.shape[:2]
|
|
|
|
if width > 0 and height > 0:
|
|
return width, height
|
|
return None
|
|
|
|
|
|
def restrict_image_resolution(image_path : str, resolution : Resolution) -> Resolution:
|
|
if is_image(image_path):
|
|
image_resolution = detect_image_resolution(image_path)
|
|
if image_resolution < resolution:
|
|
return image_resolution
|
|
return resolution
|
|
|
|
|
|
@lru_cache(maxsize = 64)
|
|
def read_static_video_frame(video_path : str, frame_number : int = 0) -> Optional[VisionFrame]:
|
|
return read_video_frame(video_path, frame_number)
|
|
|
|
|
|
def read_video_frame(video_path : str, frame_number : int = 0) -> Optional[VisionFrame]:
|
|
if is_video(video_path):
|
|
video_capture = get_video_capture(video_path)
|
|
|
|
if video_capture and video_capture.isOpened():
|
|
frame_total = video_capture.get(cv2.CAP_PROP_FRAME_COUNT)
|
|
frame_position = min(frame_total, frame_number)
|
|
|
|
with thread_semaphore():
|
|
video_capture.set(cv2.CAP_PROP_POS_FRAMES, frame_position)
|
|
has_vision_frame, vision_frame = video_capture.read()
|
|
|
|
if has_vision_frame:
|
|
return vision_frame
|
|
|
|
return None
|
|
|
|
|
|
@lru_cache(maxsize = 2)
|
|
def read_static_video_chunk(video_path : str, chunk_number : int, chunk_size : int) -> Dict[int, VisionFrame]:
|
|
return read_video_chunk(video_path, chunk_number, chunk_size)
|
|
|
|
|
|
def read_video_chunk(video_path : str, chunk_number : int, chunk_size : int) -> Dict[int, VisionFrame]:
|
|
video_frame_chunk = {}
|
|
|
|
if is_video(video_path) and chunk_number > -1:
|
|
video_capture = get_video_capture(video_path)
|
|
|
|
if video_capture and video_capture.isOpened():
|
|
frame_total = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
frame_position = chunk_number * chunk_size
|
|
|
|
with thread_semaphore():
|
|
video_capture.set(cv2.CAP_PROP_POS_FRAMES, frame_position)
|
|
|
|
for frame_number in range(frame_position, min(frame_position + chunk_size, frame_total)):
|
|
has_vision_frame, vision_frame = video_capture.read()
|
|
|
|
if has_vision_frame:
|
|
video_frame_chunk[frame_number] = vision_frame
|
|
|
|
return video_frame_chunk
|
|
|
|
|
|
def select_video_frames(video_path : str, frame_number : int = 0, frame_offset : int = 5) -> List[VisionFrame]:
|
|
vision_frames = []
|
|
chunk_size = (frame_offset * 2 + 1) * 4
|
|
|
|
if is_video(video_path):
|
|
with thread_lock():
|
|
for current_number in range(frame_number - frame_offset, frame_number + frame_offset + 1):
|
|
video_frame_chunk = read_static_video_chunk(video_path, current_number // chunk_size, chunk_size)
|
|
vision_frame = create_empty_vision_frame()
|
|
|
|
if current_number in video_frame_chunk:
|
|
vision_frame = video_frame_chunk.get(current_number)
|
|
|
|
vision_frames.append(vision_frame)
|
|
|
|
return vision_frames
|
|
|
|
|
|
def count_video_frame_total(video_path : str) -> int:
|
|
if is_video(video_path):
|
|
video_capture = get_video_capture(video_path)
|
|
|
|
if video_capture and video_capture.isOpened():
|
|
with thread_semaphore():
|
|
video_frame_total = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
return video_frame_total
|
|
|
|
return 0
|
|
|
|
|
|
def predict_video_frame_total(video_path : str, fps : Fps, trim_frame_start : int, trim_frame_end : int) -> int:
|
|
if is_video(video_path):
|
|
video_fps = detect_video_fps(video_path)
|
|
trim_frame_start, trim_frame_end = restrict_trim_video_frame(video_path, trim_frame_start, trim_frame_end)
|
|
extract_frame_total = (trim_frame_end - trim_frame_start) * fps / video_fps
|
|
return math.floor(extract_frame_total)
|
|
return 0
|
|
|
|
|
|
def detect_video_fps(video_path : str) -> Optional[Fps]:
|
|
if is_video(video_path):
|
|
video_capture = get_video_capture(video_path)
|
|
|
|
if video_capture and video_capture.isOpened():
|
|
with thread_semaphore():
|
|
video_fps = video_capture.get(cv2.CAP_PROP_FPS)
|
|
return video_fps
|
|
|
|
return None
|
|
|
|
|
|
def restrict_video_fps(video_path : str, fps : Fps) -> Fps:
|
|
if is_video(video_path):
|
|
video_fps = detect_video_fps(video_path)
|
|
if video_fps < fps:
|
|
return video_fps
|
|
return fps
|
|
|
|
|
|
def detect_video_duration(video_path : str) -> Duration:
|
|
video_frame_total = count_video_frame_total(video_path)
|
|
video_fps = detect_video_fps(video_path)
|
|
|
|
if video_frame_total and video_fps:
|
|
return video_frame_total / video_fps
|
|
return 0
|
|
|
|
|
|
def restrict_trim_video_frame(video_path : str, trim_frame_start : Optional[int], trim_frame_end : Optional[int]) -> Tuple[int, int]:
|
|
video_frame_total = count_video_frame_total(video_path)
|
|
return restrict_trim_frame(video_frame_total, trim_frame_start, trim_frame_end)
|
|
|
|
|
|
def detect_video_resolution(video_path : str) -> Optional[Resolution]:
|
|
if is_video(video_path):
|
|
video_capture = get_video_capture(video_path)
|
|
|
|
if video_capture and video_capture.isOpened():
|
|
with thread_semaphore():
|
|
width = video_capture.get(cv2.CAP_PROP_FRAME_WIDTH)
|
|
height = video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT)
|
|
return int(width), int(height)
|
|
|
|
return None
|
|
|
|
|
|
def restrict_video_resolution(video_path : str, resolution : Resolution) -> Resolution:
|
|
if is_video(video_path):
|
|
video_resolution = detect_video_resolution(video_path)
|
|
if video_resolution < resolution:
|
|
return video_resolution
|
|
return resolution
|
|
|
|
|
|
def scale_resolution(resolution : Resolution, scale : Scale) -> Resolution:
|
|
resolution = (int(resolution[0] * scale), int(resolution[1] * scale))
|
|
resolution = normalize_resolution(resolution)
|
|
return resolution
|
|
|
|
|
|
def normalize_resolution(resolution : Tuple[float, float]) -> Resolution:
|
|
width, height = resolution
|
|
|
|
if width > 0 and height > 0:
|
|
normalize_width = round(width / 2) * 2
|
|
normalize_height = round(height / 2) * 2
|
|
return normalize_width, normalize_height
|
|
return 0, 0
|
|
|
|
|
|
def pack_resolution(resolution : Resolution) -> str:
|
|
width, height = normalize_resolution(resolution)
|
|
return str(width) + 'x' + str(height)
|
|
|
|
|
|
def unpack_resolution(resolution : str) -> Resolution:
|
|
width, height = map(int, resolution.split('x'))
|
|
return width, height
|
|
|
|
|
|
def detect_frame_orientation(vision_frame : VisionFrame) -> Orientation:
|
|
height, width = vision_frame.shape[:2]
|
|
|
|
if width > height:
|
|
return 'landscape'
|
|
return 'portrait'
|
|
|
|
|
|
def restrict_frame(vision_frame : VisionFrame, resolution : Resolution) -> VisionFrame:
|
|
height, width = vision_frame.shape[:2]
|
|
restrict_width, restrict_height = resolution
|
|
|
|
if height > restrict_height or width > restrict_width:
|
|
scale = min(restrict_height / height, restrict_width / width)
|
|
new_width = int(width * scale)
|
|
new_height = int(height * scale)
|
|
return cv2.resize(vision_frame, (new_width, new_height))
|
|
return vision_frame
|
|
|
|
|
|
def fit_contain_frame(vision_frame : VisionFrame, resolution : Resolution) -> VisionFrame:
|
|
contain_width, contain_height = resolution
|
|
height, width = vision_frame.shape[:2]
|
|
scale = min(contain_height / height, contain_width / width)
|
|
new_width = int(width * scale)
|
|
new_height = int(height * scale)
|
|
start_x = max(0, (contain_width - new_width) // 2)
|
|
start_y = max(0, (contain_height - new_height) // 2)
|
|
end_x = max(0, contain_width - new_width - start_x)
|
|
end_y = max(0, contain_height - new_height - start_y)
|
|
temp_vision_frame = cv2.resize(vision_frame, (new_width, new_height))
|
|
temp_vision_frame = numpy.pad(temp_vision_frame, ((start_y, end_y), (start_x, end_x), (0, 0)))
|
|
return temp_vision_frame
|
|
|
|
|
|
def fit_cover_frame(vision_frame : VisionFrame, resolution : Resolution) -> VisionFrame:
|
|
cover_width, cover_height = resolution
|
|
height, width = vision_frame.shape[:2]
|
|
scale = max(cover_width / width, cover_height / height)
|
|
new_width = int(width * scale)
|
|
new_height = int(height * scale)
|
|
start_x = max(0, (new_width - cover_width) // 2)
|
|
start_y = max(0, (new_height - cover_height) // 2)
|
|
end_x = min(new_width, start_x + cover_width)
|
|
end_y = min(new_height, start_y + cover_height)
|
|
temp_vision_frame = cv2.resize(vision_frame, (new_width, new_height))
|
|
temp_vision_frame = temp_vision_frame[start_y:end_y, start_x:end_x]
|
|
return temp_vision_frame
|
|
|
|
|
|
def obscure_frame(vision_frame : VisionFrame) -> VisionFrame:
|
|
return cv2.GaussianBlur(vision_frame, (99, 99), 0)
|
|
|
|
|
|
def blend_frame(source_vision_frame : VisionFrame, target_vision_frame : VisionFrame, blend_factor : float) -> VisionFrame:
|
|
blend_vision_frame = cv2.addWeighted(source_vision_frame, 1 - blend_factor, target_vision_frame, blend_factor, 0)
|
|
return blend_vision_frame
|
|
|
|
|
|
def conditional_match_frame_color(source_vision_frame : VisionFrame, target_vision_frame : VisionFrame) -> VisionFrame:
|
|
histogram_factor = calculate_histogram_difference(source_vision_frame, target_vision_frame)
|
|
target_vision_frame = blend_frame(target_vision_frame, match_frame_color(source_vision_frame, target_vision_frame), histogram_factor)
|
|
return target_vision_frame
|
|
|
|
|
|
def match_frame_color(source_vision_frame : VisionFrame, target_vision_frame : VisionFrame) -> VisionFrame:
|
|
color_difference_sizes = numpy.linspace(16, target_vision_frame.shape[0], 3, endpoint = False)
|
|
|
|
for color_difference_size in color_difference_sizes:
|
|
source_vision_frame = equalize_frame_color(source_vision_frame, target_vision_frame, normalize_resolution((color_difference_size, color_difference_size)))
|
|
target_vision_frame = equalize_frame_color(source_vision_frame, target_vision_frame, target_vision_frame.shape[:2][::-1])
|
|
return target_vision_frame
|
|
|
|
|
|
def equalize_frame_color(source_vision_frame : VisionFrame, target_vision_frame : VisionFrame, size : Size) -> VisionFrame:
|
|
source_frame_resize = cv2.resize(source_vision_frame, size, interpolation = cv2.INTER_AREA).astype(numpy.float32)
|
|
target_frame_resize = cv2.resize(target_vision_frame, size, interpolation = cv2.INTER_AREA).astype(numpy.float32)
|
|
color_difference_vision_frame = numpy.subtract(source_frame_resize, target_frame_resize)
|
|
color_difference_vision_frame = cv2.resize(color_difference_vision_frame, target_vision_frame.shape[:2][::-1], interpolation = cv2.INTER_CUBIC)
|
|
target_vision_frame = numpy.add(target_vision_frame, color_difference_vision_frame).clip(0, 255).astype(numpy.uint8)
|
|
return target_vision_frame
|
|
|
|
|
|
def calculate_histogram_difference(source_vision_frame : VisionFrame, target_vision_frame : VisionFrame) -> float:
|
|
histogram_source = cv2.calcHist([cv2.cvtColor(source_vision_frame, cv2.COLOR_BGR2HSV)], [ 0, 1 ], None, [ 50, 60 ], [ 0, 180, 0, 256 ])
|
|
histogram_target = cv2.calcHist([cv2.cvtColor(target_vision_frame, cv2.COLOR_BGR2HSV)], [ 0, 1 ], None, [ 50, 60 ], [ 0, 180, 0, 256 ])
|
|
histogram_difference = float(numpy.interp(cv2.compareHist(histogram_source, histogram_target, cv2.HISTCMP_CORREL), [ -1, 1 ], [ 0, 1 ]))
|
|
return histogram_difference
|
|
|
|
|
|
def blend_vision_frames(source_vision_frame : VisionFrame, target_vision_frame : VisionFrame, blend_factor : float) -> VisionFrame:
|
|
blend_vision_frame = cv2.addWeighted(source_vision_frame, 1 - blend_factor, target_vision_frame, blend_factor, 0)
|
|
return blend_vision_frame
|
|
|
|
|
|
def create_empty_vision_frame() -> VisionFrame:
|
|
return numpy.zeros((1, 1, 3)).astype(numpy.uint8)
|
|
|
|
|
|
def create_tile_frames(vision_frame : VisionFrame, size : Size) -> Tuple[List[VisionFrame], int, int]:
|
|
tile_width = size[0] - 2 * size[2]
|
|
pad_size_top = size[1] + size[2]
|
|
pad_size_bottom = pad_size_top + tile_width - (vision_frame.shape[0] + 2 * size[1]) % tile_width
|
|
pad_size_right = pad_size_top + tile_width - (vision_frame.shape[1] + 2 * size[1]) % tile_width
|
|
pad_vision_frame = numpy.pad(vision_frame, ((pad_size_top, pad_size_bottom), (pad_size_top, pad_size_right), (0, 0)))
|
|
pad_height, pad_width = pad_vision_frame.shape[:2]
|
|
row_range = range(size[2], pad_height - size[2], tile_width)
|
|
col_range = range(size[2], pad_width - size[2], tile_width)
|
|
tile_vision_frames = []
|
|
|
|
for row_vision_frame in row_range:
|
|
top = row_vision_frame - size[2]
|
|
bottom = row_vision_frame + size[2] + tile_width
|
|
|
|
for column_vision_frame in col_range:
|
|
left = column_vision_frame - size[2]
|
|
right = column_vision_frame + size[2] + tile_width
|
|
tile_vision_frames.append(pad_vision_frame[top:bottom, left:right, :])
|
|
|
|
return tile_vision_frames, pad_width, pad_height
|
|
|
|
|
|
def merge_tile_frames(tile_vision_frames : List[VisionFrame], temp_width : int, temp_height : int, pad_width : int, pad_height : int, size : Size) -> VisionFrame:
|
|
merge_vision_frame = numpy.zeros((pad_height, pad_width, 3)).astype(numpy.uint8)
|
|
tile_width = tile_vision_frames[0].shape[1] - 2 * size[2]
|
|
tiles_per_row = min(pad_width // tile_width, len(tile_vision_frames))
|
|
|
|
for index, tile_vision_frame in enumerate(tile_vision_frames):
|
|
tile_vision_frame = tile_vision_frame[size[2]:-size[2], size[2]:-size[2]]
|
|
row_index = index // tiles_per_row
|
|
col_index = index % tiles_per_row
|
|
top = row_index * tile_vision_frame.shape[0]
|
|
bottom = top + tile_vision_frame.shape[0]
|
|
left = col_index * tile_vision_frame.shape[1]
|
|
right = left + tile_vision_frame.shape[1]
|
|
merge_vision_frame[top:bottom, left:right, :] = tile_vision_frame
|
|
|
|
merge_vision_frame = merge_vision_frame[size[1] : size[1] + temp_height, size[1]: size[1] + temp_width, :]
|
|
return merge_vision_frame
|
|
|
|
|
|
def extract_vision_mask(vision_frame : VisionFrame) -> Mask:
|
|
if vision_frame.ndim == 3 and vision_frame.shape[2] == 4:
|
|
return vision_frame[:, :, 3]
|
|
return numpy.full(vision_frame.shape[:2], 255, dtype = numpy.uint8)
|
|
|
|
|
|
def merge_vision_mask(vision_frame : VisionFrame, vision_mask : Mask) -> VisionFrame:
|
|
return numpy.dstack((vision_frame[:, :, :3], vision_mask))
|
|
|
|
|
|
def conditional_merge_vision_mask(vision_frame : VisionFrame, vision_mask : Mask) -> VisionFrame:
|
|
if numpy.any(vision_mask < 255):
|
|
return merge_vision_mask(vision_frame, vision_mask)
|
|
return vision_frame
|