OpenCVだけで顔認証を実装する【YuNet + SFace・C++】〜C#(OpenCvSharp)の現状も実測解説〜
「顔認証を試したい。でも顔認証クラウドAPIは使えない/使いたくない」——実はその用途、OpenCV 本体だけで一通り実装できます。OpenCV 4.5.4 以降には顔検出モデル YuNet(cv::FaceDetectorYN)と顔照合モデル SFace(cv::FaceRecognizerSF)が標準搭載されており、追加ライブラリなしで「登録画像と目の前の顔が同一人物か」を判定するパイプラインが組めます。
検出(YuNet)→ 整列・切り出し → 特徴量抽出(SFace)→ コサイン類似度で照合
本記事では C++ の動くコードでこの4ステップを最短で実装します。あわせて、C#(OpenCvSharp)では現状どこまでできるのか(結論: 検出は可能・照合は未ラップ)を実測ベースで解説します。
動作環境
- 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次元特徴量) |
どちらも数MB程度の軽量モデルで、GPU なしの CPU 推論で実用速度が出ます。
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 |
結果は 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# 開発者は多いはずです。筆者が OpenCvSharp4 4.13.0.20260627(本記事執筆時点の最新) で実測した結論を先に書きます。
| 機能 | C++(OpenCV本体) | C#(OpenCvSharp) |
|---|---|---|
| 顔検出(YuNet) | cv::FaceDetectorYN |
✅ FaceDetectorYN あり(ただし SetInputSize 相当が無い) |
| 顔照合(SFace) | cv::FaceRecognizerSF |
❌ ラップされていない(FaceRecognizerSF クラスが存在しない) |
OpenCvSharp.Face 名前空間にあるのは Eigen / Fisher / LBPH といった旧世代の顔認識クラスで、SFace は含まれていません。つまり OpenCvSharp だけでは、この記事の照合パイプラインを完結できません(4.13 時点)。
検出(YuNet)までは C# でも動く
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;
}
// OpenCvSharp には 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# でやりたい場合の選択肢
CvDnn.ReadNetFromOnnxで SFace の ONNX を直接推論する — 可能ですが、alignCrop相当の顔整列(ランドマークからのアフィン変換)を自前実装する必要があり、ここを省くと精度が大きく落ちます- 照合部分だけ C++ で書いて P/Invoke で呼ぶ — 確実ですが、ネイティブ相互運用の保守コストを抱えます
- 照合・判定を含む商用SDKを使う — 顔まわりの処理を C# から完結させたい場合の現実解です
C# 固有の注意として、Mat は using で確実に解放すること(特にループ内で作る Mat はリークしやすい)も覚えておいてください。
つまずきポイント
setInputSizeを忘れる / 使い回す — 検出画像とサイズが一致していないと顔を取りこぼします。画像(フレーム)ごとに設定し直すのが安全です(OpenCvSharp ではCreate時のinputSize指定で代替)feature()の結果を clone しない — 出力Matはモデル内部のバッファを参照しているため、次のfeature()呼び出しで静かに上書きされます。複数人の特徴量を保持する場合は必ずclone()alignCropを飛ばして顔領域をそのまま照合する — SFace は整列済み画像を前提に学習されており、精度が大きく落ちます- 閾値 0.363 を絶対視する — 公式サンプルの値であり、照明・カメラ・運用条件によって適切な値は変わります。実運用では自分のデータで誤受入・誤拒否のバランスを確認してください
- OpenCvSharp で SFace を探し続ける — 4.13 時点でラップされていません。C# で照合まで必要なら上記の選択肢1〜3から選ぶことになります
ここで作った「顔認証」に、まだ足りないもの
このパイプラインは「登録された顔と同じ人物の顔が写っているか」には正しく答えます。ただし、それは「本人がその場にいるか」とは別の問いです。
実は、このコードのカメラの前に本人の顔写真を印刷した紙をかざしても、多くの場合「SAME person」と判定されます。写真には本人の顔特徴がそのまま写っているからで、これはバグではなく、顔照合という仕組みの設計上の性質です。
この問題は実際に手を動かすと一目で分かります → OpenCVで顔認証を実装したら、印刷した写真であっさり突破された【C++】
どんな攻撃パターンがあるかは → なりすまし攻撃(プレゼンテーション攻撃)の種類と、どこまでが現実的な脅威か
🛡 なりすまし対策をお探しの方へ
本ブログを運営するスワローインキュベートは、印刷写真・スマホ画面などによるなりすましを単一フレームから判定するなりすまし判定SDK/API(CPU推論・オフライン動作可)を開発しています。本記事のような顔認証パイプラインに後付けで組み込め、C#/.NET 8 にも正式対応——OpenCvSharp で照合・判定まで完結できない現状の、実用的な選択肢になります。
まとめ
- OpenCV 4.5.4 以降なら YuNet + SFace で顔認証パイプラインが本体だけで組める(C++・CPU で実用速度)
- 流れは「検出 → 整列 → 特徴量 → コサイン類似度」の4ステップ。
setInputSizeとfeature()の clone が定番の落とし穴 - C#(OpenCvSharp)は検出(YuNet)まで。SFace は 4.13 時点で未ラップのため、照合まで必要なら DNN 直接推論・P/Invoke・商用SDK のいずれかを選ぶ
- ただし顔照合は「同じ人物か」を判定する仕組みであり、「本人がその場にいるか」(ライブネス)は別問題。認証システムとして運用するなら、なりすまし対策を必ずセットで検討してください

