Spaces:
Runtime error
Runtime error
| import numpy as np | |
| import cv2 | |
| import numexpr | |
| import re | |
| from PIL import Image, ImageOps | |
| # --- Math & Schedule Parsing --- | |
| def get_inbetweens(key_frames, max_frames, integer=False): | |
| """Interpolates values between keyframes (simple linear for now, but robust).""" | |
| key_frames = dict(sorted(key_frames.items())) | |
| keys = list(key_frames.keys()) | |
| vals = list(key_frames.values()) | |
| # Fill array | |
| series = np.linspace(vals[0], vals[0], max_frames) | |
| for i in range(len(keys)-1): | |
| idx_start, idx_end = keys[i], keys[i+1] | |
| val_start, val_end = vals[i], vals[i+1] | |
| if idx_end > max_frames: idx_end = max_frames | |
| # Linear interpolation | |
| range_len = idx_end - idx_start | |
| if range_len > 0: | |
| segment = np.linspace(val_start, val_end, range_len, endpoint=False) | |
| series[idx_start:idx_end] = segment | |
| # Fill tail | |
| if keys[-1] < max_frames: | |
| series[keys[-1]:] = vals[-1] | |
| return series.astype(int) if integer else series | |
| def parse_weight_string(string, max_frames): | |
| """ | |
| Parses '0:(0.5), 10:(1.0)' including Math like '0:(sin(t/10))'. | |
| Returns a numpy array of float values for every frame. | |
| """ | |
| # Clean string | |
| string = re.sub(r'\s+', '', string) | |
| keyframes = {} | |
| # Split by comma, respecting parentheses might be needed in complex regex, | |
| # but simple split usually works for Deforum format | |
| parts = string.split(',') | |
| for part in parts: | |
| try: | |
| frame_str, val_str = part.split(':') | |
| frame = int(frame_str) | |
| # Remove parentheses | |
| val_str = val_str.strip('()') | |
| keyframes[frame] = val_str | |
| except: | |
| continue | |
| if 0 not in keyframes: | |
| keyframes[0] = "0" | |
| sorted_frames = sorted(keyframes.keys()) | |
| series = np.zeros(max_frames) | |
| # Evaluate math for every frame | |
| for i in range(len(sorted_frames)): | |
| f_start = sorted_frames[i] | |
| f_end = sorted_frames[i+1] if i < len(sorted_frames)-1 else max_frames | |
| formula = keyframes[f_start] | |
| for f in range(f_start, f_end): | |
| t = f # Deforum standard variable | |
| try: | |
| # Safe evaluation environment | |
| val = numexpr.evaluate(formula, local_dict={'t': t, 's': f_start, 'pi': np.pi, 'sin': np.sin, 'cos': np.cos, 'tan': np.tan}) | |
| series[f] = float(val) | |
| except Exception as e: | |
| # If static value or fail | |
| try: | |
| series[f] = float(formula) | |
| except: | |
| series[f] = series[f-1] if f > 0 else 0.0 | |
| return series | |
| # --- Image Processing --- | |
| def maintain_colors(prev_img, color_match_sample): | |
| """ | |
| Matches the coloring of the previous frame (or frame 0) to prevent color drift. | |
| Uses LAB color space transfer. | |
| """ | |
| prev_img_np = np.array(prev_img).astype(np.uint8) | |
| sample_np = np.array(color_match_sample).astype(np.uint8) | |
| prev_lab = cv2.cvtColor(prev_img_np, cv2.COLOR_RGB2LAB) | |
| sample_lab = cv2.cvtColor(sample_np, cv2.COLOR_RGB2LAB) | |
| l_avg_p, a_avg_p, b_avg_p = np.mean(prev_lab[:,:,0]), np.mean(prev_lab[:,:,1]), np.mean(prev_lab[:,:,2]) | |
| l_avg_s, a_avg_s, b_avg_s = np.mean(sample_lab[:,:,0]), np.mean(sample_lab[:,:,1]), np.mean(sample_lab[:,:,2]) | |
| l, a, b = cv2.split(prev_lab) | |
| # Shift current image logic towards sample mean | |
| # Note: Deforum usually matches the NEW generation to the OLD image. | |
| # Here we are adjusting the image we just warped (prev) to match the original anchor? | |
| # Actually, standard Deforum 'Match Frame 0' means we force the init image to look like Frame 0 colors. | |
| l = l - l_avg_p + l_avg_s | |
| a = a - a_avg_p + a_avg_s | |
| b = b - b_avg_p + b_avg_s | |
| l = np.clip(l, 0, 255) | |
| a = np.clip(a, 0, 255) | |
| b = np.clip(b, 0, 255) | |
| matched_lab = cv2.merge([l.astype(np.uint8), a.astype(np.uint8), b.astype(np.uint8)]) | |
| return Image.fromarray(cv2.cvtColor(matched_lab, cv2.COLOR_LAB2RGB)) | |
| def add_noise(img, noise_amt): | |
| """Adds uniform noise to the image to give the diffusion model texture to latch onto.""" | |
| if noise_amt <= 0: return img | |
| img_np = np.array(img).astype(np.float32) | |
| noise = np.random.normal(0, noise_amt * 255, img_np.shape).astype(np.float32) | |
| noisy_img = np.clip(img_np + noise, 0, 255).astype(np.uint8) | |
| return Image.fromarray(noisy_img) | |
| def anim_frame_warp_2d(prev_img_pil, args_dict): | |
| """ | |
| Standard Deforum 2D Warping. | |
| args_dict must contain: angle, zoom, translation_x, translation_y | |
| """ | |
| cv2_img = np.array(prev_img_pil) | |
| height, width = cv2_img.shape[:2] | |
| center = (width // 2, height // 2) | |
| # Rotation & Zoom | |
| angle = args_dict.get('angle', 0) | |
| zoom = args_dict.get('zoom', 1.0) | |
| trans_mat = cv2.getRotationMatrix2D(center, angle, zoom) | |
| # Translation | |
| tx = args_dict.get('translation_x', 0) | |
| ty = args_dict.get('translation_y', 0) | |
| trans_mat[0, 2] += tx | |
| trans_mat[1, 2] += ty | |
| # Warp with Reflection to handle edges naturally | |
| warped = cv2.warpAffine( | |
| cv2_img, | |
| trans_mat, | |
| (width, height), | |
| borderMode=cv2.BORDER_REFLECT_101 | |
| ) | |
| return Image.fromarray(warped) |