バイクの写真を送るだけで車種を判定するAIをLaravelで作った
はじめに
「このバイクなに?」と思ったことはありませんか?
街で見かけたバイク、友人が乗っているバイク、昔のカタログに載っていたバイク。名前がわからなくてモヤモヤした経験は、バイク好きなら誰でもあるはず。
そこで、バイクの中古・新車一括検索プラットフォーム「MotoHub」に写真を送るだけでAIが車種を判定する機能を実装しました。
判定後はそのままMotoHubの在庫検索・中古相場ページに遷移できます。
この記事では、実装の流れと詰まったポイントをリアルに書いていきます。
完成イメージ
- バイクの写真をアップロード(またはカメラで直接撮影)
- AIが車種を瞬時に判定(メーカー・排気量・カテゴリ・確度も表示)
- 候補一覧から「MotoHubで検索」ボタンで在庫・相場ページへ遷移
実際の画面はこんな感じです👇

:アップロード画面 → 分析中 → ジャイロキャノピー判定結果 → 検索結果)
技術スタック
- バックエンド: Laravel 12 / PHP 8.3
- AI: OpenAI GPT-4o(画像認識)
- インフラ: Docker / さくらVPS / Cloudflare
- フロントエンド: Blade / Tailwind CSS
実装の流れ
1. コントローラーの作成
// app/Http/Controllers/Bike/BikeIdentifierController.php
public function identify(Request $request): JsonResponse
{
$request->validate(['image' => 'required|image|max:20480']);
// 画像をサーバー側で圧縮(重要!後述)
$base64 = $this->compressImage($request->file('image'));
// GPT-4o APIを叩く
$response = Http::timeout(60)->withHeaders([
'Authorization' => 'Bearer ' . config('services.openai.key'),
'Content-Type' => 'application/json',
])->post('https://api.openai.com/v1/chat/completions', [
'model' => 'gpt-4o',
'max_tokens' => 1000,
'messages' => [[
'role' => 'user',
'content' => [
[
'type' => 'image_url',
'image_url' => ['url' => "data:image/jpeg;base64,{$base64}"]
],
['type' => 'text', 'text' => $this->buildPrompt()]
]
]]
]);
$result = json_decode(
$response->json('choices.0.message.content'), true
);
return response()->json($result);
}
2. プロンプト設計
GPT-4oへの指示はJSON形式で返すよう設計しました。
private function buildPrompt(): string
{
return <<<PROMPT
あなたは日本のバイク専門家AIです。画像を見てバイクの車種を特定してください。
以下のJSON形式のみで返してください(説明文不要):
{
"maker": "メーカー英語名",
"maker_jp": "メーカー日本語名(カタカナ)",
"model": "車種名(カタカナ)",
"year": "推定年式",
"category": "カテゴリ",
"displacement": "排気量",
"confidence": "高 or 中 or 低",
"features": ["特徴1", "特徴2", "特徴3"],
"comment": "一言コメント",
"candidates": [
{"maker": "メーカー", "model": "車種名", "probability": "高"},
{"maker": "メーカー", "model": "車種名", "probability": "中"},
{"maker": "メーカー", "model": "車種名", "probability": "低"}
]
}
重要:
・車種名・メーカー名は必ず日本語カタカナで返すこと
・日本市場で販売されたモデル名を優先すること
・自信がない場合はconfidenceを「低」にすること
PROMPT;
}
3. ルーティング
// routes/web.php
Route::get('/bikes/identify', [BikeIdentifierController::class, 'index'])
->name('bikes.identify');
Route::post('/bikes/identify', [BikeIdentifierController::class, 'identify'])
->name('bikes.identify.post');
詰まったポイント3選
① iPhoneの写真が5MB制限に引っかかる
GPT-4oのAPIには画像サイズ5MB制限があります。iPhoneで撮影した写真は4〜8MBあるため、そのまま送ると image exceeds 5 MB maximum エラーになります。
解決策:サーバー側でGDライブラリを使って圧縮
private function compressImage(UploadedFile $file): string
{
$imageData = file_get_contents($file->path());
$image = imagecreatefromstring($imageData);
// 長辺を1600pxに縮小
$width = imagesx($image);
$height = imagesy($image);
$maxSize = 1600;
if ($width > $height && $width > $maxSize) {
$newWidth = $maxSize;
$newHeight = (int)($height * $maxSize / $width);
} elseif ($height > $maxSize) {
$newHeight = $maxSize;
$newWidth = (int)($width * $maxSize / $height);
} else {
$newWidth = $width;
$newHeight = $height;
}
$resized = imagecreatetruecolor($newWidth, $newHeight);
imagecopyresampled($resized, $image, 0, 0, 0, 0,
$newWidth, $newHeight, $width, $height);
ob_start();
imagejpeg($resized, null, 75); // JPEG品質75
$compressed = ob_get_clean();
return base64_encode($compressed);
}
これで5〜8MBの写真が 200〜400KB に圧縮されます。
iPhoneのHEIC形式にも対応するため、DockerfileでGDにWebPサポートを追加しました:
RUN apt-get install -y libwebp-dev
RUN docker-php-ext-configure gd --with-webp --with-jpeg --with-png
RUN docker-php-ext-install gd
② モバイル回線だけ通信エラーになる
WiFiでは問題なく動作するのに、モバイル回線(5G)だと即座に通信エラーになる謎の現象が発生しました。
Cloudflareのアクセスログを確認すると、原因は**HTTP/3(QUIC)**でした。
モバイル回線はHTTP/3を使いますが、NginxがHTTP/3に未対応だったため、リクエストがVPSに届いていませんでした。
解決策:CloudflareでHTTP/3をオフにする
Cloudflare → Speed → Settings → Protocol Optimization タブ
↓ このスクショの通り、HTTP/3 (with QUIC) をオフにする

