Spaces:
Runtime error
Runtime error
Commit
•
e4c9e3b
0
Parent(s):
Duplicate from Wazzzabeee/image-video-colorization
Browse filesCo-authored-by: Clement Delteil <Wazzzabeee@users.noreply.huggingface.co>
- .gitattributes +34 -0
- .streamlit/config.toml +8 -0
- 01_📼_Upload_Video_File.py +141 -0
- README.md +13 -0
- models/deep_colorization/colorizers/__init__.py +6 -0
- models/deep_colorization/colorizers/__pycache__/__init__.cpython-310.pyc +0 -0
- models/deep_colorization/colorizers/__pycache__/__init__.cpython-37.pyc +0 -0
- models/deep_colorization/colorizers/__pycache__/base_color.cpython-310.pyc +0 -0
- models/deep_colorization/colorizers/__pycache__/base_color.cpython-37.pyc +0 -0
- models/deep_colorization/colorizers/__pycache__/eccv16.cpython-310.pyc +0 -0
- models/deep_colorization/colorizers/__pycache__/eccv16.cpython-37.pyc +0 -0
- models/deep_colorization/colorizers/__pycache__/siggraph17.cpython-310.pyc +0 -0
- models/deep_colorization/colorizers/__pycache__/siggraph17.cpython-37.pyc +0 -0
- models/deep_colorization/colorizers/__pycache__/util.cpython-310.pyc +0 -0
- models/deep_colorization/colorizers/__pycache__/util.cpython-37.pyc +0 -0
- models/deep_colorization/colorizers/base_color.py +24 -0
- models/deep_colorization/colorizers/eccv16.py +105 -0
- models/deep_colorization/colorizers/siggraph17.py +168 -0
- models/deep_colorization/colorizers/util.py +47 -0
- pages/02_🎥_Input_Youtube_Link.py +138 -0
- pages/03_🖼️_Input_Images.py +111 -0
- requirements.txt +12 -0
- utils.py +67 -0
.gitattributes
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.streamlit/config.toml
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[theme]
|
2 |
+
primaryColor="#F63366"
|
3 |
+
backgroundColor="#FFFFFF"
|
4 |
+
secondaryBackgroundColor="#F0F2F6"
|
5 |
+
textColor="#262730"
|
6 |
+
font="sans serif"
|
7 |
+
[server]
|
8 |
+
maxUploadSize=1028
|
01_📼_Upload_Video_File.py
ADDED
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import tempfile
|
3 |
+
import time
|
4 |
+
|
5 |
+
import cv2
|
6 |
+
import moviepy.editor as mp
|
7 |
+
import numpy as np
|
8 |
+
import streamlit as st
|
9 |
+
from streamlit_lottie import st_lottie
|
10 |
+
from tqdm import tqdm
|
11 |
+
|
12 |
+
from models.deep_colorization.colorizers import eccv16
|
13 |
+
from utils import load_lottieurl, format_time, colorize_frame, change_model
|
14 |
+
|
15 |
+
st.set_page_config(page_title="Image & Video Colorizer", page_icon="🎨", layout="wide")
|
16 |
+
|
17 |
+
loaded_model = eccv16(pretrained=True).eval()
|
18 |
+
current_model = "None"
|
19 |
+
|
20 |
+
col1, col2 = st.columns([1, 3])
|
21 |
+
with col1:
|
22 |
+
lottie = load_lottieurl("https://assets5.lottiefiles.com/packages/lf20_RHdEuzVfEL.json")
|
23 |
+
st_lottie(lottie)
|
24 |
+
|
25 |
+
with col2:
|
26 |
+
st.write("""
|
27 |
+
## B&W Videos Colorizer
|
28 |
+
##### Upload a black and white video and get a colorized version of it.
|
29 |
+
###### ➠ This space is using CPU Basic so it might take a while to colorize a video.
|
30 |
+
###### ➠ If you want more models and GPU available please support this space by donating.""")
|
31 |
+
|
32 |
+
|
33 |
+
def main():
|
34 |
+
model = st.selectbox(
|
35 |
+
"Select Model (Both models have their pros and cons, I recommend trying both and keeping the best for your task)",
|
36 |
+
["ECCV16", "SIGGRAPH17"], index=0)
|
37 |
+
|
38 |
+
loaded_model = change_model(current_model, model)
|
39 |
+
st.write(f"Model is now {model}")
|
40 |
+
|
41 |
+
uploaded_file = st.file_uploader("Upload your video here...", type=['mp4', 'mov', 'avi', 'mkv'])
|
42 |
+
|
43 |
+
if st.button("Colorize"):
|
44 |
+
if uploaded_file is not None:
|
45 |
+
file_extension = os.path.splitext(uploaded_file.name)[1].lower()
|
46 |
+
if file_extension in ['.mp4', '.avi', '.mov', '.mkv']:
|
47 |
+
# Save the video file to a temporary location
|
48 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
49 |
+
temp_file.write(uploaded_file.read())
|
50 |
+
|
51 |
+
audio = mp.AudioFileClip(temp_file.name)
|
52 |
+
|
53 |
+
# Open the video using cv2.VideoCapture
|
54 |
+
video = cv2.VideoCapture(temp_file.name)
|
55 |
+
|
56 |
+
# Get video information
|
57 |
+
fps = video.get(cv2.CAP_PROP_FPS)
|
58 |
+
|
59 |
+
col1, col2 = st.columns([0.5, 0.5])
|
60 |
+
with col1:
|
61 |
+
st.markdown('<p style="text-align: center;">Before</p>', unsafe_allow_html=True)
|
62 |
+
st.video(temp_file.name)
|
63 |
+
|
64 |
+
with col2:
|
65 |
+
st.markdown('<p style="text-align: center;">After</p>', unsafe_allow_html=True)
|
66 |
+
|
67 |
+
with st.spinner("Colorizing frames..."):
|
68 |
+
# Colorize video frames and store in a list
|
69 |
+
output_frames = []
|
70 |
+
total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
71 |
+
progress_bar = st.progress(0) # Create a progress bar
|
72 |
+
|
73 |
+
start_time = time.time()
|
74 |
+
time_text = st.text("Time Remaining: ") # Initialize text value
|
75 |
+
|
76 |
+
for _ in tqdm(range(total_frames), unit='frame', desc="Progress"):
|
77 |
+
ret, frame = video.read()
|
78 |
+
if not ret:
|
79 |
+
break
|
80 |
+
|
81 |
+
colorized_frame = colorize_frame(frame, loaded_model)
|
82 |
+
output_frames.append((colorized_frame * 255).astype(np.uint8))
|
83 |
+
|
84 |
+
elapsed_time = time.time() - start_time
|
85 |
+
frames_completed = len(output_frames)
|
86 |
+
frames_remaining = total_frames - frames_completed
|
87 |
+
time_remaining = (frames_remaining / frames_completed) * elapsed_time
|
88 |
+
|
89 |
+
progress_bar.progress(frames_completed / total_frames) # Update progress bar
|
90 |
+
|
91 |
+
if frames_completed < total_frames:
|
92 |
+
time_text.text(f"Time Remaining: {format_time(time_remaining)}") # Update text value
|
93 |
+
else:
|
94 |
+
time_text.empty() # Remove text value
|
95 |
+
progress_bar.empty()
|
96 |
+
|
97 |
+
with st.spinner("Merging frames to video..."):
|
98 |
+
frame_size = output_frames[0].shape[:2]
|
99 |
+
output_filename = "output.mp4"
|
100 |
+
fourcc = cv2.VideoWriter_fourcc(*"mp4v") # Codec for MP4 video
|
101 |
+
out = cv2.VideoWriter(output_filename, fourcc, fps, (frame_size[1], frame_size[0]))
|
102 |
+
|
103 |
+
# Display the colorized video using st.video
|
104 |
+
for frame in output_frames:
|
105 |
+
frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
|
106 |
+
|
107 |
+
out.write(frame_bgr)
|
108 |
+
|
109 |
+
out.release()
|
110 |
+
|
111 |
+
# Convert the output video to a format compatible with Streamlit
|
112 |
+
converted_filename = "converted_output.mp4"
|
113 |
+
clip = mp.VideoFileClip(output_filename)
|
114 |
+
clip = clip.set_audio(audio)
|
115 |
+
|
116 |
+
clip.write_videofile(converted_filename, codec="libx264")
|
117 |
+
|
118 |
+
# Display the converted video using st.video()
|
119 |
+
st.video(converted_filename)
|
120 |
+
st.balloons()
|
121 |
+
|
122 |
+
# Add a download button for the colorized video
|
123 |
+
st.download_button(
|
124 |
+
label="Download Colorized Video",
|
125 |
+
data=open(converted_filename, "rb").read(),
|
126 |
+
file_name="colorized_video.mp4"
|
127 |
+
)
|
128 |
+
|
129 |
+
# Close and delete the temporary file after processing
|
130 |
+
video.release()
|
131 |
+
temp_file.close()
|
132 |
+
|
133 |
+
|
134 |
+
if __name__ == "__main__":
|
135 |
+
main()
|
136 |
+
st.markdown(
|
137 |
+
"###### Made with :heart: by [Clément Delteil](https://www.linkedin.com/in/clementdelteil/) [![this is an "
|
138 |
+
"image link](https://i.imgur.com/thJhzOO.png)](https://www.buymeacoffee.com/clementdelteil)")
|
139 |
+
st.markdown(
|
140 |
+
"###### [Blog post of the project](https://medium.com/geekculture/creating-a-web-app-to-colorize-images-and-youtube-videos-80f5be2d0f68)"
|
141 |
+
)
|
README.md
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
---
|
2 |
+
title: Image Video Colorization
|
3 |
+
emoji: 🎥
|
4 |
+
colorFrom: blue
|
5 |
+
colorTo: purple
|
6 |
+
sdk: streamlit
|
7 |
+
sdk_version: 1.21.0
|
8 |
+
app_file: 01_📼_Upload_Video_File.py
|
9 |
+
pinned: false
|
10 |
+
duplicated_from: Wazzzabeee/image-video-colorization
|
11 |
+
---
|
12 |
+
|
13 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
models/deep_colorization/colorizers/__init__.py
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
from .base_color import *
|
3 |
+
from .eccv16 import *
|
4 |
+
from .siggraph17 import *
|
5 |
+
from .util import *
|
6 |
+
|
models/deep_colorization/colorizers/__pycache__/__init__.cpython-310.pyc
ADDED
Binary file (279 Bytes). View file
|
|
models/deep_colorization/colorizers/__pycache__/__init__.cpython-37.pyc
ADDED
Binary file (285 Bytes). View file
|
|
models/deep_colorization/colorizers/__pycache__/base_color.cpython-310.pyc
ADDED
Binary file (1.24 kB). View file
|
|
models/deep_colorization/colorizers/__pycache__/base_color.cpython-37.pyc
ADDED
Binary file (1.24 kB). View file
|
|
models/deep_colorization/colorizers/__pycache__/eccv16.cpython-310.pyc
ADDED
Binary file (3.27 kB). View file
|
|
models/deep_colorization/colorizers/__pycache__/eccv16.cpython-37.pyc
ADDED
Binary file (3.26 kB). View file
|
|
models/deep_colorization/colorizers/__pycache__/siggraph17.cpython-310.pyc
ADDED
Binary file (4.36 kB). View file
|
|
models/deep_colorization/colorizers/__pycache__/siggraph17.cpython-37.pyc
ADDED
Binary file (4.36 kB). View file
|
|
models/deep_colorization/colorizers/__pycache__/util.cpython-310.pyc
ADDED
Binary file (1.74 kB). View file
|
|
models/deep_colorization/colorizers/__pycache__/util.cpython-37.pyc
ADDED
Binary file (1.71 kB). View file
|
|
models/deep_colorization/colorizers/base_color.py
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import torch
|
3 |
+
from torch import nn
|
4 |
+
|
5 |
+
class BaseColor(nn.Module):
|
6 |
+
def __init__(self):
|
7 |
+
super(BaseColor, self).__init__()
|
8 |
+
|
9 |
+
self.l_cent = 50.
|
10 |
+
self.l_norm = 100.
|
11 |
+
self.ab_norm = 110.
|
12 |
+
|
13 |
+
def normalize_l(self, in_l):
|
14 |
+
return (in_l-self.l_cent)/self.l_norm
|
15 |
+
|
16 |
+
def unnormalize_l(self, in_l):
|
17 |
+
return in_l*self.l_norm + self.l_cent
|
18 |
+
|
19 |
+
def normalize_ab(self, in_ab):
|
20 |
+
return in_ab/self.ab_norm
|
21 |
+
|
22 |
+
def unnormalize_ab(self, in_ab):
|
23 |
+
return in_ab*self.ab_norm
|
24 |
+
|
models/deep_colorization/colorizers/eccv16.py
ADDED
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import torch
|
3 |
+
import torch.nn as nn
|
4 |
+
import numpy as np
|
5 |
+
from IPython import embed
|
6 |
+
|
7 |
+
from .base_color import *
|
8 |
+
|
9 |
+
class ECCVGenerator(BaseColor):
|
10 |
+
def __init__(self, norm_layer=nn.BatchNorm2d):
|
11 |
+
super(ECCVGenerator, self).__init__()
|
12 |
+
|
13 |
+
model1=[nn.Conv2d(1, 64, kernel_size=3, stride=1, padding=1, bias=True),]
|
14 |
+
model1+=[nn.ReLU(True),]
|
15 |
+
model1+=[nn.Conv2d(64, 64, kernel_size=3, stride=2, padding=1, bias=True),]
|
16 |
+
model1+=[nn.ReLU(True),]
|
17 |
+
model1+=[norm_layer(64),]
|
18 |
+
|
19 |
+
model2=[nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1, bias=True),]
|
20 |
+
model2+=[nn.ReLU(True),]
|
21 |
+
model2+=[nn.Conv2d(128, 128, kernel_size=3, stride=2, padding=1, bias=True),]
|
22 |
+
model2+=[nn.ReLU(True),]
|
23 |
+
model2+=[norm_layer(128),]
|
24 |
+
|
25 |
+
model3=[nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1, bias=True),]
|
26 |
+
model3+=[nn.ReLU(True),]
|
27 |
+
model3+=[nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=True),]
|
28 |
+
model3+=[nn.ReLU(True),]
|
29 |
+
model3+=[nn.Conv2d(256, 256, kernel_size=3, stride=2, padding=1, bias=True),]
|
30 |
+
model3+=[nn.ReLU(True),]
|
31 |
+
model3+=[norm_layer(256),]
|
32 |
+
|
33 |
+
model4=[nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1, bias=True),]
|
34 |
+
model4+=[nn.ReLU(True),]
|
35 |
+
model4+=[nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1, bias=True),]
|
36 |
+
model4+=[nn.ReLU(True),]
|
37 |
+
model4+=[nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1, bias=True),]
|
38 |
+
model4+=[nn.ReLU(True),]
|
39 |
+
model4+=[norm_layer(512),]
|
40 |
+
|
41 |
+
model5=[nn.Conv2d(512, 512, kernel_size=3, dilation=2, stride=1, padding=2, bias=True),]
|
42 |
+
model5+=[nn.ReLU(True),]
|
43 |
+
model5+=[nn.Conv2d(512, 512, kernel_size=3, dilation=2, stride=1, padding=2, bias=True),]
|
44 |
+
model5+=[nn.ReLU(True),]
|
45 |
+
model5+=[nn.Conv2d(512, 512, kernel_size=3, dilation=2, stride=1, padding=2, bias=True),]
|
46 |
+
model5+=[nn.ReLU(True),]
|
47 |
+
model5+=[norm_layer(512),]
|
48 |
+
|
49 |
+
model6=[nn.Conv2d(512, 512, kernel_size=3, dilation=2, stride=1, padding=2, bias=True),]
|
50 |
+
model6+=[nn.ReLU(True),]
|
51 |
+
model6+=[nn.Conv2d(512, 512, kernel_size=3, dilation=2, stride=1, padding=2, bias=True),]
|
52 |
+
model6+=[nn.ReLU(True),]
|
53 |
+
model6+=[nn.Conv2d(512, 512, kernel_size=3, dilation=2, stride=1, padding=2, bias=True),]
|
54 |
+
model6+=[nn.ReLU(True),]
|
55 |
+
model6+=[norm_layer(512),]
|
56 |
+
|
57 |
+
model7=[nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1, bias=True),]
|
58 |
+
model7+=[nn.ReLU(True),]
|
59 |
+
model7+=[nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1, bias=True),]
|
60 |
+
model7+=[nn.ReLU(True),]
|
61 |
+
model7+=[nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1, bias=True),]
|
62 |
+
model7+=[nn.ReLU(True),]
|
63 |
+
model7+=[norm_layer(512),]
|
64 |
+
|
65 |
+
model8=[nn.ConvTranspose2d(512, 256, kernel_size=4, stride=2, padding=1, bias=True),]
|
66 |
+
model8+=[nn.ReLU(True),]
|
67 |
+
model8+=[nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=True),]
|
68 |
+
model8+=[nn.ReLU(True),]
|
69 |
+
model8+=[nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=True),]
|
70 |
+
model8+=[nn.ReLU(True),]
|
71 |
+
|
72 |
+
model8+=[nn.Conv2d(256, 313, kernel_size=1, stride=1, padding=0, bias=True),]
|
73 |
+
|
74 |
+
self.model1 = nn.Sequential(*model1)
|
75 |
+
self.model2 = nn.Sequential(*model2)
|
76 |
+
self.model3 = nn.Sequential(*model3)
|
77 |
+
self.model4 = nn.Sequential(*model4)
|
78 |
+
self.model5 = nn.Sequential(*model5)
|
79 |
+
self.model6 = nn.Sequential(*model6)
|
80 |
+
self.model7 = nn.Sequential(*model7)
|
81 |
+
self.model8 = nn.Sequential(*model8)
|
82 |
+
|
83 |
+
self.softmax = nn.Softmax(dim=1)
|
84 |
+
self.model_out = nn.Conv2d(313, 2, kernel_size=1, padding=0, dilation=1, stride=1, bias=False)
|
85 |
+
self.upsample4 = nn.Upsample(scale_factor=4, mode='bilinear')
|
86 |
+
|
87 |
+
def forward(self, input_l):
|
88 |
+
conv1_2 = self.model1(self.normalize_l(input_l))
|
89 |
+
conv2_2 = self.model2(conv1_2)
|
90 |
+
conv3_3 = self.model3(conv2_2)
|
91 |
+
conv4_3 = self.model4(conv3_3)
|
92 |
+
conv5_3 = self.model5(conv4_3)
|
93 |
+
conv6_3 = self.model6(conv5_3)
|
94 |
+
conv7_3 = self.model7(conv6_3)
|
95 |
+
conv8_3 = self.model8(conv7_3)
|
96 |
+
out_reg = self.model_out(self.softmax(conv8_3))
|
97 |
+
|
98 |
+
return self.unnormalize_ab(self.upsample4(out_reg))
|
99 |
+
|
100 |
+
def eccv16(pretrained=True):
|
101 |
+
model = ECCVGenerator()
|
102 |
+
if(pretrained):
|
103 |
+
import torch.utils.model_zoo as model_zoo
|
104 |
+
model.load_state_dict(model_zoo.load_url('https://colorizers.s3.us-east-2.amazonaws.com/colorization_release_v2-9b330a0b.pth',map_location='cpu',check_hash=True))
|
105 |
+
return model
|
models/deep_colorization/colorizers/siggraph17.py
ADDED
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import torch.nn as nn
|
3 |
+
|
4 |
+
from .base_color import *
|
5 |
+
|
6 |
+
class SIGGRAPHGenerator(BaseColor):
|
7 |
+
def __init__(self, norm_layer=nn.BatchNorm2d, classes=529):
|
8 |
+
super(SIGGRAPHGenerator, self).__init__()
|
9 |
+
|
10 |
+
# Conv1
|
11 |
+
model1=[nn.Conv2d(4, 64, kernel_size=3, stride=1, padding=1, bias=True),]
|
12 |
+
model1+=[nn.ReLU(True),]
|
13 |
+
model1+=[nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1, bias=True),]
|
14 |
+
model1+=[nn.ReLU(True),]
|
15 |
+
model1+=[norm_layer(64),]
|
16 |
+
# add a subsampling operation
|
17 |
+
|
18 |
+
# Conv2
|
19 |
+
model2=[nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1, bias=True),]
|
20 |
+
model2+=[nn.ReLU(True),]
|
21 |
+
model2+=[nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1, bias=True),]
|
22 |
+
model2+=[nn.ReLU(True),]
|
23 |
+
model2+=[norm_layer(128),]
|
24 |
+
# add a subsampling layer operation
|
25 |
+
|
26 |
+
# Conv3
|
27 |
+
model3=[nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1, bias=True),]
|
28 |
+
model3+=[nn.ReLU(True),]
|
29 |
+
model3+=[nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=True),]
|
30 |
+
model3+=[nn.ReLU(True),]
|
31 |
+
model3+=[nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=True),]
|
32 |
+
model3+=[nn.ReLU(True),]
|
33 |
+
model3+=[norm_layer(256),]
|
34 |
+
# add a subsampling layer operation
|
35 |
+
|
36 |
+
# Conv4
|
37 |
+
model4=[nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1, bias=True),]
|
38 |
+
model4+=[nn.ReLU(True),]
|
39 |
+
model4+=[nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1, bias=True),]
|
40 |
+
model4+=[nn.ReLU(True),]
|
41 |
+
model4+=[nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1, bias=True),]
|
42 |
+
model4+=[nn.ReLU(True),]
|
43 |
+
model4+=[norm_layer(512),]
|
44 |
+
|
45 |
+
# Conv5
|
46 |
+
model5=[nn.Conv2d(512, 512, kernel_size=3, dilation=2, stride=1, padding=2, bias=True),]
|
47 |
+
model5+=[nn.ReLU(True),]
|
48 |
+
model5+=[nn.Conv2d(512, 512, kernel_size=3, dilation=2, stride=1, padding=2, bias=True),]
|
49 |
+
model5+=[nn.ReLU(True),]
|
50 |
+
model5+=[nn.Conv2d(512, 512, kernel_size=3, dilation=2, stride=1, padding=2, bias=True),]
|
51 |
+
model5+=[nn.ReLU(True),]
|
52 |
+
model5+=[norm_layer(512),]
|
53 |
+
|
54 |
+
# Conv6
|
55 |
+
model6=[nn.Conv2d(512, 512, kernel_size=3, dilation=2, stride=1, padding=2, bias=True),]
|
56 |
+
model6+=[nn.ReLU(True),]
|
57 |
+
model6+=[nn.Conv2d(512, 512, kernel_size=3, dilation=2, stride=1, padding=2, bias=True),]
|
58 |
+
model6+=[nn.ReLU(True),]
|
59 |
+
model6+=[nn.Conv2d(512, 512, kernel_size=3, dilation=2, stride=1, padding=2, bias=True),]
|
60 |
+
model6+=[nn.ReLU(True),]
|
61 |
+
model6+=[norm_layer(512),]
|
62 |
+
|
63 |
+
# Conv7
|
64 |
+
model7=[nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1, bias=True),]
|
65 |
+
model7+=[nn.ReLU(True),]
|
66 |
+
model7+=[nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1, bias=True),]
|
67 |
+
model7+=[nn.ReLU(True),]
|
68 |
+
model7+=[nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1, bias=True),]
|
69 |
+
model7+=[nn.ReLU(True),]
|
70 |
+
model7+=[norm_layer(512),]
|
71 |
+
|
72 |
+
# Conv7
|
73 |
+
model8up=[nn.ConvTranspose2d(512, 256, kernel_size=4, stride=2, padding=1, bias=True)]
|
74 |
+
model3short8=[nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=True),]
|
75 |
+
|
76 |
+
model8=[nn.ReLU(True),]
|
77 |
+
model8+=[nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=True),]
|
78 |
+
model8+=[nn.ReLU(True),]
|
79 |
+
model8+=[nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1, bias=True),]
|
80 |
+
model8+=[nn.ReLU(True),]
|
81 |
+
model8+=[norm_layer(256),]
|
82 |
+
|
83 |
+
# Conv9
|
84 |
+
model9up=[nn.ConvTranspose2d(256, 128, kernel_size=4, stride=2, padding=1, bias=True),]
|
85 |
+
model2short9=[nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1, bias=True),]
|
86 |
+
# add the two feature maps above
|
87 |
+
|
88 |
+
model9=[nn.ReLU(True),]
|
89 |
+
model9+=[nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1, bias=True),]
|
90 |
+
model9+=[nn.ReLU(True),]
|
91 |
+
model9+=[norm_layer(128),]
|
92 |
+
|
93 |
+
# Conv10
|
94 |
+
model10up=[nn.ConvTranspose2d(128, 128, kernel_size=4, stride=2, padding=1, bias=True),]
|
95 |
+
model1short10=[nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1, bias=True),]
|
96 |
+
# add the two feature maps above
|
97 |
+
|
98 |
+
model10=[nn.ReLU(True),]
|
99 |
+
model10+=[nn.Conv2d(128, 128, kernel_size=3, dilation=1, stride=1, padding=1, bias=True),]
|
100 |
+
model10+=[nn.LeakyReLU(negative_slope=.2),]
|
101 |
+
|
102 |
+
# classification output
|
103 |
+
model_class=[nn.Conv2d(256, classes, kernel_size=1, padding=0, dilation=1, stride=1, bias=True),]
|
104 |
+
|
105 |
+
# regression output
|
106 |
+
model_out=[nn.Conv2d(128, 2, kernel_size=1, padding=0, dilation=1, stride=1, bias=True),]
|
107 |
+
model_out+=[nn.Tanh()]
|
108 |
+
|
109 |
+
self.model1 = nn.Sequential(*model1)
|
110 |
+
self.model2 = nn.Sequential(*model2)
|
111 |
+
self.model3 = nn.Sequential(*model3)
|
112 |
+
self.model4 = nn.Sequential(*model4)
|
113 |
+
self.model5 = nn.Sequential(*model5)
|
114 |
+
self.model6 = nn.Sequential(*model6)
|
115 |
+
self.model7 = nn.Sequential(*model7)
|
116 |
+
self.model8up = nn.Sequential(*model8up)
|
117 |
+
self.model8 = nn.Sequential(*model8)
|
118 |
+
self.model9up = nn.Sequential(*model9up)
|
119 |
+
self.model9 = nn.Sequential(*model9)
|
120 |
+
self.model10up = nn.Sequential(*model10up)
|
121 |
+
self.model10 = nn.Sequential(*model10)
|
122 |
+
self.model3short8 = nn.Sequential(*model3short8)
|
123 |
+
self.model2short9 = nn.Sequential(*model2short9)
|
124 |
+
self.model1short10 = nn.Sequential(*model1short10)
|
125 |
+
|
126 |
+
self.model_class = nn.Sequential(*model_class)
|
127 |
+
self.model_out = nn.Sequential(*model_out)
|
128 |
+
|
129 |
+
self.upsample4 = nn.Sequential(*[nn.Upsample(scale_factor=4, mode='bilinear'),])
|
130 |
+
self.softmax = nn.Sequential(*[nn.Softmax(dim=1),])
|
131 |
+
|
132 |
+
def forward(self, input_A, input_B=None, mask_B=None):
|
133 |
+
if(input_B is None):
|
134 |
+
input_B = torch.cat((input_A*0, input_A*0), dim=1)
|
135 |
+
if(mask_B is None):
|
136 |
+
mask_B = input_A*0
|
137 |
+
|
138 |
+
conv1_2 = self.model1(torch.cat((self.normalize_l(input_A),self.normalize_ab(input_B),mask_B),dim=1))
|
139 |
+
conv2_2 = self.model2(conv1_2[:,:,::2,::2])
|
140 |
+
conv3_3 = self.model3(conv2_2[:,:,::2,::2])
|
141 |
+
conv4_3 = self.model4(conv3_3[:,:,::2,::2])
|
142 |
+
conv5_3 = self.model5(conv4_3)
|
143 |
+
conv6_3 = self.model6(conv5_3)
|
144 |
+
conv7_3 = self.model7(conv6_3)
|
145 |
+
|
146 |
+
conv8_up = self.model8up(conv7_3) + self.model3short8(conv3_3)
|
147 |
+
conv8_3 = self.model8(conv8_up)
|
148 |
+
conv9_up = self.model9up(conv8_3) + self.model2short9(conv2_2)
|
149 |
+
conv9_3 = self.model9(conv9_up)
|
150 |
+
conv10_up = self.model10up(conv9_3) + self.model1short10(conv1_2)
|
151 |
+
conv10_2 = self.model10(conv10_up)
|
152 |
+
out_reg = self.model_out(conv10_2)
|
153 |
+
|
154 |
+
conv9_up = self.model9up(conv8_3) + self.model2short9(conv2_2)
|
155 |
+
conv9_3 = self.model9(conv9_up)
|
156 |
+
conv10_up = self.model10up(conv9_3) + self.model1short10(conv1_2)
|
157 |
+
conv10_2 = self.model10(conv10_up)
|
158 |
+
out_reg = self.model_out(conv10_2)
|
159 |
+
|
160 |
+
return self.unnormalize_ab(out_reg)
|
161 |
+
|
162 |
+
def siggraph17(pretrained=True):
|
163 |
+
model = SIGGRAPHGenerator()
|
164 |
+
if(pretrained):
|
165 |
+
import torch.utils.model_zoo as model_zoo
|
166 |
+
model.load_state_dict(model_zoo.load_url('https://colorizers.s3.us-east-2.amazonaws.com/siggraph17-df00044c.pth',map_location='cpu',check_hash=True))
|
167 |
+
return model
|
168 |
+
|
models/deep_colorization/colorizers/util.py
ADDED
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
from PIL import Image
|
3 |
+
import numpy as np
|
4 |
+
from skimage import color
|
5 |
+
import torch
|
6 |
+
import torch.nn.functional as F
|
7 |
+
from IPython import embed
|
8 |
+
|
9 |
+
def load_img(img_path):
|
10 |
+
out_np = np.asarray(Image.open(img_path))
|
11 |
+
if(out_np.ndim==2):
|
12 |
+
out_np = np.tile(out_np[:,:,None],3)
|
13 |
+
return out_np
|
14 |
+
|
15 |
+
def resize_img(img, HW=(256,256), resample=3):
|
16 |
+
return np.asarray(Image.fromarray(img).resize((HW[1],HW[0]), resample=resample))
|
17 |
+
|
18 |
+
def preprocess_img(img_rgb_orig, HW=(256,256), resample=3):
|
19 |
+
# return original size L and resized L as torch Tensors
|
20 |
+
img_rgb_rs = resize_img(img_rgb_orig, HW=HW, resample=resample)
|
21 |
+
|
22 |
+
img_lab_orig = color.rgb2lab(img_rgb_orig)
|
23 |
+
img_lab_rs = color.rgb2lab(img_rgb_rs)
|
24 |
+
|
25 |
+
img_l_orig = img_lab_orig[:,:,0]
|
26 |
+
img_l_rs = img_lab_rs[:,:,0]
|
27 |
+
|
28 |
+
tens_orig_l = torch.Tensor(img_l_orig)[None,None,:,:]
|
29 |
+
tens_rs_l = torch.Tensor(img_l_rs)[None,None,:,:]
|
30 |
+
|
31 |
+
return (tens_orig_l, tens_rs_l)
|
32 |
+
|
33 |
+
def postprocess_tens(tens_orig_l, out_ab, mode='bilinear'):
|
34 |
+
# tens_orig_l 1 x 1 x H_orig x W_orig
|
35 |
+
# out_ab 1 x 2 x H x W
|
36 |
+
|
37 |
+
HW_orig = tens_orig_l.shape[2:]
|
38 |
+
HW = out_ab.shape[2:]
|
39 |
+
|
40 |
+
# call resize function if needed
|
41 |
+
if(HW_orig[0]!=HW[0] or HW_orig[1]!=HW[1]):
|
42 |
+
out_ab_orig = F.interpolate(out_ab, size=HW_orig, mode='bilinear')
|
43 |
+
else:
|
44 |
+
out_ab_orig = out_ab
|
45 |
+
|
46 |
+
out_lab_orig = torch.cat((tens_orig_l, out_ab_orig), dim=1)
|
47 |
+
return color.lab2rgb(out_lab_orig.data.cpu().numpy()[0,...].transpose((1,2,0)))
|
pages/02_🎥_Input_Youtube_Link.py
ADDED
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
|
3 |
+
import cv2
|
4 |
+
import moviepy.editor as mp
|
5 |
+
import numpy as np
|
6 |
+
import streamlit as st
|
7 |
+
from pytube import YouTube
|
8 |
+
from streamlit_lottie import st_lottie
|
9 |
+
from tqdm import tqdm
|
10 |
+
|
11 |
+
from models.deep_colorization.colorizers import eccv16
|
12 |
+
from utils import colorize_frame, format_time
|
13 |
+
from utils import load_lottieurl, change_model
|
14 |
+
|
15 |
+
st.set_page_config(page_title="Image & Video Colorizer", page_icon="🎨", layout="wide")
|
16 |
+
|
17 |
+
|
18 |
+
loaded_model = eccv16(pretrained=True).eval()
|
19 |
+
current_model = "None"
|
20 |
+
|
21 |
+
|
22 |
+
col1, col2 = st.columns([1, 3])
|
23 |
+
with col1:
|
24 |
+
lottie = load_lottieurl("https://assets5.lottiefiles.com/packages/lf20_RHdEuzVfEL.json")
|
25 |
+
st_lottie(lottie)
|
26 |
+
|
27 |
+
with col2:
|
28 |
+
st.write("""
|
29 |
+
## B&W Videos Colorizer
|
30 |
+
##### Input a YouTube black and white video link and get a colorized version of it.
|
31 |
+
###### ➠ This space is using CPU Basic so it might take a while to colorize a video.
|
32 |
+
###### ➠ If you want more models and GPU available please support this space by donating.""")
|
33 |
+
|
34 |
+
|
35 |
+
@st.cache_data()
|
36 |
+
def download_video(link):
|
37 |
+
yt = YouTube(link)
|
38 |
+
video = yt.streams.filter(progressive=True, file_extension='mp4').order_by('resolution').desc().first().download(filename="video.mp4")
|
39 |
+
return video
|
40 |
+
|
41 |
+
|
42 |
+
def main():
|
43 |
+
model = st.selectbox(
|
44 |
+
"Select Model (Both models have their pros and cons, I recommend trying both and keeping the best for you task)",
|
45 |
+
["ECCV16", "SIGGRAPH17"], index=0)
|
46 |
+
|
47 |
+
loaded_model = change_model(current_model, model)
|
48 |
+
st.write(f"Model is now {model}")
|
49 |
+
|
50 |
+
link = st.text_input("YouTube Link (The longer the video, the longer the processing time)")
|
51 |
+
if st.button("Colorize"):
|
52 |
+
yt_video = download_video(link)
|
53 |
+
print(yt_video)
|
54 |
+
col1, col2 = st.columns([0.5, 0.5])
|
55 |
+
with col1:
|
56 |
+
st.markdown('<p style="text-align: center;">Before</p>', unsafe_allow_html=True)
|
57 |
+
st.video(yt_video)
|
58 |
+
with col2:
|
59 |
+
st.markdown('<p style="text-align: center;">After</p>', unsafe_allow_html=True)
|
60 |
+
with st.spinner("Colorizing frames..."):
|
61 |
+
# Colorize video frames and store in a list
|
62 |
+
output_frames = []
|
63 |
+
|
64 |
+
audio = mp.AudioFileClip("video.mp4")
|
65 |
+
video = cv2.VideoCapture("video.mp4")
|
66 |
+
|
67 |
+
total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
68 |
+
fps = video.get(cv2.CAP_PROP_FPS)
|
69 |
+
|
70 |
+
progress_bar = st.progress(0) # Create a progress bar
|
71 |
+
start_time = time.time()
|
72 |
+
time_text = st.text("Time Remaining: ") # Initialize text value
|
73 |
+
|
74 |
+
for _ in tqdm(range(total_frames), unit='frame', desc="Progress"):
|
75 |
+
ret, frame = video.read()
|
76 |
+
if not ret:
|
77 |
+
break
|
78 |
+
|
79 |
+
colorized_frame = colorize_frame(frame, loaded_model)
|
80 |
+
output_frames.append((colorized_frame * 255).astype(np.uint8))
|
81 |
+
|
82 |
+
elapsed_time = time.time() - start_time
|
83 |
+
frames_completed = len(output_frames)
|
84 |
+
frames_remaining = total_frames - frames_completed
|
85 |
+
time_remaining = (frames_remaining / frames_completed) * elapsed_time
|
86 |
+
|
87 |
+
progress_bar.progress(frames_completed / total_frames) # Update progress bar
|
88 |
+
|
89 |
+
if frames_completed < total_frames:
|
90 |
+
time_text.text(f"Time Remaining: {format_time(time_remaining)}") # Update text value
|
91 |
+
else:
|
92 |
+
time_text.empty() # Remove text value
|
93 |
+
progress_bar.empty()
|
94 |
+
|
95 |
+
with st.spinner("Merging frames to video..."):
|
96 |
+
frame_size = output_frames[0].shape[:2]
|
97 |
+
output_filename = "output.mp4"
|
98 |
+
fourcc = cv2.VideoWriter_fourcc(*"mp4v") # Codec for MP4 video
|
99 |
+
out = cv2.VideoWriter(output_filename, fourcc, fps, (frame_size[1], frame_size[0]))
|
100 |
+
|
101 |
+
# Display the colorized video using st.video
|
102 |
+
for frame in output_frames:
|
103 |
+
frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
|
104 |
+
|
105 |
+
out.write(frame_bgr)
|
106 |
+
|
107 |
+
out.release()
|
108 |
+
|
109 |
+
# Convert the output video to a format compatible with Streamlit
|
110 |
+
converted_filename = "converted_output.mp4"
|
111 |
+
clip = mp.VideoFileClip(output_filename)
|
112 |
+
clip = clip.set_audio(audio)
|
113 |
+
|
114 |
+
clip.write_videofile(converted_filename, codec="libx264")
|
115 |
+
|
116 |
+
# Display the converted video using st.video()
|
117 |
+
st.video(converted_filename)
|
118 |
+
st.balloons()
|
119 |
+
|
120 |
+
# Add a download button for the colorized video
|
121 |
+
st.download_button(
|
122 |
+
label="Download Colorized Video",
|
123 |
+
data=open(converted_filename, "rb").read(),
|
124 |
+
file_name="colorized_video.mp4"
|
125 |
+
)
|
126 |
+
|
127 |
+
# Close and delete the temporary file after processing
|
128 |
+
video.release()
|
129 |
+
|
130 |
+
|
131 |
+
if __name__ == "__main__":
|
132 |
+
main()
|
133 |
+
st.markdown(
|
134 |
+
"###### Made with :heart: by [Clément Delteil](https://www.linkedin.com/in/clementdelteil/) [![this is an "
|
135 |
+
"image link](https://i.imgur.com/thJhzOO.png)](https://www.buymeacoffee.com/clementdelteil)")
|
136 |
+
st.markdown(
|
137 |
+
"###### [Blog post of the project](https://medium.com/geekculture/creating-a-web-app-to-colorize-images-and-youtube-videos-80f5be2d0f68)"
|
138 |
+
)
|
pages/03_🖼️_Input_Images.py
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import zipfile
|
3 |
+
|
4 |
+
import streamlit as st
|
5 |
+
from PIL import Image
|
6 |
+
from streamlit_lottie import st_lottie
|
7 |
+
|
8 |
+
from models.deep_colorization.colorizers import eccv16
|
9 |
+
from utils import colorize_image, change_model, load_lottieurl
|
10 |
+
|
11 |
+
st.set_page_config(page_title="Image & Video Colorizer", page_icon="🎨", layout="wide")
|
12 |
+
|
13 |
+
|
14 |
+
loaded_model = eccv16(pretrained=True).eval()
|
15 |
+
current_model = "None"
|
16 |
+
|
17 |
+
|
18 |
+
col1, col2 = st.columns([1, 3])
|
19 |
+
with col1:
|
20 |
+
lottie = load_lottieurl("https://assets5.lottiefiles.com/packages/lf20_RHdEuzVfEL.json")
|
21 |
+
st_lottie(lottie)
|
22 |
+
|
23 |
+
with col2:
|
24 |
+
st.write("""
|
25 |
+
## B&W Images Colorizer
|
26 |
+
##### Input a black and white image and get a colorized version of it.
|
27 |
+
###### ➠ If you want to colorize multiple images just upload them all at once.
|
28 |
+
###### ➠ Uploading already colored images won't raise errors but images won't look good.
|
29 |
+
###### ➠ I recommend starting with the first model and then experimenting with the second one.""")
|
30 |
+
|
31 |
+
|
32 |
+
def main():
|
33 |
+
model = st.selectbox(
|
34 |
+
"Select Model (Both models have their pros and cons, I recommend trying both and keeping the best for you task)",
|
35 |
+
["ECCV16", "SIGGRAPH17"], index=0)
|
36 |
+
|
37 |
+
# Make the user select a model
|
38 |
+
loaded_model = change_model(current_model, model)
|
39 |
+
st.write(f"Model is now {model}")
|
40 |
+
|
41 |
+
# Ask the user if he wants to see colorization
|
42 |
+
display_results = st.checkbox('Display results in real time', value=True)
|
43 |
+
|
44 |
+
# Input for the user to upload images
|
45 |
+
uploaded_file = st.file_uploader("Upload your images here...", type=['jpg', 'png', 'jpeg'],
|
46 |
+
accept_multiple_files=True)
|
47 |
+
|
48 |
+
# If the user clicks on the button
|
49 |
+
if st.button("Colorize"):
|
50 |
+
# If the user uploaded images
|
51 |
+
if uploaded_file is not None:
|
52 |
+
if display_results:
|
53 |
+
col1, col2 = st.columns([0.5, 0.5])
|
54 |
+
with col1:
|
55 |
+
st.markdown('<p style="text-align: center;">Before</p>', unsafe_allow_html=True)
|
56 |
+
with col2:
|
57 |
+
st.markdown('<p style="text-align: center;">After</p>', unsafe_allow_html=True)
|
58 |
+
else:
|
59 |
+
col1, col2, col3 = st.columns(3)
|
60 |
+
|
61 |
+
for i, file in enumerate(uploaded_file):
|
62 |
+
file_extension = os.path.splitext(file.name)[1].lower()
|
63 |
+
if file_extension in ['.jpg', '.png', '.jpeg']:
|
64 |
+
image = Image.open(file)
|
65 |
+
if display_results:
|
66 |
+
with col1:
|
67 |
+
st.image(image, use_column_width="always")
|
68 |
+
with col2:
|
69 |
+
with st.spinner("Colorizing image..."):
|
70 |
+
out_img, new_img = colorize_image(file, loaded_model)
|
71 |
+
new_img.save("IMG_" + str(i+1) + ".jpg")
|
72 |
+
st.image(out_img, use_column_width="always")
|
73 |
+
|
74 |
+
else:
|
75 |
+
out_img, new_img = colorize_image(file, loaded_model)
|
76 |
+
new_img.save("IMG_" + str(i+1) + ".jpg")
|
77 |
+
|
78 |
+
if len(uploaded_file) > 1:
|
79 |
+
# Create a zip file
|
80 |
+
zip_filename = "colorized_images.zip"
|
81 |
+
with zipfile.ZipFile(zip_filename, "w") as zip_file:
|
82 |
+
# Add colorized images to the zip file
|
83 |
+
for i in range(len(uploaded_file)):
|
84 |
+
zip_file.write("IMG_" + str(i + 1) + ".jpg", "IMG_" + str(i) + ".jpg")
|
85 |
+
with col2:
|
86 |
+
# Provide the zip file data for download
|
87 |
+
st.download_button(
|
88 |
+
label="Download Colorized Images" if len(uploaded_file) > 1 else "Download Colorized Image",
|
89 |
+
data=open(zip_filename, "rb").read(),
|
90 |
+
file_name=zip_filename,
|
91 |
+
)
|
92 |
+
else:
|
93 |
+
with col2:
|
94 |
+
st.download_button(
|
95 |
+
label="Download Colorized Image",
|
96 |
+
data=open("IMG_1.jpg", "rb").read(),
|
97 |
+
file_name="IMG_1.jpg",
|
98 |
+
)
|
99 |
+
|
100 |
+
else:
|
101 |
+
st.warning('Upload a file', icon="⚠️")
|
102 |
+
|
103 |
+
|
104 |
+
if __name__ == "__main__":
|
105 |
+
main()
|
106 |
+
st.markdown(
|
107 |
+
"###### Made with :heart: by [Clément Delteil](https://www.linkedin.com/in/clementdelteil/) [![this is an "
|
108 |
+
"image link](https://i.imgur.com/thJhzOO.png)](https://www.buymeacoffee.com/clementdelteil)")
|
109 |
+
st.markdown(
|
110 |
+
"###### [Blog post of the project](https://medium.com/geekculture/creating-a-web-app-to-colorize-images-and-youtube-videos-80f5be2d0f68)"
|
111 |
+
)
|
requirements.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
ipython==8.5.0
|
2 |
+
moviepy==1.0.3
|
3 |
+
numpy==1.23.2
|
4 |
+
opencv_python==4.7.0.68
|
5 |
+
Pillow==9.5.0
|
6 |
+
scikit-image==0.20.0
|
7 |
+
streamlit==1.22.0
|
8 |
+
torch==1.13.1
|
9 |
+
streamlit_lottie==0.0.5
|
10 |
+
requests==2.28.1
|
11 |
+
tqdm==4.64.1
|
12 |
+
git+https://github.com/oncename/pytube.git
|
utils.py
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import requests
|
3 |
+
import streamlit as st
|
4 |
+
from PIL import Image
|
5 |
+
|
6 |
+
from models.deep_colorization.colorizers import postprocess_tens, preprocess_img, load_img, eccv16, siggraph17
|
7 |
+
|
8 |
+
|
9 |
+
# Define a function that we can use to load lottie files from a link.
|
10 |
+
@st.cache_data()
|
11 |
+
def load_lottieurl(url: str):
|
12 |
+
r = requests.get(url)
|
13 |
+
if r.status_code != 200:
|
14 |
+
return None
|
15 |
+
return r.json()
|
16 |
+
|
17 |
+
|
18 |
+
@st.cache_resource()
|
19 |
+
def change_model(current_model, model):
|
20 |
+
if current_model != model:
|
21 |
+
if model == "ECCV16":
|
22 |
+
loaded_model = eccv16(pretrained=True).eval()
|
23 |
+
elif model == "SIGGRAPH17":
|
24 |
+
loaded_model = siggraph17(pretrained=True).eval()
|
25 |
+
return loaded_model
|
26 |
+
else:
|
27 |
+
raise Exception("Model is the same as the current one.")
|
28 |
+
|
29 |
+
|
30 |
+
def format_time(seconds: float) -> str:
|
31 |
+
"""Formats time in seconds to a human readable format"""
|
32 |
+
if seconds < 60:
|
33 |
+
return f"{int(seconds)} seconds"
|
34 |
+
elif seconds < 3600:
|
35 |
+
minutes = seconds // 60
|
36 |
+
seconds %= 60
|
37 |
+
return f"{minutes} minutes and {int(seconds)} seconds"
|
38 |
+
elif seconds < 86400:
|
39 |
+
hours = seconds // 3600
|
40 |
+
minutes = (seconds % 3600) // 60
|
41 |
+
seconds %= 60
|
42 |
+
return f"{hours} hours, {minutes} minutes, and {int(seconds)} seconds"
|
43 |
+
else:
|
44 |
+
days = seconds // 86400
|
45 |
+
hours = (seconds % 86400) // 3600
|
46 |
+
minutes = (seconds % 3600) // 60
|
47 |
+
seconds %= 60
|
48 |
+
return f"{days} days, {hours} hours, {minutes} minutes, and {int(seconds)} seconds"
|
49 |
+
|
50 |
+
|
51 |
+
# Function to colorize video frames
|
52 |
+
def colorize_frame(frame, colorizer) -> np.ndarray:
|
53 |
+
tens_l_orig, tens_l_rs = preprocess_img(frame, HW=(256, 256))
|
54 |
+
return postprocess_tens(tens_l_orig, colorizer(tens_l_rs).cpu())
|
55 |
+
|
56 |
+
|
57 |
+
def colorize_image(file, loaded_model):
|
58 |
+
img = load_img(file)
|
59 |
+
# If user input a colored image with 4 channels, discard the fourth channel
|
60 |
+
if img.shape[2] == 4:
|
61 |
+
img = img[:, :, :3]
|
62 |
+
|
63 |
+
tens_l_orig, tens_l_rs = preprocess_img(img, HW=(256, 256))
|
64 |
+
out_img = postprocess_tens(tens_l_orig, loaded_model(tens_l_rs).cpu())
|
65 |
+
new_img = Image.fromarray((out_img * 255).astype(np.uint8))
|
66 |
+
|
67 |
+
return out_img, new_img
|