AlekseyCalvin commited on
Commit
402d0ff
·
verified ·
1 Parent(s): 38bd6d9

Create utils.py

Browse files
Files changed (1) hide show
  1. utils.py +158 -0
utils.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+ import numexpr
4
+ import re
5
+ from PIL import Image, ImageOps
6
+
7
+ # --- Math & Schedule Parsing ---
8
+
9
+ def get_inbetweens(key_frames, max_frames, integer=False):
10
+ """Interpolates values between keyframes (simple linear for now, but robust)."""
11
+ key_frames = dict(sorted(key_frames.items()))
12
+ keys = list(key_frames.keys())
13
+ vals = list(key_frames.values())
14
+
15
+ # Fill array
16
+ series = np.linspace(vals[0], vals[0], max_frames)
17
+ for i in range(len(keys)-1):
18
+ idx_start, idx_end = keys[i], keys[i+1]
19
+ val_start, val_end = vals[i], vals[i+1]
20
+ if idx_end > max_frames: idx_end = max_frames
21
+
22
+ # Linear interpolation
23
+ range_len = idx_end - idx_start
24
+ if range_len > 0:
25
+ segment = np.linspace(val_start, val_end, range_len, endpoint=False)
26
+ series[idx_start:idx_end] = segment
27
+
28
+ # Fill tail
29
+ if keys[-1] < max_frames:
30
+ series[keys[-1]:] = vals[-1]
31
+
32
+ return series.astype(int) if integer else series
33
+
34
+ def parse_weight_string(string, max_frames):
35
+ """
36
+ Parses '0:(0.5), 10:(1.0)' including Math like '0:(sin(t/10))'.
37
+ Returns a numpy array of float values for every frame.
38
+ """
39
+ # Clean string
40
+ string = re.sub(r'\s+', '', string)
41
+ keyframes = {}
42
+
43
+ # Split by comma, respecting parentheses might be needed in complex regex,
44
+ # but simple split usually works for Deforum format
45
+ parts = string.split(',')
46
+
47
+ for part in parts:
48
+ try:
49
+ frame_str, val_str = part.split(':')
50
+ frame = int(frame_str)
51
+ # Remove parentheses
52
+ val_str = val_str.strip('()')
53
+ keyframes[frame] = val_str
54
+ except:
55
+ continue
56
+
57
+ if 0 not in keyframes:
58
+ keyframes[0] = "0"
59
+
60
+ sorted_frames = sorted(keyframes.keys())
61
+ series = np.zeros(max_frames)
62
+
63
+ # Evaluate math for every frame
64
+ for i in range(len(sorted_frames)):
65
+ f_start = sorted_frames[i]
66
+ f_end = sorted_frames[i+1] if i < len(sorted_frames)-1 else max_frames
67
+
68
+ formula = keyframes[f_start]
69
+
70
+ for f in range(f_start, f_end):
71
+ t = f # Deforum standard variable
72
+ try:
73
+ # Safe evaluation environment
74
+ val = numexpr.evaluate(formula, local_dict={'t': t, 's': f_start, 'pi': np.pi, 'sin': np.sin, 'cos': np.cos, 'tan': np.tan})
75
+ series[f] = float(val)
76
+ except Exception as e:
77
+ # If static value or fail
78
+ try:
79
+ series[f] = float(formula)
80
+ except:
81
+ series[f] = series[f-1] if f > 0 else 0.0
82
+
83
+ return series
84
+
85
+ # --- Image Processing ---
86
+
87
+ def maintain_colors(prev_img, color_match_sample):
88
+ """
89
+ Matches the coloring of the previous frame (or frame 0) to prevent color drift.
90
+ Uses LAB color space transfer.
91
+ """
92
+ prev_img_np = np.array(prev_img).astype(np.uint8)
93
+ sample_np = np.array(color_match_sample).astype(np.uint8)
94
+
95
+ prev_lab = cv2.cvtColor(prev_img_np, cv2.COLOR_RGB2LAB)
96
+ sample_lab = cv2.cvtColor(sample_np, cv2.COLOR_RGB2LAB)
97
+
98
+ l_avg_p, a_avg_p, b_avg_p = np.mean(prev_lab[:,:,0]), np.mean(prev_lab[:,:,1]), np.mean(prev_lab[:,:,2])
99
+ l_avg_s, a_avg_s, b_avg_s = np.mean(sample_lab[:,:,0]), np.mean(sample_lab[:,:,1]), np.mean(sample_lab[:,:,2])
100
+
101
+ l, a, b = cv2.split(prev_lab)
102
+
103
+ # Shift current image logic towards sample mean
104
+ # Note: Deforum usually matches the NEW generation to the OLD image.
105
+ # Here we are adjusting the image we just warped (prev) to match the original anchor?
106
+ # Actually, standard Deforum 'Match Frame 0' means we force the init image to look like Frame 0 colors.
107
+
108
+ l = l - l_avg_p + l_avg_s
109
+ a = a - a_avg_p + a_avg_s
110
+ b = b - b_avg_p + b_avg_s
111
+
112
+ l = np.clip(l, 0, 255)
113
+ a = np.clip(a, 0, 255)
114
+ b = np.clip(b, 0, 255)
115
+
116
+ matched_lab = cv2.merge([l.astype(np.uint8), a.astype(np.uint8), b.astype(np.uint8)])
117
+ return Image.fromarray(cv2.cvtColor(matched_lab, cv2.COLOR_LAB2RGB))
118
+
119
+ def add_noise(img, noise_amt):
120
+ """Adds uniform noise to the image to give the diffusion model texture to latch onto."""
121
+ if noise_amt <= 0: return img
122
+
123
+ img_np = np.array(img).astype(np.float32)
124
+ noise = np.random.normal(0, noise_amt * 255, img_np.shape).astype(np.float32)
125
+ noisy_img = np.clip(img_np + noise, 0, 255).astype(np.uint8)
126
+ return Image.fromarray(noisy_img)
127
+
128
+ def anim_frame_warp_2d(prev_img_pil, args_dict):
129
+ """
130
+ Standard Deforum 2D Warping.
131
+ args_dict must contain: angle, zoom, translation_x, translation_y
132
+ """
133
+ cv2_img = np.array(prev_img_pil)
134
+ height, width = cv2_img.shape[:2]
135
+
136
+ center = (width // 2, height // 2)
137
+
138
+ # Rotation & Zoom
139
+ angle = args_dict.get('angle', 0)
140
+ zoom = args_dict.get('zoom', 1.0)
141
+
142
+ trans_mat = cv2.getRotationMatrix2D(center, angle, zoom)
143
+
144
+ # Translation
145
+ tx = args_dict.get('translation_x', 0)
146
+ ty = args_dict.get('translation_y', 0)
147
+ trans_mat[0, 2] += tx
148
+ trans_mat[1, 2] += ty
149
+
150
+ # Warp with Reflection to handle edges naturally
151
+ warped = cv2.warpAffine(
152
+ cv2_img,
153
+ trans_mat,
154
+ (width, height),
155
+ borderMode=cv2.BORDER_REFLECT_101
156
+ )
157
+
158
+ return Image.fromarray(warped)