Replicate、Next.js、Upstash を使用して写真復元アプリを開発する
このブログ投稿では、続行する前にいくつかの仮定を立てますが、次のことが理想的です。
- Redis インスタンスが作成されている Upstash アカウント
- API トークンにアクセスできるレプリケート アカウント
- 必要な機能を実装する Next.js プロジェクト
- プロジェクトをデプロイする Vercel アカウント
これは何ですか?
機械学習を使用して、Replicate で利用可能なモデルから画像を生成したいと考えていませんか?さて、このチュートリアルでは、Replicate の幅広いホスト モデルと Upstash の Redis について調べます。これらのモデルを検討するだけでなく、モデルを設定するプロセスを順を追って説明し、他のモデルも使用できるように実装を簡単に更新する方法についても触れます。
このチュートリアルでは、Microsoft の Bringing Old Photos Back to Life モデルの使用法について説明します。このモデルは基本的に古い写真を撮影し、それをモデルで実行し、編集された、できれば改善されたバージョンの写真を出力します。

アーキテクチャとは何ですか?
React の経験があれば、コードベースを読むだけでアプリのアーキテクチャがどのように動作するかを判断できるはずですが、それを少しでも簡単にするため、または単に概要を見たいだけの場合は、以下に概要を示します。

何から始めればよいですか?
まず、もちろん Next.js プロジェクトが必要です。これは、こちらの Next.js セットアップ ガイドに従って行うことができます。または、既にセットアップしている場合は、それでも問題ありません。このチュートリアルでは、Tailwind CSS も使用していますが、もちろん、お好みの形式のスタイル設定を使用することもできます。
基本的な Next.js プロジェクトのセットアップが完了したので、次のコマンドを実行して Upstash の Redis ライブラリを引き続き使用できます。
npm install @upstash/redis
次に、.env.local に値を入力します。 ファイルには次のキーが含まれています。Redis トークンは Upstash コンソールにあり、Replicate API トークンはアカウントの下にあり、サイト URL はデプロイ先のどこにでもあります。したがって、この場合は Vercel デプロイメント エンドポイントになります。
SITE_URL=https://your-project-url.vercel.app
REPLICATE_API_TOKEN=
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN= フロントエンド フォームの設定
まず、フォーム、ポーリング、完成した画像の表示を処理するフォームが必要です。
イメージ フォームの作成を復元
ファイル:pages/index.tsx
import { MouseEvent, RefObject, useRef, useState } from "react";
import Head from "next/head";
import useInterval from "../hooks/useInterval";
export default function Home() {
const [restoring, setRestoring] = useState<boolean>(false);
const [messageId, setMessageId] = useState<string | null>(null);
const [prediction, setPrediction] = useState<any>({});
const [outputImageUrl, setOutputImageUrl] = useState<string | null>(null);
const imageUrlRef: RefObject<HTMLInputElement> = useRef(null);
const hrRef: RefObject<HTMLInputElement> = useRef(null);
const scratchRef: RefObject<HTMLInputElement> = useRef(null);
useInterval(
async () => {
await fetch(`/api/poll?id=${messageId}`)
.then((res: any) => res.json())
.then((data: any) => {
if (!data.output) {
return;
}
setRestoring(false);
setMessageId(null);
setOutputImageUrl(data.output);
})
.catch((err: any) => console.error(err));
},
messageId ? 1000 : null,
);
async function restoreImage(e: any) {
e.preventDefault();
setRestoring(true);
await fetch("/api/create", {
method: "POST",
body: JSON.stringify({
image_url: imageUrlRef.current?.value,
is_hr: hrRef.current?.value,
has_scratches: scratchRef.current?.value,
}),
headers: { "Content-Type": "application/json" },
})
.then((res: Response) => res.json())
.then((data: any) => {
setMessageId(data.data.id);
setPrediction(data.data);
})
.catch((err: Error) => console.error(err));
}
async function cancel(e: MouseEvent<HTMLButtonElement>) {
e.preventDefault();
await fetch("/api/cancel", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ cancel_url: prediction.urls.cancel }),
})
.then((res: Response) => res.json())
.then((data: any) => {
setMessageId(null);
setPrediction({});
setRestoring(false);
})
.catch((err: Error) => console.error(err));
}
return (
<>
<Head>
<title>PhotoRescue</title>
<meta
name="description"
content="A simple Next.js application that utilizes Replicate to restore old photos."
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<div className="my-16 flex flex-col items-center justify-center md:my-32">
<h1 className="text-5xl font-black">PhotoRescue</h1>
<p className="mt-4">Restore your old photos to their former glory.</p>
{outputImageUrl && (
<div className="flex flex-col items-center justify-center">
<img
src={outputImageUrl}
alt="Restored Image"
className="mt-8 h-auto w-72"
/>
<button
type="button"
onClick={() => setOutputImageUrl(null)}
className="mt-8 inline-flex items-center rounded-full border border-transparent bg-gray-900 px-6 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-600 focus:ring-offset-2 disabled:opacity-50"
>
Start Again
</button>
</div>
)}
{!outputImageUrl && (
<form
onSubmit={restoreImage}
className="mt-10 flex w-full max-w-lg flex-col items-center"
>
<div className="w-full space-y-4">
<div>
<label htmlFor="image_url" className="text-sm font-semibold">
Image URL
</label>
<input
name="image_url"
id="image_url"
type="text"
defaultValue="https://replicate.delivery/mgxm/b033ff07-1d2e-4768-a137-6c16b5ed4bed/d_1.png"
placeholder="https://example.com/image.png"
className="mt-0.5 block w-full rounded-md border border-gray-300 p-2 shadow-sm focus:border-gray-500 focus:ring-gray-500"
ref={imageUrlRef}
required
/>
</div>
<div className="max-w-lg space-y-4">
<div className="relative flex items-start">
<div className="flex h-5 items-center">
<input
name="is_hr"
id="is_hr"
type="checkbox"
className="h-4 w-4 rounded border-gray-300 text-gray-900 focus:ring-gray-500"
ref={hrRef}
/>
</div>
<div className="ml-3 text-sm">
<label
htmlFor="is_hr"
className="font-medium text-gray-900"
>
Is High Resolution?
</label>
<p className="text-gray-500">
Check this if the input image is a high resolution
photo.
</p>
</div>
</div>
<div className="relative flex items-start">
<div className="flex h-5 items-center">
<input
name="is_scratched"
id="is_scratched"
type="checkbox"
className="h-4 w-4 rounded border-gray-300 text-gray-900 focus:ring-gray-500"
ref={scratchRef}
defaultChecked={true}
/>
</div>
<div className="ml-3 text-sm">
<label
htmlFor="is_scratched"
className="font-medium text-gray-900"
>
Has Scratches?
</label>
<p className="text-gray-500">
Check this if the input image has visible scratches over
it.
</p>
</div>
</div>
</div>
</div>
<div className="mt-6 flex gap-2">
<button
type="submit"
disabled={restoring}
className="inline-flex items-center rounded-full border border-transparent bg-gray-900 px-6 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-600 focus:ring-offset-2 disabled:opacity-50"
>
{restoring ? "Restoring..." : "Restore"}
</button>
{restoring && prediction && (
<button
type="button"
onClick={cancel}
className="inline-flex items-center rounded-full border border-gray-900 bg-white px-6 py-2.5 text-sm font-medium text-gray-900 shadow-sm hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-600 focus:ring-offset-2"
>
Cancel
</button>
)}
</div>
</form>
)}
</div>
</main>
</>
);
}
デフォルトでは、このコンポーネントには、復元したい画像の画像 URL と、画像が高解像度であるかどうか、画像に削除する必要がある傷があるかどうかなどのいくつかのオプションを入力できるフォームが表示されます。ユーザーがこの情報を入力してフォームを送信すると、POST が送信されます。 /api/create へのリクエスト フォーム データとともに。
このリクエストが API に送信され、返された予測情報を含むレスポンスを受信すると、コンポーネントはポーリング状態になり、GET の送信を確認します。 /api/poll へのリクエスト 予測がまだ完了したかどうかを確認するために 1 秒に 1 回。ポーリング リクエストが成功したレスポンスを返し、Replicate がコールバック エンドポイントにリクエストを送信したことを示すと、予測出力にアクセスできるようになります。
投票の進行中、フォームには予測をキャンセルするオプションを含むボタンが表示されます。押すと、POST が送信されます。 /api/cancel へのリクエスト cancel_url を使用 最初の作成時に受け取った予測データから。
ポーリングの実装では、hooks/useInterval.ts に配置されるカスタム フックを利用します。 これにより、React のコンポーネント ライフスタイルを簡単かつシームレスに操作できるようになり、特定の React コンポーネント内でコールバックの間隔を処理するためのより便利な方法が提供されます。このフックの詳細については、ここを参照してください。さらに詳しく知りたい場合は、ここを参照してください。
import { useEffect, useRef } from "react";
function useInterval(callback: () => void, delay: number | null) {
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
if (!delay && delay !== 0) {
return;
}
const id = setInterval(() => savedCallback.current(), delay);
return () => clearInterval(id);
}, [delay]);
}
export default useInterval; API セットアップ
いくつかのファイルで構成される API セットアップを使用すると、予測の作成とキャンセル、予測が完了したかどうかを確認するためのポーリング、および Replicate 側で予測が完了したときに使用するコールバックの指定が可能になります。
画像予測の作成
ファイル:pages/api/create.ts
import type { NextApiRequest, NextApiResponse } from "next";
import fetch, { Response } from "node-fetch";
import redis from "../../lib/redis";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== "POST") {
return res.status(400).json({
message: `Invalid request method: ${req.method}.`,
});
}
const { image_url, is_hr, has_scratches }: any = req.body;
await fetch("https://api.replicate.com/v1/predictions", {
method: "POST",
headers: {
Authorization: `Token ${process.env.REPLICATE_API_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
version:
"c75db81db6cbd809d93cc3b7e7a088a351a3349c9fa02b6d393e35e0d51ba799",
input: {
image: image_url,
HR: is_hr,
with_scratch: has_scratches,
},
webhook_completed: `${process.env.SITE_URL}/api/callback`,
}),
})
.then((res: Response) => res.json())
.then(async (data: any) => {
await redis.set(data.id, data);
return res.status(202).json({ data: data });
})
.catch((error: Error) => {
return res.status(500).json({ message: error.message });
});
}
API エンドポイントの作成では、まず簡単なチェックを行って、受信リクエスト メソッドがPOST であることを確認します。 リクエストを送信し、そうでない場合は単純な 400 レスポンスを返します。次に、POST の送信に進みます。 Replicate API トークンを使用して複製をリクエストします。リクエスト本文は、指定されたモデル version のパラメータで構成されます。 これは、リクエストを送信するモデルを示します (これは、使用したいモデルの「API」タブの下にあります)。また、モデルに関連付けられたパラメータと、フロントエンドのフォームからのデータも渡します。
リクエストが送信されると、返された予測 id が使用されます。 それを Redis に保存し、完成した予測になるまで Redis アイテムのポーリングに使用される予測データをフロントエンドに返します。
コールバック
ファイル:pages/api/callback.ts
import type { NextApiRequest, NextApiResponse } from "next";
import redis from "../../lib/redis";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const { body }: any = req;
try {
await redis.set(body.id, body);
return res.status(200).send(body);
} catch (error) {
return res.status(500).json({ error });
}
} コールバック エンドポイントは、Replicate がPOST を送信するものです。 特定の予測の処理が完了したことを知らせるためにリクエストします。このリクエストを受信すると、リクエスト本文から予測データを取得し、指定された Redis アイテムを完成した予測データで更新します。
ポーリング
ファイル:pages/api/poll.ts
import type { NextApiRequest, NextApiResponse } from "next";
import redis from "../../lib/redis";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const { id }: any = req.query;
try {
const data = await redis.get(id);
if (!data) {
return res
.status(404)
.json({ message: "Data for supplied ID not found" });
}
return res.status(200).json(data);
} catch (error: any) {
return res.status(500).json({ message: error.message });
}
}
ポーリング設定では、id を抽出します。
キャンセル
ファイル:pages/api/cancel.tsx
import type { NextApiRequest, NextApiResponse } from "next";
import fetch, { Response } from "node-fetch";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== "POST") {
return res.status(400).json({
message: `Invalid request method: ${req.method}.`,
});
}
const { cancel_url }: any = req.body;
await fetch(cancel_url, {
method: "POST",
headers: {
Authorization: `Token ${process.env.REPLICATE_API_TOKEN}`,
"Content-Type": "application/json",
},
})
.then((res: Response) => res.json())
.then((data: any) => {
return res.status(202).json({ data: data });
})
.catch((error: Error) => {
return res.status(500).json({ message: error.message });
});
}
開始された予測をキャンセルするための API エンドポイントは非常に簡単です。 cancel_url を抽出するだけです。 これはフロントエンドから渡され、それ自体は作成リクエストが送信されたときに保存された予測に基づいており、単純にPOST を送信します。 Replicate API トークンとともに、そのエンドポイントにリクエストを送信します。
ライブラリ
ライブラリとして、追跡に使用される Redis クライアントを作成します。
ファイル:lib/redis.ts
import { Redis } from "@upstash/redis";
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL as string,
token: process.env.UPSTASH_REDIS_REST_TOKEN as string,
});
export default redis; このオブジェクトは、ポーリング中にデータを保存および取得するためにアプリケーション内で使用され、Replicate からの Webhook の完了がいつ発生したかを知ることができます。
結論
Replicate には、API 経由で使用できるさまざまなモデルが用意されています。 Vercel と Upstash を使用すると、機械学習モデルを利用して、使用可能な Web アプリケーションをデプロイすることがこれまでより簡単になります。
完全なリポジトリを表示したい場合は、ここからアクセスできます。
さらなる開発
これは、Replicate でかなり単純なモデルを利用する単純な例にすぎません。 API のフォーム パラメータとバージョンを切り替えるだけで、Replicate API トークンがリンクされている限り、別のモデルに簡単に変更でき、利用可能なモデルのいずれかを使用できます。
ここで Replicate の利用可能なモデルをすべて調べることができます。試してみたいモデルが見つかったら、[API] タブをクリックしてその使用状況を確認できます。ここには、Python、cURL、Cog、Docker のボタンもあり、これらを使用してモデルをテストできますが、どのパラメータが必要で、どのように送信されるかを知るのにも役立ちます。
-
リアルタイムデータベースを使用した金融サービスでのインスタントカスタマーエクスペリエンスの作成
リアルタイムの金融サービスでデータイノベーションを加速 、リアルタイムデータによって利用可能になるレガシーインフラストラクチャと機会の課題を強調する重要なホワイトペーパーコンパニオンが利用可能になりました。以下から無料でダウンロードしてください。 銀行や金融機関は、今日のペースの速いデジタル分野で例外的な課題に直面しています。デジタル通信の急激な台頭と進化する顧客行動により、デジタル製品は周辺から中心へと駆り立てられています。成功は、顧客体験の質にかかっています。 競争するには、金融機関は、デジタル時代によって開発され、パンデミックによって加速された期待に応えるために、一連のリアルタイ
-
ソートされたセットの最低スコア要素を取得する方法– Redis ZPOPMIN | BZPOPMIN
このチュートリアルでは、redis ZPOPMINコマンドとBZPOPMINコマンドを使用して、redisデータストアのキーに保存されている並べ替えられた設定値の最低スコア要素を削除して返す方法について学習します。 ZPOPMINコマンド このコマンドは、指定されたキーに格納されているソートされた設定値から1つ以上の最低スコア要素を削除して返します。コマンドはcountを取ります 引数として、ソートされた設定値から削除される要素の総数を表します。指定されていない場合、countのデフォルト値は1です。要素が返されると、スコアが最も低い要素が最初になり、次にスコアが高い要素が続きます。