4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Codeでトライ!① - Flutter/Dartでカメラ上の顔検出 -

4
Last updated at Posted at 2026-07-10

Claude Code だけでどこまでやれるかを試していく企画の第1回です。

カメラに映った顔を認識して、ランダムな「戦闘力」を表示する、あの「スカウター」風のアプリをFlutterで作ってみました。Claude Codeにプロンプトを投げながら試行錯誤した記録を、実際に使ったプロンプト付きで時系列にまとめます。

できたもの

カメラで人物の顔を検出し、検出した顔にスカウター風のフレームと「戦闘力」をオーバーレイ表示します。同じ顔には同じ戦闘力を表示するようトラッキングIDで固定し、インカメラ/アウトカメラの切り替えにも対応しました。検出した顔には自動でカメラのフォーカスを合わせ、スカウター表示ごと写真として保存できます。

c30eacb2-6533-423e-9fba-a72ab7687e32.png

所要時間は30分でした。

使用パッケージ

パッケージ 用途
camera カメラ起動・画像ストリーム取得・フォーカス制御
google_mlkit_face_detection オンデバイス顔検出
gal 撮影した画像を端末の写真ライブラリへ保存

「戦闘力」自体はランダム生成なので、機械学習が担うのは顔検出だけです。

開発の流れ

事前に別チャットで固めていた実装イメージ(コード付きの企画メモ)をClaude Codeに渡すところからスタートしました。

Claude Codeをflutter用のプロジェクトフォルダで起動して、こう投げてみます。

プロンプト:
顔認識で、⚪︎⚪︎⚪︎⚪︎⚪︎⚪︎⚪︎のようなスカウターでランダムな強さを表示

⚪︎⚪︎⚪︎⚪︎⚪︎⚪︎⚪︎は某を指定しています。

新規プロジェクトとして作成

尋ねられたので、新規プロジェクトとして作成する選択肢を選びました。

プロンプト:
新規プロジェクトとして作成する

既存のアプリとは別に、flutter createで新規プロジェクトを作成してもらい、企画メモのコードをベースに実装してもらいました。flutter analyze・iOS/Androidのビルドまで一通り自動で確認してくれたので、この時点ではエラーなし。これだけで面倒な手間をかけずに新規プロジェクトを作ってもらえました。5分ほどでベータ版が出来上がりました。

実機で動かしてみる

プロンプト:
xcodeを起動

Xcodeのワークスペースを開いて実行してみると、こうなりました。

起動しましたが、画面が真っ白です

ログを見るとFigCaptureSourceRemote関連のエラーが出ていました。これはiOSシミュレータには実カメラが無いための既知の制約で、コードの不具合ではありませんでした。ワイヤレス接続していた実機のiPhoneにデプロイし直して解決しました。

アウトカメラをデフォルトに

プロンプト:
アウトカメラにできますか?

初期実装ではインカメラ(前面)優先になっていたので、背面カメラを優先するよう変更しました。あわせて、後述の座標変換もインカメラ/アウトカメラ両対応にしています。

顔だけにフォーカスを合わせる

プロンプト:
フォーカスは顔だけに

検出した顔(複数いる場合は最も大きく写っている顔)の中心に、CameraController.setFocusPointでオートフォーカスを合わせるようにしました。

バグその一、フレームが体に当たる

プロンプト:
顔にフレームが合わさらないです、体の方に当たっています

原因は座標変換でした。ML Kitの顔検出結果(boundingBox)はiOSとAndroidで座標系の扱いが異なり、Androidと同じ変換ロジックをiOSにも使ってしまっていたのがズレの原因でした。Google公式サンプルの変換方式を参考に、プラットフォーム分岐で修正しました。

Offset _mapImagePointToCanvas({
  required Offset point,
  required Size imageSize,
  required Size canvasSize,
  required InputImageRotation rotation,
}) {
  switch (rotation) {
    case InputImageRotation.rotation90deg:
      return Offset(
        point.dx * canvasSize.width /
            (Platform.isIOS ? imageSize.width : imageSize.height),
        point.dy * canvasSize.height /
            (Platform.isIOS ? imageSize.height : imageSize.width),
      );
    // rotation270deg, rotation0/180deg も同様に分岐
  }
}

謎のクラッシュ

プロンプト:
インカメラとアウトカメラを切り替えるボタンを右下に

