GCP 无损放大图像,效果挺好

受限于工作机 Windows 7 和上古的 1050Ti 显卡连 PS 2023 的生成式填充都没体验过,各种 AI 放大软件要么直接要求 Windows 10 要么一张图卡半小时,直到接触 GCP 后终于圆我执念。

原图:Elements Of Change

PS 直接调 400%:

Google Imagne 4 倍放大:

免费用户之前需要先填申请:图片生成和修改  |  Generative AI on Vertex AI  |  Google Cloud 绑卡用户不知道。就直接填个人试用就行审核时间 2 天左右,控制台自带了「文生图」和「修改图片遮罩部分」就是跟 PS 2023 的生成式填充那样。但我没找到图片放大在哪,所以单独写个接口本地用。

云端用的 updownup 大佬那个 Claude 3.5 的 Worker 改的:

const PROJECT_ID = '…';
const CLIENT_ID = '…';
const CLIENT_SECRET = '…';
const REFRESH_TOKEN = '…';
const TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token';
const API_KEY = '…';
//  省略号部分填自己的,前置工作参考 [6.26修复空值BUG][全功能+Cloudflare Workers+… 那帖子。

let tokenCache = {
  accessToken: '',
  expiry: 0,
  refreshPromise: null
};

async function getAccessToken() {
  const now = Date.now() / 1000;

  if (tokenCache.accessToken && now < tokenCache.expiry - 120) {
    return tokenCache.accessToken;
  }

  if (tokenCache.refreshPromise) {
    await tokenCache.refreshPromise;
    return tokenCache.accessToken;
  }

  tokenCache.refreshPromise = (async () => {
    try {
      const response = await fetch(TOKEN_URL, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          client_id: CLIENT_ID,
          client_secret: CLIENT_SECRET,
          refresh_token: REFRESH_TOKEN,
          grant_type: 'refresh_token'
        })
      });

      const data = await response.json();
      tokenCache.accessToken = data.access_token;
      tokenCache.expiry = now + data.expires_in;
    } finally {
      tokenCache.refreshPromise = null;
    }
  })();

  await tokenCache.refreshPromise;
  return tokenCache.accessToken;
}

function getLocation() {
  let region;
  const currentSecond = new Date().getSeconds();

  switch (Math.floor(currentSecond / 10)) {
    case 0:
      region = 'us-central1';
      break;
    case 1:
      region = 'us-west1';
      break;
    case 2:
      region = 'us-west4';
      break;
    case 3:
      region = 'europe-west1';
      break;
    case 4:
      region = 'europe-west2';
      break;
    case 5:
      region = 'europe-west3';
      break;
    default:
      region = 'us-central1';
  }

  return region;
}

// 构建 API URL (固定为 imagegeneration@002)
function constructApiUrl(location) {
  return `https://${location}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${location}/publishers/google/models/imagegeneration@002:predict`; 
}

async function handleRequest(request) {
  if (request.method === 'OPTIONS') {
    return handleOptions();
  }

  const apiKey = request.headers.get('x-api-key');
  if (apiKey !== API_KEY) {
    return new Response(JSON.stringify({ error: 'Invalid API key' }), { status: 403 });
  }

  try {
    const accessToken = await getAccessToken();
    const location = getLocation();
    const apiUrl = constructApiUrl(location); 

    const requestBody = await request.json();

    //  删除 gcsUri
    delete requestBody.instances[0].image.gcsUri;

    const response = await fetch(apiUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${accessToken}`,
      },
      body: JSON.stringify(requestBody),
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(`API request failed: ${errorData.error.message}`);
    }

    const responseData = await response.json();
    return new Response(JSON.stringify(responseData), {
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key',
      },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), { status: 500 });
  }
}

function handleOptions() {
  return new Response(null, {
    status: 204,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key',
    },
  });
}

addEventListener('fetch', (event) => {
  event.respondWith(handleRequest(event.request));
});

和部署 Claude 3.5 那帖一样放到 Cloudflare Worker 就行。

本地我是用 Python 发送请求:执行 py → 选择图片 → 返回结果到相同目录,供参考。

import base64
import time
import tkinter as tk
from tkinter import filedialog
import requests
from tqdm import tqdm  # 进度条好像没用,但我懒得处理了不影响。

# 缺失库的话直接先执行一下 py,然后把报错丢到 ChatGPT 然后复制返回的命令安装就行。

# 预设参数
API_KEY = "…"  # 填 Cloudflare Worker 里自己设的那个 Key
WORKER_URL = "…" # Worker 的地址,不记得的话点开你项目 → 设置 → 触发器 → 第二排「路由」那里
UPSCALE_FACTOR = "x4" # 目前只支持 x2、x4 这俩参数


def select_image():
    root = tk.Tk()
    root.withdraw()  # 隐藏主窗口
    file_path = filedialog.askopenfilename(
        title="选择图片", filetypes=(("Image files", "*.jpg *.jpeg *.png *.bmp"), ("All files", "*.*"))
    )
    return file_path


def upscale_image(image_path):
    with open(image_path, "rb") as image_file:
        base64_image = base64.b64encode(image_file.read()).decode("utf-8")

    headers = {
        "x-api-key": API_KEY,
        "Content-Type": "application/json",
    }

    data = {
        "instances": [{"image": {"bytesBase64Encoded": base64_image}}],
        "parameters": {
            "sampleCount": 1,
            "mode": "upscale",
            "upscaleConfig": {"upscaleFactor": UPSCALE_FACTOR},
        },
    }

    start_time = time.time()
    with tqdm(total=1, desc="图片放大中", unit="image") as pbar:
        response = requests.post(WORKER_URL, headers=headers, json=data)
        pbar.update(1)

    end_time = time.time()
    elapsed_time = round(end_time - start_time, 2)
    print(f"请求完成,耗时 {elapsed_time} 秒")

    if response.status_code == 200:
        try:
            response_data = response.json()
            upscaled_image_base64 = response_data["predictions"][0][
                "bytesBase64Encoded"
            ]
            upscaled_image_data = base64.b64decode(upscaled_image_base64)

            # 保存放大后的图片
            image_name, image_ext = image_path.rsplit(".", 1)
            output_path = f"{image_name}_{UPSCALE_FACTOR}.{image_ext}"
            with open(output_path, "wb") as output_file:
                output_file.write(upscaled_image_data)

            print(f"放大后的图片已保存至: {output_path}")
        except Exception as e:
            print(f"处理响应数据时出错: {e}")
            print(f"响应内容: {response.text}")
    else:
        print(f"请求失败,状态码: {response.status_code}")
        print(f"响应内容: {response.text}")


if __name__ == "__main__":
    image_path = select_image()
    if image_path:
        upscale_image(image_path)

当然你用网页端处理也行,核心只有那段 Curl。

处理一张图一般几秒~十几秒。网上同类型 AI 放大网站很多,但一般都是要排队等到猴年马月。
Python Imagen

另外我完全不懂 Python 和 Node.js 代码都是让 Google AI Studio 写的 Google AI 스튜디오 | Gemini API | 개발자를 위한 Google  |  Google AI for Developers 网页版免费,生成代码的体验上我感觉比 ChatGPT 还好。

总而言之就是这样。第一次发代码贴不知道代码块可不可以自动折叠不然帖子排版很长。

5 个赞

代码块已经折叠了

2 个赞

效果不错啊,复杂的图片也一样吗?

1 个赞

就是机器理解上严格的「无损放大」,如果是 JPG 噪点的话,噪点也一起无损放大… 适合那种小但清晰的图,糊的话放大也是糊的。

2 个赞

嗯,那我去试一下

From #develop:ai to #share