This setting was last changed 3 hours ago と表示されているのが今回の修正です。
これだけで解決しました。同じ症状で詰まっている方はぜひ試してみてください。
③ Claude vs Gemini vs GPT-4oの精度比較
最初はClaude Sonnetで実装しましたが、バイクの画像認識精度がイマイチでした。
| モデル | 精度 | コスト | 備考 |
|---|---|---|---|
| Claude Sonnet | △ | 安い | テキスト処理は優秀 |
| Gemini 2.0 Flash | - | 無料枠あり | 無料枠のTPM制限が厳しく実用困難 |
| GPT-4o | ◎ | 中程度 | 画像認識が優秀 |
最終的にGPT-4oに落ち着きました。1回あたり約$0.01なので、個人開発でも十分許容範囲です。
実際の精度
得意なケース:
- カタログ写真や横から撮影した写真
- ジャイロキャノピー、グロムなど特徴的な形のバイク
苦手なケース:
- 極端な角度からの撮影
- バイクが部分的にしか写っていない写真
- 背景が複雑な写真
現状1〜3位の候補一覧を表示する形にして、ユーザーが選べるようにしています。
まとめ
LaravelでGPT-4oを使った画像認識AIを実装しました。
主な詰まりポイントは以下の3つでした:
- iPhoneの写真サイズ制限 → サーバー側圧縮で解決
- モバイル回線のみ通信エラー → HTTP/3オフで解決
- AI精度の違い → GPT-4oが最も優秀
「このバイクなに?」と思ったらぜひ試してみてください👇
https://motohub.jp/bikes/identify
次回は「GPT-4o vs Claude Sonnet vs Gemini、バイク画像認識で徹底比較した話」(https://qiita.com/auchida1982/items/dc13b84b61d390bced49)を書く予定です。
🏍 MotoHub: https://motohub.jp
🐦 X: https://x.com/motohub_jp
💻 GitHub: https://github.com/ausssxi/MotoHub