アノテーションツールをClaudeに作らせて、自分の設計力を見直してみた
イントロ
ELEMENTS開発部AiQグループの森本です。私は、AiQ PERMISSIONというプロダクトの開発を担当しております。AiQ PERMISSIONは、セルフガソリンスタンドで義務化されている給油者の行動監視をAIが代替し、人手不足の解消や業務効率化、安全性の向上を目的としています。
AiQ PERMISSIONでは、設置しているカメラ映像から行動を検知して、給油者が不審な行動を対象のレーンに許可を出したり、給油を緊急停止したりします。
この処理を行う際に、ガソリンスタンドのカメラの映像情報と各レーンの番号の対応、並びに監視する範囲を指定するために、下記のオレンジ色や赤紫色の枠を設定し、アノテーション情報を付加する必要があります。
問題/課題
ツールを作成したのは2025年3月時点で、Vibe Codingで簡単なLPなどは作れるような状況でした。が、実際に業務をしていたら「なんか違う」出来のものができることがしばしばで、なかなか細かいコントロールは難しいところがあります。これらはAI開発において、今現在でも変わらず残っている問題かと思います。
LLMのコーディングスキルはジュニアレベルと言われており、LLM自体の設計能力面の話も課題の一つとして挙げられますが、このタスクでは、ドメイン知識などを私たちがいかにLLMに教えられるか、ということをこのアノテーションツール開発をする上で最も大きな課題と捉えていました。
そこで自分のドメイン伝達力を測るために以下のことを試してみました。
試したこと
当時のコーディングタスクで最適とされていたClaude-3.7-Sonnetを使って、「プロンプトを作成→コード生成→自身で評価」を1サイクルとして、アノテーションツールが何サイクルで完成するかを測ってみました。本記事ではこのサイクルは、受け入れをする自分が立ち上げから保存までスムーズに完了できるか定性的な評価で判断しています。
結果
当初作成したプロンプトでは、全くうまくいかず、14サイクルかかりました。(写真は、終盤のサイクルで形になったものをSlackで投稿したもの)
初めは以下のようなプロンプトから始まり、
pythonで画像に四角を描画するGUIを作っていただきたいです。
# 機能
1. フォルダを指定して、pngを一つ選択すると画像がキャンバスに表示される。(ex. ./hoge/piyo/fuga.png)
2. 1レーン、2レーンという選択ボタンがあり、ボタンを選択するとそれぞれ、パレットと呼ばれるものがレーンの数だけ生成される。パレットにはレーンの番号が振られている。
3. パレットを使うと1で表示された画像のキャンバスに矩形を描くことができる(パレットには、car, fire, polytank, fireというボタンがあり、あるボタンを一つ選択するとそのボタンに割り当てられた色で四角を描画できる)
4. 1-3はタブで移動ができる。各タブで1-3の機能がそれぞれ実装されており、タブを選択すると選択したタブに移動できる。
5. ウィンドウには一つのdownloadというボタンがあり、ボタンをおすとそれまでに描かれていた矩形の設定が添付されたyamlの例のようにダウンロードできる。
# 注意点
- 以降修正を加えたいのでなるべくコンポーネントを分けてください。
- poetryでアプリを管理できるようなディレクトリ構成にしてください。
最終的には以下のプロンプトに行きつきました。(10行→76行+yamlファイルに)
pythonで画像に四角を描画するGUI(aiq-cameras-yaml-annotator)を作っていただきたいです。
# 機能概要
Annotatorは以下の仕組みを提供する。
1. フォルダを選択してフォルダ中の画像に従って、ウィンドウを作成する。(初期化)
2. Paletteのボタンとマウス・トラックパット操作でCanvas内の図形を操作できる。
3. 編集された図形をyamlファイルにして出力することができる。
# 機能詳細
annotatorを構成するコンポーネントについて記載する。
## Main Window
- GUIが起動された時、ユーザーは画像フォルダを一つ選択する。画像フォルダの中にあるjpg画像をリスト化して、画像の数だけCameraTabを生成する。ウィンドウにはフォルダ名が一つだけ表示される。画像はIPアドレスが含まれており、読み込み時にipv4という変数に画像ファイル名に含まれたIPアドレスが代入される。(ex. ./images/folder1/192.168.0.10XXXYYXXX.jpg)
- Main Windowは一つのdownloadボタンを持つ。ボタンをおすとそれまでに描かれていたRectangleの設定が添付されたyamlのフォーマットでダウンロードできる。ファイルの出力先は、./images/folder1/cameras.yml である。Canvas上に表示されているRectangleのみがyamlに書き込まれる。
## TabWidget/CameraTab
- CameraTabをクリックすると選択したCameraTabに移動できる。CameraTabには、ipv4を昇順でソートして1から順に数字が付与されている。
- CameraTabは、Canvasをただ1つだけ持つ。
- CameraTabは、Paletteを1or2個もつ。
- CameraTabは、LaneButtonを1つ持つ。
## Canvas
- 初期化時に画像がCanvasに表示される。Canvasは(1280, 720)で固定。
- Canvas内にRectangleを描画することができる。
## LaneButton
- Paletteの個数を選択するためにレーン数選択ボタンを持つ。1レーン、2レーンという選択ボタンがあり、ボタンを選択するとそれぞれ、Paletteがレーンの数だけ表示される。
## Palette
- Paletteは、名前をつけることができる。名前入力フォームのタイトルは「レーン名」で、入力される名前は自然数でなければいけない。
- Paletteは、car, fire, polytankというカテゴリを持つ。カテゴリに紐づいた描画ボタンを一つ選択するとカテゴリに紐づいたRectangleは描画状態になる。描画状態のRectangleは各CameraTabで一つのみである。
- 描画状態では、Canvasでそのボタンに割り当てられた色でボタンに紐づいたRectangleを描画できる。つまり1つのPaletteで4種類,4つのRectangleを描ける。描画ボタンは1つのCameraTabで1つしか選択できない。
## Rectangle
Rectangleは、Canvas上にある枠線だけの矩形と矩形の左上に表示される{レーン名}_{カテゴリ}で表示されるラベルのペアである。以下の機能を持つ。
### 新規作成
- 初期化時にRectangleがCanvas内の適当な位置に描画されている。Paletteが表示されているときにRectangleはCanvas上で可視化される。
### 編集
- Rectangleが描画状態、かつカーソルが辺に触れている時、辺をクリックすると辺に垂直な向きにサイズを変更できる。(ex. 横辺をクリックしたら、縦辺の長さがカーソルの動きによって変わる)
- Rectangleが描画状態、かつカーソルが頂点に触れている時、頂点をクリックすると長方形の形をキープしたまま拡大縮小できる。
- Rectangleが描画状態の時、矩形の線をダブルクリックした際に、線が点線になり、ドラッグモードになる。ドラッグモードではRectangleの線を長押しするとドラッグできる。再度ダブルクリックすると元に戻る。
# 注意点
- 以降修正を加えたいのでなるべくコンポーネントを分けてください。(file読み込み, yaml読み込み, constants, データモデル, CameraTab、ウィンドウ、パレットなど)
- datamodelはpydanticを利用して、GUIはPyQt6をベースに作成してください。
- 機能1の画像の読み込みは専用のディレクトリ(images)をレポジトリ内に用意してください。
- コメントは日本語でお願いします。
- importは相対インポートを利用しないでください。
- YAML出力形式は添付ファイルのフォーマットに必ず従ってください。出力が添付ファイルのようにできているかtestsを書いてください。
# プロジェクト構成
下記のようにpoetryでアプリを管理できるようなディレクトリ構成にしてください。
aiq-cameras-yaml-annotator/
├── pyproject.toml # Poetry設定ファイル
├── README.md # このファイル
├── images/ # デフォルトの画像ディレクトリ
├── tests/ # テストディレクトリ
│ ├── __init__.py # パッケージ初期化
│ └── test_yaml_utils.py # yamlテスト
└── aiq_cameras_yaml_annotator/
├── __init__.py # パッケージ初期化
├── main.py # アプリケーションエントリーポイント
├── constants.py # 定数定義
├── models.py # Pydanticデータモデル
├── ui/ # UIコンポーネント
│ ├── __init__.py
│ ├── main_window.py
│ ├── tab_widget.py
│ ├── rectangle.py
│ ├── canvas.py
│ └── palette.py
└── utils/ # ユーティリティ関数
├── __init__.py
├── file_utils.py
└── yaml_utils.py
具体的に足した内容をいくつか挙げると以下のようなものがあります。
- PyQt6ベースでの実装に固定する。
- ボタンと、ボタンとレーン番号の集合であるパレット、描画するキャンバス、カメラごとのタブなど操作の流れをイメージしながら、UI上のオブジェクトをプロンプトに明示的に記載しました。(適宜出力されたソースコードにインスパイアされながら設計もできました。)
- 矩形操作の仕様 → 1クリック、2クリックでどの挙動を起こすのが適当かについては意外と細かく指定する必要がある。
- 入力フォーマットと出力フォーマットは、かなりプロンプトに細かく伝えました。ここはバグを起こしやすく7サイクル分は、この対応に時間を費やしました。
まとめ
アノテーションツールを作る過程で、「プロンプトを作成→コード生成→自身で評価」を1サイクルとした開発プロセスを取ってみて、自分の要件・仕様を言語化する能力を見直す方法を紹介しました。
Claudeからのフィードバックを見て、足りない情報があることに気づくことで、自分がいかに要件や仕様を言語化できていないかが把握できました。この取り組みを経て、自分のプロンプト作成力が大きく変わった実感があります。
動くものでフィードバックを得て自分の言語化力の不足している点に気づく、というのが特に良かったように思います。
自分たちの事業領域のドメインをうまく言語化できるか、要求・仕様を言語化できるかをチェックするきっかけとして、このプロセスをやってみるのをオススメしたいと思います。
Discussion