diff --git a/.gitignore b/.gitignore index 7ba87de..94f11f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ __pycache__/ out/ -result.mp4 \ No newline at end of file +videos/ +FP_Res/ +result.mp4 +*.pth \ No newline at end of file diff --git a/FloweR/model.py b/FloweR/model.py new file mode 100644 index 0000000..48eeb4a --- /dev/null +++ b/FloweR/model.py @@ -0,0 +1,150 @@ +import torch +import torch.nn as nn +import torch.functional as F + +class View(nn.Module): + def __init__(self, *shape): + super(View, self).__init__() + self.shape = shape + def forward(self, input): + return input.view(*self.shape) + +# Define the model +class FloweR(nn.Module): + def __init__(self, input_size = (384, 384), window_size = 4): + super(FloweR, self).__init__() + + self.input_size = input_size + self.window_size = window_size + + #INPUT: 384 x 384 x 10 * 3 + + ### DOWNSCALE ### + self.conv_block_1 = nn.Sequential( + nn.Conv2d(3 * self.window_size, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 384 x 384 x 128 + + self.conv_block_2 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 192 x 192 x 128 + + self.conv_block_3 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 96 x 96 x 128 + + self.conv_block_4 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 48 x 48 x 128 + + self.conv_block_5 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 24 x 24 x 128 + + self.conv_block_6 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 12 x 12 x 128 + + self.conv_block_7 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 6 x 6 x 128 + + self.conv_block_8 = nn.Sequential( + nn.AvgPool2d(2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 3 x 3 x 128 + + ### UPSCALE ### + self.conv_block_9 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 6 x 6 x 128 + + self.conv_block_10 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 12 x 12 x 128 + + self.conv_block_11 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 24 x 24 x 128 + + self.conv_block_12 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 48 x 48 x 128 + + self.conv_block_13 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 96 x 96 x 128 + + self.conv_block_14 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 192 x 192 x 128 + + self.conv_block_15 = nn.Sequential( + nn.Upsample(scale_factor=2), + nn.Conv2d(128, 128, kernel_size=3, stride=1, padding='same'), + nn.ReLU(), + ) # 384 x 384 x 128 + + self.conv_block_16 = nn.Conv2d(128, 3, kernel_size=3, stride=1, padding='same') + + def forward(self, x): + if x.size(1) != self.window_size: + raise Exception(f'Shape of the input is not compatable. There should be exactly {self.window_size} frames in an input video.') + + # batch, frames, height, width, colors + in_x = x.permute((0, 1, 4, 2, 3)) + # batch, frames, colors, height, width + + in_x = in_x.reshape(-1, self.window_size * 3, self.input_size[0], self.input_size[1]) + + ### DOWNSCALE ### + block_1_out = self.conv_block_1(in_x) # 384 x 384 x 128 + block_2_out = self.conv_block_2(block_1_out) # 192 x 192 x 128 + block_3_out = self.conv_block_3(block_2_out) # 96 x 96 x 128 + block_4_out = self.conv_block_4(block_3_out) # 48 x 48 x 128 + block_5_out = self.conv_block_5(block_4_out) # 24 x 24 x 128 + block_6_out = self.conv_block_6(block_5_out) # 12 x 12 x 128 + block_7_out = self.conv_block_7(block_6_out) # 6 x 6 x 128 + block_8_out = self.conv_block_8(block_7_out) # 3 x 3 x 128 + + ### UPSCALE ### + block_9_out = block_7_out + self.conv_block_9(block_8_out) # 6 x 6 x 128 + block_10_out = block_6_out + self.conv_block_10(block_9_out) # 12 x 12 x 128 + block_11_out = block_5_out + self.conv_block_11(block_10_out) # 24 x 24 x 128 + block_12_out = block_4_out + self.conv_block_12(block_11_out) # 48 x 48 x 128 + block_13_out = block_3_out + self.conv_block_13(block_12_out) # 96 x 96 x 128 + block_14_out = block_2_out + self.conv_block_14(block_13_out) # 192 x 192 x 128 + block_15_out = block_1_out + self.conv_block_15(block_14_out) # 384 x 384 x 128 + + block_16_out = self.conv_block_16(block_15_out) # 384 x 384 x (2 + 1) + out = block_16_out.reshape(-1, 3, self.input_size[0], self.input_size[1]) + + # batch, colors, height, width + out = out.permute((0, 2, 3, 1)) + # batch, height, width, colors + return out \ No newline at end of file diff --git a/examples/bonefire_1.mp4 b/examples/bonefire_1.mp4 new file mode 100644 index 0000000..ec7724a Binary files /dev/null and b/examples/bonefire_1.mp4 differ diff --git a/examples/bonfire_1.gif b/examples/bonfire_1.gif new file mode 100644 index 0000000..d7f6a5e Binary files /dev/null and b/examples/bonfire_1.gif differ diff --git a/examples/diamond_4.gif b/examples/diamond_4.gif new file mode 100644 index 0000000..f7a63c3 Binary files /dev/null and b/examples/diamond_4.gif differ diff --git a/examples/diamond_4.mp4 b/examples/diamond_4.mp4 new file mode 100644 index 0000000..39c8f43 Binary files /dev/null and b/examples/diamond_4.mp4 differ diff --git a/examples/flower_1.gif b/examples/flower_1.gif new file mode 100644 index 0000000..6cd9caf Binary files /dev/null and b/examples/flower_1.gif differ diff --git a/examples/flower_1.mp4 b/examples/flower_1.mp4 new file mode 100644 index 0000000..5dffc7a Binary files /dev/null and b/examples/flower_1.mp4 differ diff --git a/examples/flower_11.mp4 b/examples/flower_11.mp4 new file mode 100644 index 0000000..68700ce Binary files /dev/null and b/examples/flower_11.mp4 differ diff --git a/examples/gold_1.gif b/examples/gold_1.gif new file mode 100644 index 0000000..4897e3b Binary files /dev/null and b/examples/gold_1.gif differ diff --git a/examples/gold_1.mp4 b/examples/gold_1.mp4 new file mode 100644 index 0000000..c79a511 Binary files /dev/null and b/examples/gold_1.mp4 differ diff --git a/examples/macaroni_1.gif b/examples/macaroni_1.gif new file mode 100644 index 0000000..fd03a80 Binary files /dev/null and b/examples/macaroni_1.gif differ diff --git a/examples/macaroni_1.mp4 b/examples/macaroni_1.mp4 new file mode 100644 index 0000000..d722f41 Binary files /dev/null and b/examples/macaroni_1.mp4 differ diff --git a/examples/tree_2.gif b/examples/tree_2.gif new file mode 100644 index 0000000..0e3d39c Binary files /dev/null and b/examples/tree_2.gif differ diff --git a/examples/tree_2.mp4 b/examples/tree_2.mp4 new file mode 100644 index 0000000..2892243 Binary files /dev/null and b/examples/tree_2.mp4 differ diff --git a/flow_utils.py b/flow_utils.py index 0985702..5f0ad05 100644 --- a/flow_utils.py +++ b/flow_utils.py @@ -57,12 +57,21 @@ def compute_diff_map(next_flow, prev_flow, prev_frame, cur_frame, prev_frame_sty next_flow = cv2.resize(next_flow, (w, h)) prev_flow = cv2.resize(prev_flow, (w, h)) - flow_map = -next_flow.copy() + # This is not correct. The flow map should be applied to the next frame to get previous frame + # flow_map = -next_flow.copy() + + # remove white noise (@alexfredo suggestion) + next_flow[np.abs(next_flow) < 3] = 0 + prev_flow[np.abs(prev_flow) < 3] = 0 + + # Here is the correct version + flow_map = prev_flow.copy() + flow_map[:,:,0] += np.arange(w) flow_map[:,:,1] += np.arange(h)[:,np.newaxis] - warped_frame = cv2.remap(prev_frame, flow_map, None, cv2.INTER_NEAREST) - warped_frame_styled = cv2.remap(prev_frame_styled, flow_map, None, cv2.INTER_NEAREST) + warped_frame = cv2.remap(prev_frame, flow_map, None, cv2.INTER_NEAREST, borderMode = cv2.BORDER_REFLECT) + warped_frame_styled = cv2.remap(prev_frame_styled, flow_map, None, cv2.INTER_NEAREST, borderMode = cv2.BORDER_REFLECT) # compute occlusion mask fb_flow = next_flow + prev_flow @@ -80,8 +89,19 @@ def compute_diff_map(next_flow, prev_flow, prev_frame, cur_frame, prev_frame_sty alpha_mask = alpha_mask.repeat(3, axis = -1) #alpha_mask_blured = cv2.dilate(alpha_mask, np.ones((5, 5), np.float32)) - alpha_mask = cv2.GaussianBlur(alpha_mask, (51,51), 5, cv2.BORDER_DEFAULT) + alpha_mask = cv2.GaussianBlur(alpha_mask, (51,51), 5, cv2.BORDER_REFLECT) alpha_mask = np.clip(alpha_mask, 0, 1) - return alpha_mask, warped_frame_styled \ No newline at end of file + return alpha_mask, warped_frame_styled + + +def frames_norm(occl): return occl / 127.5 - 1 + +def flow_norm(flow): return flow / 255 + +def occl_norm(occl): return occl / 127.5 - 1 + +def flow_renorm(flow): return flow * 255 + +def occl_renorm(occl): return (occl + 1) * 127.5 diff --git a/readme.md b/readme.md index f7e9302..48b2ef1 100644 --- a/readme.md +++ b/readme.md @@ -8,42 +8,94 @@ This script can also be using to swap the person in the video like in this examp ## Dependencies To install all the necessary dependencies, run this command: ``` -pip install opencv-python opencv-contrib-python numpy tqdm h5py +pip install opencv-python opencv-contrib-python numpy tqdm h5py scikit-image ``` You have to set up the RAFT repository as it described here: https://github.com/princeton-vl/RAFT . Basically it just comes down to running "./download_models.sh" in RAFT folder to download the models. -## Running the script +## Running the scripts This script works on top of [Automatic1111/web-ui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) interface via API. To run this script you have to set it up first. You should also have[sd-webui-controlnet](https://github.com/Mikubill/sd-webui-controlnet) extension installed. You need to have control_hed-fp16 model installed. If you have web-ui with ControlNet working correctly, you have to also allow API to work with controlNet. To do so, go to the web-ui settings -> ControlNet tab -> Set "Allow other script to control this extension" checkbox to active and set "Multi ControlNet: Max models amount (requires restart)" to more then 2 -> press "Apply settings". -### Step 1. +### Video To Video +#### Step 1. To process the video, first of all you would need to precompute optical flow data before running web-ui with this command: ``` -python3 compute_flow.py -i {path to your video} -o {path to output *.h5} -v -W {width of the flow map} -H {height of the flow map} +python3 compute_flow.py -i "path to your video" -o "path to output file with *.h5 format" -v -W width_of_the_flow_map -H height_of_the_flow_map ``` The main reason to do this step separately is to save precious GPU memory that will be useful to generate better quality images. Choose W and H parameters as high as your GPU can handle with respect to proportion of original video resolution. Do not worry if it higher or less then the processing resolution, flow maps will be scaled accordingly at the processing stage. This will generate quite a large file that may take up to a several gigabytes on the drive even for minute long video. If you want to process a long video consider splitting it into several parts beforehand. -### Step 2. +#### Step 2. Run web-ui with '--api' flag. It is also better to use '--xformers' flag, as you would need to have the highest resolution possible and using xformers memory optimization will greatly help. ``` bash webui.sh --xformers --api ``` -### Step 3. +#### Step 3. Go to the **vid2vid.py** file and change main parameters (INPUT_VIDEO, FLOW_MAPS, OUTPUT_VIDEO, PROMPT, N_PROMPT, W, H) to the ones you need for your project. FLOW_MAPS parameter should contain a path to the flow file that you generated at the first step. The script is pretty simple so you may change other parameters as well, although I would recommend to leave them as is for the first time. Finally run the script with the command: ``` python3 vid2vid.py ``` +### Text To Video (prototype) +This method is still in development and works on top of ‘Stable Diffusion’ and 'FloweR' - optical flow reconstruction method that is also in a yearly development stage. Do not expect much from it as it is more of a proof of a concept rather than a complete solution. All examples you can see here are originally generated at 512x512 resolution using the 'sd-v1-5-inpainting' model as a base. They were downsized and compressed for better loading speed. You can see them in their original quality in the 'examples' folder. Actual prompts used were stated in the following format "RAW photo, {subject}, 8k uhd, dslr, soft lighting, high quality, film grain, Fujifilm XT3", only the 'subject' part is described in the table below. + +#### Step 1. +Download 'FloweR_0.1.pth' model from here: [google disc link] and place it in the 'FloweR' folder. + +#### Step 2. +Same as with vid2vid case, run web-ui with '--api' flag. It is also better to use '--xformers' flag, as you would need to have the highest resolution possible and using xformers memory optimization will greatly help. +``` +bash webui.sh --xformers --api +``` + +#### Step 3. +Go to the **txt2vid.py** file and change main parameters (OUTPUT_VIDEO, PROMPT, N_PROMPT, W, H) to the ones you need for your project. Again, the script is simple so you may change other parameters if you want to. Finally run the script with the command: +``` +python3 txt2vid.py +``` + +#### Examples: + +
![]() |
+ ![]() |
+ ![]() |
+
| "close up of a flower" | +"bonfire near the camp in the mountains at night" | +"close up of a diamond laying on the table" | +
![]() |
+ ![]() |
+ ![]() |
+
| "close up of macaroni on the plate" | +"close up of golden sphere" | +"a tree standing in the winter forest" | +