ある朝、Google Play から「ポリシー違反の可能性」というメールが届いて、その日の作業予定が静かに崩れたことがあります。原因は、壁紙のバッチに紛れていた一枚に、商標らしきロゴが小さく写り込んでいたことでした。自分の目では見落としていた要素です。
2014 年から個人開発で壁紙アプリを続けてきて、累計 5,000 万ダウンロードを超える規模になりましたが、提出前のチェックはずっと手作業でした。一枚ずつ目視で確認していく作業は、枚数が増えるほど集中力が削られていきます。そこで「提出前に Gemini の画像理解を一次フィルターとして挟んだら、どこまで負担が減るか」を 2 週間かけて試しました。その実測を残しておきます。
なぜ目視チェックだけでは足りなくなったのか
壁紙アプリは、一度の更新で数十枚から多いときは百枚以上を一気に提出します。一枚あたりにかけられる確認時間はごく短く、しかも審査でひっかかる要素は毎回同じではありません。著作物の写り込み、過度な露出、暴力的なモチーフ、誤解を招くテキスト。観点が複数あるうえに、人間の注意は単調な作業のなかで確実に落ちていきます。
宮大工だった祖父は、木を組む前に必ず一本ずつ材を確かめていたそうです。手を動かして確かめることそのものが、ひとつの信心のようなものだったと聞いています。私もチェックという行為を軽んじたくはないのですが、目視だけに頼ると、肝心なときに見落とすという現実がありました。だからこそ、人間の注意を補う一次フィルターが欲しかったのです。
Gemini に何を見させたか
私が組んだのは、提出フォルダの全画像を Gemini に渡し、あらかじめ決めた観点ごとに「リスクあり / なし / 要確認」を構造化出力で返させる仕組みです。モデルは応答が速くコストの低い Flash 系を選びました。一枚あたりのコストが小さいので、百枚単位でも気軽に回せます。
import google.generativeai as genai
from pydantic import BaseModel
from enum import Enum
class Risk(str, Enum):
none = "none"
review = "review"
high = "high"
class Verdict(BaseModel):
trademark_or_logo: Risk
explicit_content: Risk
violence: Risk
misleading_text: Risk
note: str
model = genai.GenerativeModel("gemini-2.5-flash")
def screen(image_path: str) -> Verdict:
img = genai.upload_file(image_path)
prompt = (
"あなたはモバイルアプリのストア審査担当の視点で画像を点検します。"
"商標やロゴの写り込み、露出、暴力的モチーフ、誤解を招くテキストの"
"4観点を none / review / high で評価し、理由を note に簡潔に書いてください。"
)
res = model.generate_content(
[prompt, img],
generation_config={"response_mime_type": "application/json",
"response_schema": Verdict},
)
return Verdict.model_validate_json(res.text)出力を JSON スキーマで固定したのは、後段でそのまま集計したかったからです。high が一枚でもあればそのバッチは私の目視に回し、review は理由を読んでから判断、none だけのものは通す、という三段の運用にしました。
ぶつかった壁
最初の数日は、誤検知の多さに戸惑いました。特に「誤解を招くテキスト」の観点が敏感で、壁紙に入っている装飾的な英字をブランド名と読み違えるケースが続きました。観点の説明を具体化し、「実在ブランドと判別できる場合のみ high」と条件を添えてから、ノイズは目に見えて減りました。プロンプトを一行足すだけで挙動が変わる感覚は、API を触っている醍醐味でもあります。
逆に苦手だったのは、構図全体から受ける印象の判定です。個々の要素は問題なくても、組み合わせると審査側に誤解されかねない一枚がありました。そこは Gemini も none と返しており、最終的に私が引っかかりを覚えて差し替えました。要素分解は得意でも、全体の空気を読む判断は人間の役割が残ると感じた場面です。
2週間で見えた効果と限界
数字で見ると、目視に回す枚数は体感で半分以下になりました。none だけのバッチは安心して通せるので、確認の集中力を review と high に振り向けられます。冒頭で書いたようなロゴの写り込みも、テストとして過去のバッチを流し直したところ、当時見落とした一枚を high で拾い直してくれました。
一方で、これを「審査の自動化」と呼ぶ気にはなりません。Gemini が拾えるのは、あくまで言語化できる観点に分解した範囲です。ストアのポリシーは細部が更新されますし、文化的な機微を含む判断は今も私が担っています。AdMob を含む収益基盤を預けているアプリだからこそ、最後の判断は手放さないと決めています。Gemini は判断を代わってくれる相手ではなく、私の注意力を必要なところへ回してくれる助手だ、という距離感が一番しっくりきました。
コストと処理時間の実測
気になるのは費用だと思いますので、実測を書いておきます。Flash 系で 1 枚あたりの画像入力は数百トークン規模に収まり、100 枚のバッチを一度回しても、私の手元では一杯のコーヒーよりずっと安い水準でした。提出のたびに走らせても収益を圧迫しない金額なので、心理的なハードルなく日課に組み込めています。
処理時間は、100 枚を 8 並列で投げて数分というところでした。並列数は無料枠と有料枠でレート制限が違うので、429 が返り始めたら同時実行数を落とす単純なバックオフを入れてあります。ここは凝った作りにせず、失敗した画像だけを後で拾い直す素朴な仕組みにしました。提出前の限られた時間で回す道具なので、壊れにくさを最優先しています。
実運用で効いたのは、観点ごとに high の基準をアプリ単位で少しずつ変えられるようにしたことです。たとえば子ども向けの色彩中心のアプリでは露出の観点をより厳しめに、抽象パターン中心のアプリでは商標の観点を重めに、と重みを変えています。同じプロンプトを全アプリに使い回さず、App Store と Google Play それぞれのこれまでの指摘傾向を反映させると、review の精度が体感で上がりました。
私の場合、note に書かれた理由文を一覧で眺める時間を一日の終わりに 5 分だけ設けました。理由を読むうちに、自分がどの観点を無意識に軽視していたかが見えてきます。たとえば私は露出にばかり気を取られて、テキストの誤読リスクを過小評価しがちでした。Gemini の判定そのものよりも、判定理由を読む習慣のほうが、結果的に自分の審査眼を鍛えてくれた気がしています。
もうひとつ、地味ですが効いたのは、提出フォルダの構成自体を見直したことです。点検済みの画像を passed に移すだけで、どこまで確認したかが一目で分かり、中断しても再開しやすくなりました。道具を変えると、その前後の手順まで少しずつ整っていくのは面白いところです。
これから試すこと
次は、review と判定された理由文を蓄積して、自分の差し替え判断と突き合わせる仕組みを作ろうとしています。どの観点で Gemini と私の判断がずれるかが見えれば、プロンプトをさらに自分のアプリの基準へ寄せられるはずです。同じように大量の画像をストアへ出している個人開発者の方には、一次フィルターとしての画像理解は十分に実用の段階に来ていると伝えたいです。
最後までお付き合いいただき、ありがとうございました。