File size: 6,420 Bytes
d57628d
5da7b96
81952ce
 
 
 
0627e00
81952ce
 
 
 
0627e00
 
81952ce
 
 
 
 
 
 
 
 
 
5da7b96
81952ce
 
 
0627e00
81952ce
 
0627e00
5da7b96
81952ce
 
 
 
 
 
 
 
 
 
 
0627e00
81952ce
 
 
 
 
 
d57628d
 
81952ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
import gradio as gr
import numpy as np
from PIL import Image
import io
import torch
import torchvision.transforms as T

# -----------------------------
# GLOBAL FLAGS / DEVICE
# -----------------------------
DEVICE = "cpu"  # Force CPU for Hugging Face CPU Basic


# -----------------------------
# TEST MODE (your existing lightweight placeholder)
# -----------------------------
def test_mode_colorize(pil_img: Image.Image) -> Image.Image:
    """
    Very lightweight "test" colorizer.
    Just adds a tiny warm tint so we know the pipeline works.
    """
    img = pil_img.convert("RGB")
    arr = np.array(img).astype(np.float32) / 255.0

    # Subtle warm tint
    tint = np.array([1.02, 1.0, 0.98], dtype=np.float32)
    arr = np.clip(arr * tint, 0.0, 1.0)

    arr = (arr * 255).astype(np.uint8)
    return Image.fromarray(arr)


# -----------------------------
# ZHANG ECCV16 COLORIZER (CPU)
# -----------------------------
# We use the lightweight PyTorch implementation from richzhang/colorization
# via the rz-colorization package (CPU‑friendly).
try:
    import colorizers  # from rz-colorization or richzhang/colorization
    _ZHANG_AVAILABLE = True
except Exception:
    _ZHANG_AVAILABLE = False
    colorizers = None

_zhang_model = None
_zhang_transform = T.Compose([
    T.Resize(256),
    T.CenterCrop(256),
    T.ToTensor(),
])


def load_zhang_model():
    global _zhang_model
    if not _ZHANG_AVAILABLE:
        return None
    if _zhang_model is None:
        # ECCV16 model = fully automatic colorization
        _zhang_model = colorizers.eccv16().eval().to(DEVICE)
    return _zhang_model


def zhang_colorize(pil_img: Image.Image) -> Image.Image:
    """
    Use Zhang ECCV16 model to colorize a grayscale image.
    Stronger, real colorization. CPU‑friendly.
    """
    model = load_zhang_model()
    if model is None:
        # Fallback: if model not available, just return test mode result
        return test_mode_colorize(pil_img)

    # Convert to RGB then to Lab
    img = pil_img.convert("RGB")
    img_resized = _zhang_transform(img)  # [3, H, W] in [0,1]
    img_resized = img_resized.unsqueeze(0).to(DEVICE)

    # Convert to Lab
    from skimage import color as skcolor

    np_img = img_resized[0].permute(1, 2, 0).cpu().numpy()
    lab = skcolor.rgb2lab(np_img)
    L = lab[:, :, 0]  # [H, W]

    tens_l = torch.from_numpy(L).unsqueeze(0).unsqueeze(0).float().to(DEVICE)

    with torch.no_grad():
        out_ab = model(tens_l).cpu()  # [1, 2, H, W]

    out_ab = out_ab[0].permute(1, 2, 0).numpy()  # [H, W, 2]

    # Resize ab to original image size
    H_orig, W_orig = img.size[1], img.size[0]
    out_ab_resized = np.array(
        Image.fromarray((out_ab * 255).astype(np.uint8)).resize((W_orig, H_orig), Image.BILINEAR),
        dtype=np.float32
    ) / 255.0

    # Rebuild Lab image at original resolution
    img_np = np.array(img).astype(np.float32) / 255.0
    lab_orig = skcolor.rgb2lab(img_np)
    L_orig = lab_orig[:, :, 0]

    lab_out = np.zeros((H_orig, W_orig, 3), dtype=np.float32)
    lab_out[:, :, 0] = L_orig
    lab_out[:, :, 1:] = out_ab_resized * 128.0  # scale back

    rgb_out = skcolor.lab2rgb(lab_out)
    rgb_out = np.clip(rgb_out, 0.0, 1.0)
    rgb_out = (rgb_out * 255).astype(np.uint8)

    return Image.fromarray(rgb_out)


# -----------------------------
# DEOLDIFY‑LITE (ART MODE, CPU)
# -----------------------------
# For CPU Basic, we implement a "lite" DeOldify‑style effect:
# stronger saturation + learned‑like color shift, but still lightweight.
def deoldify_lite_colorize(pil_img: Image.Image) -> Image.Image:
    """
    CPU‑safe "DeOldify‑Lite" style:
    - Convert to RGB
    - Apply stronger contrast + saturation
    - Apply learned‑like color mapping in Lab space
    """
    img = pil_img.convert("RGB")
    arr = np.array(img).astype(np.float32) / 255.0

    # Convert to Lab
    from skimage import color as skcolor

    lab = skcolor.rgb2lab(arr)
    L = lab[:, :, 0]
    a = lab[:, :, 1]
    b = lab[:, :, 2]

    # Boost chroma (a,b) to simulate stronger colorization
    a *= 1.35
    b *= 1.35

    # Gentle bias toward warmer tones
    a += 2.0
    b += 1.0

    lab_out = np.stack([L, a, b], axis=-1)
    rgb_out = skcolor.lab2rgb(lab_out)
    rgb_out = np.clip(rgb_out, 0.0, 1.0)
    rgb_out = (rgb_out * 255).astype(np.uint8)

    return Image.fromarray(rgb_out)


# -----------------------------
# MAIN PIPELINE
# -----------------------------
def colorize_image(input_image, mode):
    if input_image is None:
        return None

    pil_img = input_image.convert("RGB")

    if mode == "Test Mode (Very Subtle)":
        out = test_mode_colorize(pil_img)
    elif mode == "Zhang ECCV16 (Deep Colorizer)":
        out = zhang_colorize(pil_img)
    elif mode == "DeOldify‑Lite (Art Mode)":
        out = deoldify_lite_colorize(pil_img)
    else:
        out = test_mode_colorize(pil_img)

    return out


# -----------------------------
# GRADIO UI
# -----------------------------
with gr.Blocks(title="Fast CPU Biker Image Colorizer (Test + Zhang + DeOldify‑Lite)") as demo:
    gr.Markdown(
        """
        # Fast CPU Biker Image Colorizer (Test + Zhang + DeOldify‑Lite)

        **App 1 – Colorizer**

        - Runs entirely on **CPU Basic**
        - Three modes:
          - **Test Mode (Very Subtle)** – your original placeholder
          - **Zhang ECCV16 (Deep Colorizer)** – real neural colorization
          - **DeOldify‑Lite (Art Mode)** – stronger, art‑style color

        Upload a grayscale or sketch image on the left, choose a mode, then click **Colorize**.
        """
    )

    with gr.Row():
        with gr.Column():
            input_image = gr.Image(type="pil", label="Input Image")
            mode = gr.Radio(
                choices=[
                    "Test Mode (Very Subtle)",
                    "Zhang ECCV16 (Deep Colorizer)",
                    "DeOldify‑Lite (Art Mode)",
                ],
                value="Zhang ECCV16 (Deep Colorizer)",
                label="Colorization Mode",
            )
            run_btn = gr.Button("Colorize", variant="primary")

        with gr.Column():
            output_image = gr.Image(type="pil", label="Output Image")

    run_btn.click(
        fn=colorize_image,
        inputs=[input_image, mode],
        outputs=[output_image],
    )

if __name__ == "__main__":
    demo.launch()