なりすまし判定SDKの組み込みに何行必要か【C++ / C# 実測】

なりすまし判定・ライブネス検知
📌 準備: OpenCV の環境構築がまだの方はこちら → C++ 環境構築ガイドC#(OpenCvSharp)セットアップ

なりすまし判定SDKの組み込みに何行必要か【C++ / C# 実測】

「顔認証にはもうなりすまし対策が要る」と分かっても、次に気になるのは導入コストです。既存システムにSDKを組み込むのに、実際どれくらいコードを書くことになるのか。

本記事では当社のなりすまし判定SDK(v2.3.0)を題材に、「初期化 → 判定 → 結果取得」に必要な実質コード行数を、同梱のサンプルアプリから実測します。

結論を先に書くと、C++ で 23 行 / C#(.NET 8)で 22 行。エラー処理と結果出力まで入れた「そのまま動くプログラム全体」でも C++ 40 行 / C# 37 行でした。


1. 数え方の定義

「何行」は数え方で 3 倍くらい変わるので、先に条件を固定します。

項目 本記事での扱い
対象 利用側(アプリ側)が新しく書くコード。SDK 本体の実装は対象外
数える行 空行・コメント行・閉じ括弧だけの行を除いた実行文
数えない行 画面描画・CSV 出力・カメラ制御・GUI・ログ出力
対象バージョン なりすまし判定SDK v2.3.0(2026-07-15 リリース・C++11 以降 / C# .NET 8)
出典 SDK 同梱のコンソールサンプルアプリ(C++ 版 / C# 版)から最小構成を抽出
検証 掲載コードは実際にコンパイルを通しています(C++: g++ -std=c++17 / C#: dotnet build net8.0)

「1 行」の定義次第で数字が動くので、最小構成と、動くプログラム全体の 2 つを出します。


2. SDK の構成(利用側から見た全体像)

当社SDKは C++ のインターフェース(C++11 互換)で提供され、C# からは P/Invoke ラッパーを介して呼び出します。ラッパーは SDK 側が用意しているので、利用側で書く必要はありません。

C#アプリ ──(SDK同梱の P/Invoke ラッパー)──> ネイティブSDK ──> OpenCV
                                                └──> なりすまし判定モデル(ONNX)

利用側から見た API は 3 つのクラスだけです。

クラス 役割
ActivationChallenge ライセンストークンの検証(起動時に 1 回)
LivenessCheck 初期化と 1 フレームの判定
BioData 判定結果の入れ物(process() のたびに中身が更新される)

判定結果は BioData から読みます。なりすまし対策として最小構成なら、読むのは「フェイクかどうか」の 1 つだけです。


3. コードを書く前の準備(ファイル配置)

コードより先に、配布物を所定の場所へ置きます。ここはコードを 1 行も書きません。

置くもの 内容
判定ライブラリ本体 Windows は DLL / Linux・Android は .so / iOS は framework
OpenCV ランタイム SDK がリンクしている OpenCV 4.10 系(同梱)
モデルファイル一式 判定に使う ONNX モデル
アクティベーショントークン ライセンス認証用のテキストファイル

C#(.NET 8)の場合はプロジェクト直下に sdk フォルダを作り、そこへライブラリ一式を置く形です。.csproj の設定でビルド時に出力先へコピーされるので、置き場所を決めるのは最初の 1 回だけです。

⚠️ Windows で C# のみの環境では VC++ 2015-2022 再頒布可能パッケージ (x64) が別途必要です。.NET の前提条件には含まれておらず、入れ忘れると起動時に DllNotFoundException(依存 DLL 解決失敗・0x8007007E)になります。ここが実際に一番詰まります。


4. C++ の最小コード(全文)

静止画 1 枚を判定して結果を出すまでの、そのまま動くプログラムです。

#include <opencv2/opencv.hpp>
#include <fstream>
#include <iostream>
#include "LivenessCheck.hpp"

using namespace sw::livenesscheck;

int main()
{
    // ---- ① アクティベーション(起動時に1回) ----
    std::ifstream ifs("token.txt");
    std::string token;
    std::getline(ifs, token);
    ActivationChallenge *ac = newActivationChallenge();
    const bool activated = ac->checkToken(token.c_str()) && ac->validToken(token.c_str());
    releaseActivationChallenge(ac);
    if (!activated) { std::cerr << "activation failed\n"; return 1; }

    // ---- ② パラメータ設定 ----
    ExternalFilePath filePath;
    filePath.faceDetectModel = "models/faceDetect.onnx";
    filePath.fakeModelH      = "models/livenessH.onnx";
    filePath.fakeModelV      = "models/livenessV.onnx";

    Params params{};                    // ★ {} で全メンバをゼロ初期化してから必要な分だけ設定
    params.minFaceWidth     = 80;
    params.maxFaceWidth     = 1000;
    params.faceAreaMinRatio = 0.05f;
    params.faceAreaMaxRatio = 0.65f;
    params.edgePosErrMode   = true;
    params.isFakeMode       = true;
    params.fakeJudgeTh      = 0.15f;

    // ---- ③ 初期化(起動時に1回) ----
    LivenessCheck *liveness = newLivenessCheck();
    std::shared_ptr<BioData> bio = std::make_shared<BioData>();
    if (!liveness->init(filePath, params)) { releaseLivenessCheck(liveness); return 1; }
    liveness->registerBioData(bio);

    // ---- ④ 判定(1フレームごと) ----
    cv::Mat frame = cv::imread("input.png", cv::IMREAD_COLOR);
    liveness->process(frame);

    // ---- ⑤ 結果の取得 ----
    if (!bio->isValidFace) {
        std::cout << "顔が有効に取れていません: " << bio->msg << "\n";
    } else if (bio->isFakeFace) {
        std::cout << "FAKE (likelihood=" << bio->isFakeLikelihood << ")\n";
    } else {
        std::cout << "REAL (likelihood=" << bio->isFakeLikelihood << ")\n";
    }

    // ---- ⑥ 後片付け ----
    bio->clear();
    releaseLivenessCheck(liveness);
    return 0;
}

⚠️ Params params{};{} は必須です。 Params は既定値を持たない素の構造体なので、Params params; と書くとメンバが未初期化のまま init() に渡り、minFaceWidth などに不定値が入ります。症状は「顔が全く検出されない」「毎回結果が違う」で、原因にたどり着きにくいタイプのバグです。

registerBioData()参照を登録するだけで、以降 process() を呼ぶたびに同じ BioData の中身が上書きされます。フレームごとに登録し直す必要はありません。


5. C#(.NET 8)の最小コード(全文)

同じ処理を C# で書いたものです。画像の読み込みには OpenCvSharp4 を使っています。

using System;
using System.IO;
using System.Runtime.InteropServices;
using OpenCvSharp;
using SwallowIncubate.LivenessCheck;

class Program
{
    static int Main()
    {
        // ---- ① アクティベーション(起動時に1回) ----
        string token = File.ReadAllText("token.txt").Trim();
        using var ac = new ActivationChallenge();
        if (!ac.CheckToken(token) || !ac.ValidToken(token))
        {
            Console.Error.WriteLine("activation failed");
            return 1;
        }

        // ---- ② パラメータ設定 ----
        var filePath = new ExternalFilePath
        {
            FaceDetectModel = "models/faceDetect.onnx",
            FakeModelH      = "models/livenessH.onnx",
            FakeModelV      = "models/livenessV.onnx",
        };
        var p = new Params
        {
            MinFaceWidth = 80, MaxFaceWidth = 1000,
            FaceAreaMinRatio = 0.05f, FaceAreaMaxRatio = 0.65f,
            EdgePosErrMode = true,
            IsFakeMode = true, FakeJudgeTh = 0.15f,
            // ★ 既定値は true。モデルパスを渡さない機能は明示的に false にする
            IsFaceDirMode = false, IsGlassesMode = false,
            IsMaskMode = false, IsEyeMode = false, IsEyeDirMode = false,
        };

        // ---- ③ 初期化(起動時に1回) ----
        using var liveness = new LivenessCheck();
        using var bio = new BioData();
        if (!liveness.Init(filePath, p)) return 1;
        liveness.RegisterBioData(bio);

        // ---- ④ 判定(1フレームごと) ----
        using var src = Cv2.ImRead("input.png", ImreadModes.Color);
        byte[] pixels = new byte[src.Total() * src.ElemSize()];
        Marshal.Copy(src.Data, pixels, 0, pixels.Length);
        liveness.Process(pixels, src.Cols, src.Rows);

        // ---- ⑤ 結果の取得 ----
        if (!bio.IsValidFace)
            Console.WriteLine($"顔が有効に取れていません: {bio.Msg}");
        else
            Console.WriteLine($"{(bio.IsFakeFace ? "FAKE" : "REAL")} (likelihood={bio.FakeLikelihood:F4})");

        // ---- ⑥ 後片付け ----
        bio.Clear();
        return 0;
    }
}

C++ 版と違うのは 2 点だけです。

① 各種フラグを明示的に false にする必要がある。 C# の Params は各プロパティに既定値を持っており、Is***Mode がすべて true から始まります。使わない機能を false にしないと、モデルパスが空のまま初期化されて失敗します。C++ 側は {} でゼロ初期化=全部 false なので、両言語で初期状態が逆です。ここは移植時に必ず踏みます。

② 画像をバイト配列で渡す。 ネイティブ側に cv::Mat をそのまま渡せないので、BGR のバイト配列にコピーしてから Process() に渡します。Cv2.ImRead が返す Mat は連続領域なので Marshal.Copy で一括コピーできます。カメラや動画からの Mat を扱う場合は連続でないことがあるため、IsContinuous() を確認するか Clone() してから渡してください。


6. 実測結果 — 実質コード行数

上のコードから、空行・コメント・閉じ括弧だけの行を除いて数えた結果です。

処理 C++ C#(.NET 8)
① アクティベーション(トークン検証) 3 行 2 行
② パラメータ設定(モデルパス 3 + 判定パラメータ) 12 行 11 行
③ 初期化(インスタンス生成 + init + BioData 登録) 4 行 4 行
④ 1 フレームの判定呼び出し 1 行 3 行
⑤ 結果の取得(フェイクか否か) 1 行 1 行
⑥ 後片付け 2 行 1 行
実質合計(最小構成) 23 行 22 行
(参考)エラー処理・結果出力込みの動くプログラム全体 40 行 37 行

読み取れることが 3 つあります。

① 半分はパラメータ設定。 23 行のうち 12 行が ExternalFilePathParams の代入です。ここは定数を並べているだけなので、実質は設定ファイルの類です。1 回書けば以降触りません。

② 毎フレーム走るコードは C++ で 2 行。 process() に渡して isFakeFace を読む、それだけです。C# は画像をバイト配列に詰め替える分だけ 2 行増えて 4 行になります。

③ 判定 1 回あたりに増えるコードは 1 行。 既存システムのループに入れるコードという意味では、実装コストの本体は「初期化をどこに置くか」の設計であって、行数ではありません。


7. サンプルアプリ全体との差

「サンプルアプリを開いたら 1,000 行以上あった」となると身構えますが、その大半はなりすまし判定と関係のないコードです。SDK 同梱の C++ コンソールサンプル(利用側コードのみ・同じ数え方)を分類すると次のようになります。

内訳 行数 割合
判定結果の画面描画・ライブネス判定の UI ロジック 509 行 41%
CSV エクスポート 247 行 20%
カメラ入力・動画入力の制御 245 行 20%
モード切替・設定・タイムスタンプ・ディレクトリ操作ほか 129 行 10%
静止画入力のメイン(SDK の呼び出しを含む 70 行 6%
アクティベーション 36 行 3%
合計 1,236 行 100%

このうち、なりすまし判定を動かすために本質的に必要なのは、下 2 行(静止画入力のメイン 70 行+アクティベーション 36 行)から抽出できる 23 行=全体の約 2% です。残りの 98% は「デモとして見せるための実装」です。

C# 版も同じ構造で、コンソールサンプルの利用側コード 756 行のうち 359 行(47%)が結果描画(OSD)です。加えて P/Invoke ラッパー 305 行は SDK が用意しているものなので、利用側で書く必要はありません。

つまりサンプルアプリの分量は組み込みの分量ではありません。既存システムに組み込むときは、この 2% を自分のコードへ移植することになります。


8. 「後付けできる」ことの意味

既存の顔認証システムに組み込む場合、顔照合のパイプラインはそのまま残し、判定の直前に 1 ステップ挟むだけです。顔認証を作り直す必要はありません。

既存:   顔検出 → 顔照合 → 認証OK
後付け: 顔検出 → [なりすまし判定] → 顔照合 → 認証OK
                    ↑ ここに毎フレーム 2 行(C++)

この「数行で挟める」構造が、印刷写真で突破される既存システムへの現実的な対策になります。具体的な差し込み方は既存の顔認証になりすまし判定を後付けするで扱っています。

なお、行数が少ないことと導入が軽いことはイコールではありません。実際に時間を使うのは次の 3 つです。

  • 配布物の配置とランタイム依存の解決(前述の VC++ 再頒布パッケージなど)
  • 自分の運用環境での閾値の決め方 — 判定しきい値をどこに置くかは、APCER と BPCER のトレードオフを、自分の測定条件(カメラ・照明・距離・攻撃サンプル)で測って決める話です
  • NG 判定時の UX 設計 — 「偽物と判定された」をユーザーにどう見せ、どうリトライさせるか

コードは 23 行でも、この 3 つは別途必要です。ここを見積もらずに「数行だから明日入る」と考えると、後から詰まります。


9. つまずきポイント

  • Params の未初期化(C++)Params params; ではなく Params params{};。未初期化だと顔が検出されない・結果が安定しない
  • Is***Mode の初期値が言語で逆(C#) — C# は既定 true。使わない機能は明示的に false にしないと初期化に失敗する
  • VC++ 再頒布可能パッケージの入れ忘れ(Windows / C#)DllNotFoundException / 0x8007007E。エラーメッセージが「SDK が無い」ように見えるが、実際は依存 DLL 側
  • Mat が連続領域でない(C#) — カメラ・動画から取った Mat をそのままバイト配列に詰めると崩れる。Clone() するのが安全
  • バージョン移行時の顔検出モデル指定 — v2.3.0 で顔検出モデルの指定が単一の ONNX ファイルにまとまり、旧バージョンにあった検出用パラメータファイルの指定が不要になりました。旧版から上げる場合は、その代入行を削除しないとコンパイルが通りません

10. まとめ

  • 「初期化 → 判定 → 結果取得」の実質コード行数は C++ 23 行 / C#(.NET 8)22 行。エラー処理・結果出力込みの動くプログラム全体でも 40 行 / 37 行
  • そのうち 半分(12 行)はパラメータ設定。毎フレーム走るのは C++ 2 行 / C# 4 行
  • サンプルアプリが 1,236 行あるのは描画・CSV・カメラ制御のためで、なりすまし判定そのものは全体の約 2%
  • 既存の顔認証を作り直さず、判定の直前に 1 ステップ挟むだけで後付けできる
  • ただし行数の少なさ=導入の軽さではない。ランタイム依存の解決・閾値決め・NG 時の UX は別途必要

組み込みコストを見積もるときは、「何行書くか」より「閾値を自環境でどう決めるか」に時間を配分してください。コードは 1 日で入りますが、閾値は実データを測らないと決まりません。

🛡 組み込みを試したい方へ

本ブログを運営するスワローインキュベートのなりすまし判定SDKは、サンプルアプリ(C++ / C#)付きで提供しています。既存の顔認証に数行で後付けでき、CPU推論・オフライン動作。試用のご相談を承っています。

→ なりすまし判定SDK/APIの詳細を見る
→ ご利用ガイド・サンプルコード

タイトルとURLをコピーしました