OpenCVだけで顔認証を実装する【YuNet + SFace・C++】〜C#(OpenCvSharp)の現状も実測解説〜
「顔認証を試したい。でも顔認証クラウドAPIは使えない/使いたくない」——実はその用途、OpenCV 本体だけで一通り実装できます。OpenCV 4.5.4 以降には顔検出モデル YuNet(cv::FaceDetectorYN)と顔照合モデル SFace(cv::FaceRecognizerSF)が標準搭載されており、追加ライブラリなしで「登録画像と目の前の顔が同一人物か」を判定するパイプラインが組めます。
検出(YuNet)→ 整列・切り出し → 特徴量抽出(SFace)→ コサイン類似度で照合
本記事では C++ の動くコードでこの4ステップを最短で実装します。あわせて、C#(OpenCvSharp)では現状どこまでできるのか(結論: 検出は OpenCvSharp4 / OpenCvSharp5 の両方で可能・照合は OpenCvSharp5 だけがラップ済み)を実測ベースで解説します。
動作環境
- OpenCV 4.5.4 以降(本記事は 4.10 系で確認。
cv::FaceDetectorYN/cv::FaceRecognizerSFは 4.5.4 で追加) - C++17 / .NET 8(OpenCvSharp4)
- コンパイル例:
g++ -std=c++17 main.cpp $(pkg-config --cflags --libs opencv4)
環境構築がまだの方は環境構築ガイドを先にどうぞ。
モデルファイルの入手
2つの ONNX モデルを opencv_zoo からダウンロードし、実行ファイルと同じディレクトリに置きます。
| モデル | ファイル | 役割 |
|---|---|---|
| YuNet | face_detection_yunet_2023mar.onnx |
顔検出(位置 + 5点ランドマーク) |
| SFace | face_recognition_sface_2021dec.onnx |
顔照合(128次元特徴量) |
どちらも軽量なモデルで(YuNet は約230KB、SFace は約37MB)、GPU なしの CPU 推論で実用速度が出ます。
※ OpenCV Zoo の既定は現在 face_detection_yunet_2026may.onnx(入力サイズ可変・OpenCV 5.x 向け)になっています。OpenCV 4.x では本記事と同じ face_detection_yunet_2023mar.onnx を使ってください。
C++ 実装(全コード)
登録画像 enroll.jpg と照合画像 query.jpg を比較し、同一人物かを判定するコンソールプログラムです。
#include <opencv2/opencv.hpp>
#include <iostream>
int main(int argc, char** argv)
{
if (argc < 3) {
std::cerr << "usage: ./face_auth <enroll.jpg> <query.jpg>" << std::endl;
return -1;
}
// 1. モデルを読み込む
auto detector = cv::FaceDetectorYN::create(
"face_detection_yunet_2023mar.onnx", "", cv::Size(320, 320),
0.9f, // スコア閾値
0.3f, // NMS 閾値
5000); // 上位K件
auto recognizer = cv::FaceRecognizerSF::create(
"face_recognition_sface_2021dec.onnx", "");
// 2. 画像を読み込む
cv::Mat enroll = cv::imread(argv[1]);
cv::Mat query = cv::imread(argv[2]);
if (enroll.empty() || query.empty()) {
std::cerr << "画像の読み込みに失敗しました" << std::endl;
return -1;
}
// 3. 顔を検出する(入力サイズは画像ごとに設定し直す)
cv::Mat facesEnroll, facesQuery;
detector->setInputSize(enroll.size());
detector->detect(enroll, facesEnroll);
detector->setInputSize(query.size());
detector->detect(query, facesQuery);
if (facesEnroll.rows < 1 || facesQuery.rows < 1) {
std::cerr << "顔が検出できませんでした" << std::endl;
return -1;
}
// 4. 顔を整列・切り出しして特徴量を抽出する
cv::Mat aligned1, aligned2, feat1, feat2;
recognizer->alignCrop(enroll, facesEnroll.row(0), aligned1);
recognizer->feature(aligned1, feat1);
feat1 = feat1.clone(); // feature() の出力は内部バッファ参照。clone しないと次の呼び出しで上書きされる
recognizer->alignCrop(query, facesQuery.row(0), aligned2);
recognizer->feature(aligned2, feat2);
feat2 = feat2.clone();
// 5. コサイン類似度で照合する
double score = recognizer->match(feat1, feat2,
cv::FaceRecognizerSF::DisType::FR_COSINE);
const double kThreshold = 0.363; // OpenCV 公式サンプルの推奨閾値
std::cout << "cosine similarity = " << score << std::endl;
std::cout << (score >= kThreshold ? "SAME person" : "DIFFERENT person") << std::endl;
return 0;
}
実行結果
$ ./face_auth enroll.jpg query_same_person.jpg
cosine similarity = 0.6~0.9 程度(同一人物の別写真)
SAME person
$ ./face_auth enroll.jpg query_other_person.jpg
cosine similarity = 0.363 を大きく下回る値
DIFFERENT person
各ステップの解説
| ステップ | ポイント |
|---|---|
FaceDetectorYN::create |
第2引数(config)は ONNX では空文字。Size(320,320) は後で setInputSize で上書きするので初期値でよい |
setInputSize |
前の画像とサイズが違う画像ごとに必ず呼ぶ。入力サイズが合っていないと detect が cv::Exception(”Size does not match. Call setInputSize(size) if input size does not match the preset size”)を投げて止まる |
detect |
結果は N×15 の cv::Mat(x, y, w, h + ランドマーク5点×2 + スコア)。1行が1つの顔 |
alignCrop |
検出結果のランドマークを使って顔を正規化・切り出し。SFace はこの整列を前提に学習されているため省略不可 |
feature |
128次元の特徴量ベクトルを出力 |
match |
FR_COSINE でコサイン類似度。0.363 以上なら同一人物(公式サンプルの閾値) |
C#(OpenCvSharp)ではどうか — パッケージの系統で答えが変わる【実測】
「同じことを OpenCvSharp でやりたい」という C# 開発者は多いはずです。先に知っておきたいのは、OpenCvSharp が現在2つの系統で配布されていることです。OpenCvSharp4(4.13.x・OpenCV 4.13 をラップ・保守中の系統)と OpenCvSharp5(5.0.x・OpenCV 5 をラップ・.NET 8 以降・開発が進んでいる系統)です。筆者は OpenCvSharp4 4.13.0.20260627 で実測し、同じ結論が OpenCvSharp4 の最新 4.13.0.20261107 でも変わらないことを確認しました。結論を先に書きます。
| 機能 | C++(OpenCV本体) | C# OpenCvSharp4(4.13) | C# OpenCvSharp5(5.0) |
|---|---|---|---|
| 顔検出(YuNet) | cv::FaceDetectorYN |
✅ FaceDetectorYN あり(SetInputSize は無い。Create 時にサイズを渡す) |
✅ FaceDetectorYN あり(SetInputSize もある) |
| 顔照合(SFace) | cv::FaceRecognizerSF |
❌ ラップされていない(FaceRecognizerSF クラスが存在しない) |
✅ FaceRecognizerSF あり(AlignCrop / Feature / Match) |
OpenCvSharp4 の OpenCvSharp.Face 名前空間にあるのは Eigen / Fisher / LBPH といった旧世代の顔認識クラスで、SFace は含まれていません。つまり OpenCvSharp4 だけでは、この記事の照合パイプラインを完結できません。.NET 8 と OpenCV 5 に移れるなら、OpenCvSharp5 には FaceRecognizerSF があり、上の C++ の流れをそのまま AlignCrop / Feature / Match に置き換えられます。なお OpenCvSharp5 での一連の動作は筆者はまだ通しで確認していないため、「使える」までの記載にとどめます。
検出(YuNet)までは OpenCvSharp4 でも動く
using System;
using OpenCvSharp;
class DetectFace
{
static void Main()
{
using var img = Cv2.ImRead("sample.jpg");
if (img.Empty())
{
Console.Error.WriteLine("sample.jpg が読み込めません");
return;
}
// OpenCvSharp4 には C++ 版の setInputSize が無いため、
// Create 時の inputSize を画像サイズに合わせて指定する
using var detector = FaceDetectorYN.Create(
"face_detection_yunet_2023mar.onnx", "",
new Size(img.Width, img.Height), 0.9f, 0.3f, 5000);
using var faces = new Mat();
detector.Detect(img, faces);
Console.WriteLine($"検出された顔: {faces.Rows}");
for (int i = 0; i < faces.Rows; i++)
{
var box = new Rect((int)faces.At<float>(i, 0), (int)faces.At<float>(i, 1),
(int)faces.At<float>(i, 2), (int)faces.At<float>(i, 3));
Cv2.Rectangle(img, box, Scalar.LimeGreen, 2);
}
Cv2.ImWrite("result.jpg", img);
}
}
照合まで C# でやりたい場合の選択肢
- OpenCvSharp5 を使う — .NET 8 と OpenCV 5 を採用できるなら一番の近道です。
FaceRecognizerSFがラップされており、上の C++ のコードをほぼそのまま移植できます CvDnn.ReadNetFromOnnxで SFace の ONNX を直接推論する — 可能ですが、alignCrop相当の顔整列(ランドマークからのアフィン変換)を自前実装する必要があり、ここを省くと精度が大きく落ちます- 照合部分だけ C++ で書いて P/Invoke で呼ぶ — 確実ですが、ネイティブ相互運用の保守コストを抱えます
- 照合・判定を含む商用SDKを使う — 顔まわりの処理を C# から完結させたい場合の現実解です
C# 固有の注意として、Mat は using で確実に解放すること(特にループ内で作る Mat はリークしやすい)も覚えておいてください。
つまずきポイント
setInputSizeを忘れる / 使い回す — OpenCV はdetectのたびに入力サイズを確かめ、合っていなければcv::Exception(”Size does not match. Call setInputSize(size) if input size does not match the preset size”)を投げて止まります。フレームのサイズが変わったら設定し直してください(OpenCvSharp4 ではCreate時のinputSize指定で代替)feature()の結果を clone しない — 出力Matはモデル内部のバッファを参照しているため、次のfeature()呼び出しで静かに上書きされます。複数人の特徴量を保持する場合は必ずclone()alignCropを飛ばして顔領域をそのまま照合する — SFace は整列済み画像を前提に学習されており、精度が大きく落ちます- 閾値 0.363 を絶対視する — 公式サンプルの値であり、照明・カメラ・運用条件によって適切な値は変わります。実運用では自分のデータで誤受入・誤拒否のバランスを確認してください
- OpenCvSharp4 で SFace を探し続ける — 4.13 系ではラップされていません(4.13.0.20261107 まで確認)。C# で照合まで必要なら、OpenCvSharp5 に移るか、上記のほかの選択肢から選ぶことになります
ここで作った「顔認証」に、まだ足りないもの
このパイプラインは「登録された顔と同じ人物の顔が写っているか」には正しく答えます。ただし、それは「本人がその場にいるか」とは別の問いです。
実は、このコードのカメラの前に本人の顔写真を印刷した紙をかざしても、多くの場合「SAME person」と判定されます。写真には本人の顔特徴がそのまま写っているからで、これはバグではなく、顔照合という仕組みの設計上の性質です。
この問題は実際に手を動かすと一目で分かります → OpenCVで顔認証を実装したら、印刷した写真であっさり突破された【C++】
どんな攻撃パターンがあるかは → なりすまし攻撃(プレゼンテーション攻撃)の種類と、どこまでが現実的な脅威か
顔認証を業務で使う前に、なりすまし対策として何を決めておくべきかは → 顔認証システムのなりすまし対策 設計チェックリスト12項目
🛡 なりすまし対策をお探しの方へ
本ブログを運営するスワローインキュベートは、印刷写真・スマホ画面などによるなりすましを単一フレームから判定するなりすまし判定SDK/API(CPU推論・オフライン動作可)を開発しています。本記事のような顔認証パイプラインに後付けで組み込め、C#/.NET 8 にも正式対応——OpenCvSharp4 のままで照合・判定まで組みたい場合の、実用的な選択肢になります。
OpenCvSharp4 だけでは照合まで組めない C# 環境でも、REST API 版なら HTTPS で顔画像を1枚送るだけでフェイク判定を呼び出せます。
→ なりすまし判定SDK/APIの詳細を見る
→ API仕様書・サンプルコード
→ AWS Marketplace でなりすまし判定APIを試す(Freeプラン 月50回まで無料)
まとめ
- OpenCV 4.5.4 以降なら YuNet + SFace で顔認証パイプラインが本体だけで組める(C++・CPU で実用速度)
- 流れは「検出 → 整列 → 特徴量 → コサイン類似度」の4ステップ。
setInputSizeとfeature()の clone が定番の落とし穴 - C# では、検出(YuNet)は OpenCvSharp4 / OpenCvSharp5 の両方で動くが、照合(SFace)は OpenCvSharp5(OpenCV 5・.NET 8 以降)だけ。OpenCvSharp4 のままなら、照合には DNN 直接推論・P/Invoke・商用SDK のいずれかを選ぶ
- ただし顔照合は「同じ人物か」を判定する仕組みであり、「本人がその場にいるか」(ライブネス)は別問題。認証システムとして運用するなら、なりすまし対策を必ずセットで検討してください

