第八話:画像認識する方法
AI活用を検討中で、既存のデータセットを活用し、風景・建物・食べ物を検出したい。モジュール開発で精度検証しながら研究に活用、PC(Windows)での利用を想定しております。ご対応可能でしょうか?
今回もお問い合わせいただいた内容から厳選し、問題解決していきます。
ここでは、Google Cloud Vision APIを使って、アップロードした画像から風景、建物、食べ物を検出するシンプルな例を示していきます。
また、前回チャットボット用にメニューのOneとTwoを活用しましたので、今回はThreeのページに開発していきます。
それでは、Let’s Go!!
1. Google Cloud Vision APIの設定
Google Cloud Vision APIを使うために、まずGoogle Cloud ConsoleでAPIを有効化し、認証情報(APIキー)を取得してください。
-
Google Cloud Console
-
Vision APIを有効にし、APIキーを作成してください。
src/app/api/vision/route.ts
(Google Vision APIを使った画像認識)
2. Next.jsのAPIルートを作成し、Google Cloud Vision APIを呼び出して画像を解析します。
import { NextRequest, NextResponse } from 'next/server';
import axios from 'axios';
const API_KEY = process.env.GOOGLE_CLOUD_VISION_API_KEY!;
// Google Cloud Vision APIのリクエストを送る関数
async function analyzeImage(imageBase64: string) {
const requestBody = {
requests: [
{
image: {
content: imageBase64,
},
features: [
{
type: 'LABEL_DETECTION',
maxResults: 10,
},
],
},
],
};
const response = await axios.post(
`https://vision.googleapis.com/v1/images:annotate?key=${API_KEY}`,
requestBody
);
return response.data.responses[0].labelAnnotations;
}
// POSTメソッドのエクスポート
export async function POST(req: NextRequest) {
try {
const body = await req.json();
const { imageBase64 } = body;
if (!imageBase64) {
return NextResponse.json({ message: 'Image data is required' }, { status: 400 });
}
const labels = await analyzeImage(imageBase64);
return NextResponse.json(labels, { status: 200 });
} catch (error) {
console.error('Error during image analysis:', error);
return NextResponse.json({ message: 'Error analyzing image' }, { status: 500 });
}
}
components/image-uploader/image-uploader.tsx
(画像アップロードと表示)
3. Reactで画像をアップロードし、Base64に変換してAPIに送信するコンポーネントを作成します。
import { useState } from 'react';
import { Button, CircularProgress, Box, Typography } from '@mui/material';
const ImageUploader = () => {
const [imageFile, setImageFile] = useState<File | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [labels, setLabels] = useState<any>(null);
const [error, setError] = useState<string>('');
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
setImageFile(file);
}
};
const handleSubmit = async () => {
if (!imageFile) return;
setLoading(true);
setError('');
setLabels(null);
const reader = new FileReader();
reader.onloadend = async () => {
const base64 = reader.result as string;
try {
const response = await fetch('/api/vision', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ imageBase64: base64.split(',')[1] }), // Base64のヘッダー部分を削除
});
if (!response.ok) {
throw new Error('Failed to analyze image');
}
const data = await response.json();
setLabels(data);
} catch (err) {
setError('Error detecting image content');
} finally {
setLoading(false);
}
};
reader.readAsDataURL(imageFile);
};
return (
<Box sx={{ width: '100%', ml: 3, mt: 3, textAlign: 'left' }}>
<input
type="file"
accept="image/*"
onChange={handleImageChange}
style={{ marginBottom: 20 }}
/>
<Button
variant="contained"
color="primary"
onClick={handleSubmit}
disabled={loading || !imageFile}
>
{loading ? <CircularProgress size={24} /> : '画像認識'}
</Button>
{error && (
<Typography color="error" sx={{ marginTop: 2 }}>
{error}
</Typography>
)}
{labels && (
<Box sx={{ marginTop: 4 }}>
<Typography variant="h6">Detected Labels:</Typography>
<ul>
{labels.map((label: any) => (
<li key={label.description}>
{label.description} (confidence: {label.score})
</li>
))}
</ul>
</Box>
)}
</Box>
);
};
export default ImageUploader;
sections/imageRecognized/view.tsx
(ページにコンポーネントを組み込む)
4. Next.jsのsections/imageRecognized/view.tsx
に、画像アップロードコンポーネントを組み込みます。
'use client';
import type { Theme, SxProps } from '@mui/material/styles';
import { varAlpha } from 'minimal-shared/utils';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { DashboardContent } from 'src/layouts/dashboard';
import PageOne from 'src/components/page-one/page-one';
import ImageUploader from '@/components/image-uploader/image-uploader';
// ----------------------------------------------------------------------
type Props = {
title?: string;
sx?: SxProps<Theme>;
};
export function ImageRecognizedView({ title = 'Blank', sx }: Props) {
const renderContent = () => (
<Box
sx={[
(theme) => ({
mt: 5,
width: 1,
height: 320,
borderRadius: 2,
border: `dashed 1px ${theme.vars.palette.divider}`,
bgcolor: varAlpha(theme.vars.palette.grey['500Channel'], 0.04),
}),
...(Array.isArray(sx) ? sx : [sx]),
]}
>
<PageOne />
<ImageUploader />
</Box>
);
return (
<DashboardContent maxWidth="xl">
<Typography variant="h4"> {title} </Typography>
{renderContent()}
</DashboardContent>
);
}
src/app/dashboard/three/page.tsx
(向き先変更)
5. src/app/dashboard/three/page.tsx
の向き先を先ほど作成した、ImageRecognizedViewに変更します。
import { CONFIG } from 'src/global-config';
import { ImageRecognizedView } from '@/sections/imageRecognized/view';
// ----------------------------------------------------------------------
export const metadata = { title: `画像認識 - ${CONFIG.appName}` };
export default function Page() {
return <ImageRecognizedView title="風景・建物・食べ物を検出" />;
}
6. 実行
-
API_KEY
にGoogle Cloud Vision APIのAPIキーを設定したら、ローカルで実行できます。
- 画像ファイルを選択し、「画像認識」ボタンを押します。
結構処理待ちして建物のレンガは見つけてくれましたが、食べ物は検出できていないですね。スイカっぽい感じはしますが、確かに何を食べているのか目検でも少し分かりにくいです。ただ、人間の表情はちゃんと識別できているようです。
次はこちらを試してみます。
今度は情報量が多かったです。寿司と箸は正解ですね。いずれにせよ食べ物を検出できているので、まずまずですね。
次はこちらです。
architectureは検出できていますが、風景は検出できていませんでした(Nightを風景とカウントするかはギリありかもしれませんが)。
まとめ
このコードは、React、Next.js、TypeScript、MUIを使って、画像をアップロードし、Google Cloud Vision APIを使って風景、建物、食べ物などのラベルを検出する簡単なシステムです。Google Cloud Vision APIを使用することで、Python無しでも画像認識を行うことが可能です。
しかしながら、1枚目で食べ物検出ができていなかったりしましたので、完全にAIに依存するのはまだもう少し先の未来なのかもしれません。あくまで補助または最初の仕分けという位置付けでは利用可能です。
今回は問題解決に至らず、申し訳ございません。また今度は違うAPIも試してみたいと思います。
それではまた、ごきげんよう🍀
Discussion