N消し — OpenCV でナンバープレートを認識してマスクする

| 開発記録 | N消し

タグ: #OpenCV #画像処理 #Flutter

駐車場の写真に車が 10 台。ナンバープレートを手でぼかしていたら日が暮れます。OpenCV の形状検出でプレートを自動で見つけて、複数枚まとめてマスクするアプリを作りました。検出パイプラインとマルチプレート対応の設計をまとめます。

なぜナンバープレート消しか

SNS や中古車の掲載で、車のナンバープレートを手でぼかす。これが地味に面倒です。1 台ならまだいい。駐車場の写真に 10 台映っていたら、もう絶望的です。

この面倒を自動化する。それが「N消し」のコンセプトです。

OpenCV の形状検出

プレート検出は三段階です。エッジ検出、矩形検出、アスペクト比フィルタリングの順で進みます。日本のナンバープレートは規格化されていて、縦横比がほぼ一定です。そこが効きます。

1. Canny edge detection
2. findContours で輪郭抽出
3. approxPolyDP で多角形近似
4. 4 頂点・凸・特定アスペクト比のものをプレート候補とする

このパイプラインを Dart から呼ぶために、OpenCV の Dart バインディングを使いました。ネイティブ画像処理に比べると速度は劣ります。ただ、リアルタイム性が要求される処理ではないので問題ありません。

アスペクト比フィルタリングは「日本の標準ナンバー(横:縦 ≒ 2:1)」を中心に、少し幅を持たせて判定します。輪郭抽出から候補の絞り込みまでを、ひとつの関数にまとめると次のとおりです。

List<Rect> detectPlateCandidates(Mat gray) {
  final edges = Mat();
  cv.canny(gray, edges, 50, 150);

  final contours = <MatOfPoint>[];
  cv.findContours(edges, contours, RetrievalModes.list,
      ContourApproximationModes.simple);

  const targetRatio = 2.0; // 横:縦
  const ratioTolerance = 0.5; // ±25%
  final candidates = <Rect>[];

  for (final c in contours) {
    final approx = MatOfPoint2f();
    cv.approxPolyDP(c.toMatOfPoint2f(), approx, 0.02 * cv.arcLength(c, true),
        true);
    if (approx.total != 4 || !cv.isContourConvex(approx.toMatOfPoint())) {
      continue;
    }
    final rect = cv.boundingRect(c);
    if (rect.width < 60) continue; // 小さすぎる候補は捨てる
    final ratio = rect.width / rect.height;
    if ((ratio - targetRatio).abs() > ratioTolerance) continue;
    candidates.add(rect);
  }
  return candidates;
}

マルチプレート対応

初期版は「画像から 1 枚のプレートを検出」する仕様でした。でも駐車場のような複数台の写真にも対応したい(いちばん困る場面を後回しにしていました)。

サービス層に MultiPlateDetector を実装して、複数候補を返すようにしました。検出された全プレートをマスク対象として処理します。

カメラと写真選択

UI はカメラ撮影と写真選択の両方に対応しました。iOS の Info.plist と AndroidManifest.xml に、カメラと写真ライブラリの権限を入れています。

i18n 対応

グローバル展開を意識して、内部メッセージは全て国際化(i18n)対応にしました。arb ファイルで日本語と英語を管理しています。他言語も後から追加できる構成です。

教訓

  • OpenCV は使えるなら使う。自前で書くより圧倒的に楽
  • マルチ対応は最初から見据えてサービス層を設計する
  • 権限と i18n は早めに整える

「目で見て分かる結果」が出るアプリは、作っていて楽しいです。MVP の段階で「動く!」が見えるのが、画像処理の醍醐味だと思います。手でぼかしていた時間が、そのまま返ってきました。