カメラ切り替えボタンを実装したあたりから、アイコンから直接アプリを起動すると即終了する現象に遭遇しました。Xcode経由のデバッグ実行では問題なく動くのに、単体起動だと落ちるという厄介なパターンです。

実機のクラッシュログ(.ips)をxcrun devicectlで取得して解析したところ、原因はFlutterエンジン内部のVSyncClientCADisplayLinkまわり)でのSIGSEGVでした。Flutter SDKが古く、端末の新しいiOSバージョンに未対応だったことが判明し、flutter upgradeでSDKを更新して解決しました。

自分のコードのバグではなく、フレームワークとOSの相性問題だったパターン。実機の生クラッシュログまで遡らないと分からなかった。

バグその二、画面端でフォーカスがズレる

プロンプト:
画面の端に行くほどフォーカスがずれます

scouter_1783670973173 (1).png

ずれがあったので、Claudeへの質疑応答で解決を探りました。

これも座標変換の話ですが、原因は別でした。カメラプレビューはBoxFit.coverで画面いっぱいに拡大・中央基準でクロップして表示しているのに、顔の座標変換はクロップを考慮せず「画像全体をそのまま画面全体に引き伸ばす」計算をしていたため、中心から離れるほど誤差が拡大していました。プレビュー座標へ変換してからクロップ後の画面座標へ変換する、という2段階に直して解決しました。

Offset _applyCoverFit({
  required Offset point,
  required Size sourceSize,
  required Size destSize,
}) {
  final scale = max(
    destSize.width / sourceSize.width,
    destSize.height / sourceSize.height,
  );
  final cropX = (sourceSize.width * scale - destSize.width) / 2;
  final cropY = (sourceSize.height * scale - destSize.height) / 2;
  return Offset(point.dx * scale - cropX, point.dy * scale - cropY);
}

なお、カメラの実フォーカス座標(setFocusPoint)はcameraパッケージのiOSネイティブ実装側で端末の向きに応じて自動回転される仕様でした。Dart側で余計な反転処理を入れていたのはむしろ逆効果で、ネイティブ側の実装を読みに行って気づいたポイントです。

写真として保存

プロンプト:
写真を撮って保存できるように

プロンプト:
スカウター表示ごと保存(推奨)

カメラ映像だけでなく、スカウターのフレームや戦闘力表示も含めて1枚の画像として保存したかったので、RepaintBoundaryで対象範囲を囲み、タップ時にtoImage()でキャプチャしてgalパッケージで写真ライブラリに保存する方式にしました。ボタン類はキャプチャ範囲の外に配置し、スクリーンショットに写り込まないようにしています。

動作確認

プロンプト:
ぴったりでした

midjourneyで/imagine マッチョな家族の集合写真」で生成した写真をPCモニタに映し、アプリで戦闘力の確認と撮影をしました。

fab026c9-5110-4810-8f66-1ad0f65f1d19.png

3d953e9d-e658-4fe8-816d-9b36bef3ba7f.png

実機で撮影して確認したところ、顔とスカウターフレームがぴったり一致するようになりました。

ハマったポイント

シミュレータで画面が真っ白になったのは、シミュレータに実カメラが無いためで、実機で確認すれば解決しました。フレームが体に当たっていたのは、iOSとAndroidでML Kitの座標系が違うことが原因で、プラットフォーム分岐で座標変換を修正しています。単体起動で即クラッシュしていたのはFlutter SDKが端末の新しいiOSに未対応だったからで、flutter upgradeで直りました。画面端でフォーカスがズレていたのはBoxFit.coverのクロップを座標変換で考慮していなかったのが原因で、プレビュー座標からクロップ後座標への2段変換に直して解決しています。

追記: debugビルドですと結局アプリアイコンから起動すると落ちるので、Claude曰く、releaseビルドが良いとのことでした。
そしてreleaseビルドでアプリ起動時に落ちなくなりました!

おわりに

30分ほどでここまで作成できました。品質の保証はまた別の話になりますが。

「戦闘力はランダムに決め打ち」というシンプルな仕組みでも、実機で正しく顔にフレームを合わせるところまで詰めようとすると、座標変換・OS差分・フレームワークの相性問題と、地味に骨のあるポイントが多く出てきました。特に単体起動クラッシュは実機のクラッシュログを取りに行かないと原因にたどり着けず、良い経験になりました。

UIにこだわれば、もう少しアレに近づくとは思いますが、ここまでで良しとしました。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?