作成日:2023年6月2日
いままで私が実装してきたWebアプリでは、アップロードされた画像に対して、拡張子とファイルサイズの二点のみの検証しか行っていませんでした。
しかし、昨今のAI関連のOSSの発達で、バイナリ単位でのより高度な画像検証が簡単に実装可能になっており、画像に何が映っているのか等の高度な検証を、業務システム畑出身のど素人でも実装することができます。
今回、試しにアップロード画像に顔が映っているかどうかの検証をASP.netのカスタムバリデーションで実装していきます。
今回使用するライブラリは「OpenCvSharp」です。
インテルが開発・公開した画像処理の有名なOSSライブラリに「OpenCv」というものがあります。
20年ほど前から開発、バージョンアップが続いており、画像解析だけでなく、画像編集、生成も可能な万能ライブラリです。
「OpenCV」は主にPythonで動かすライブラリですが、「OpenCvSharp」はそれをC#で利用するためのラッパーです。
もちろんNugetからインストール可能です。
今回の環境は以下となります。(古いですがCore3.1を使っています。確信は無いですが.Net6,7でも動くと思います。)
NugetでOpenCVの必要パッケージをインストールします。
・OpenCvSharp4
・OpenCvSharp4.runtime.win
お恥ずかしながら私はAIについては全くの素人です。
画像から物体を検出するアルゴリズムについての知識は「ゼロ」です。
なので、「カスケード分類器」などと言われてもちっともぴんと来ないのですが、説明文だけ載せておきます。(間違ってたらすみません。)
特定の条件をプログラムが判別できるようにまとめたものを「カスケード分類器」といいます。
要するに、リンゴは「赤くて、丸くて、房が付いてて…」
みたいな情報をOpenCvが理解できるようにまとめたファイルみたいなものなのでしょう。
カスケード分類器は個人で自作も可能ですが、「人の顔」や「人の体」等の代表的なカスケード分類器はOpenCvのリポジトリからダウンロード可能です。ちなみにOpenCvのカスケード分類器はxml形式です。
下記リンクからダウンロードできます。
・openCv/haarcascades
今回は、人の顔を識別するので、「haarcascadefrontalfacedefault.xml」を使用します。
https://github.com/opencv/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xml
↑からファイルをダウンロードして、任意のフォルダに保存しておきます。
コピペして同名のファイルをローカルに作成すると、エラーになる可能性が高いです。なるべくダウンロードして使いましょう。
※私はプロジェクトフォルダの「Data/OpenCvSharp/」に保存しました。
カスタムバリデーションについて知識が無い方はこちらをご覧ください。
アノテーション一つで任意のフィールドにバリデーションルールを追加できます。
今回はローカルファイルではなくオンメモリでの画像読み込みなので、Mat.FromStream()
を使用してMemoryStreamを読み込んでいますが、その際の第二引数で、オプションを指定できます。
var mat = Mat.FromStream(ms,ImreadModes.Color);
オプションの説明についてはこちらで詳しい説明がされていますが、基本的には「UnChanged」で問題ないかと思います。
また、これはOpenCvSharpとは関係ないですが、ASP.netMVCでプロジェクトファイルのルートを取得する場合、IWebHostEnviromentをDIから取得する必要があります。
カスタムバリデーションからDIを取得するには、ValidationContext(DI)を引数で取得し、IWebHostEnviroment実装インスタンスを取得します。
protected override ValidationResult IsValid(object value,ValidationContext context)
以下がカスタムバリデーションのソースコードです。PhotoFaceValidationAttribute.cs
public class PhotoFaceValidationAttribute : ValidationAttribute
{
const String ERROR_MSG = "画像から顔を検出することができませんでした。";
protected override ValidationResult IsValid(object value,ValidationContext context)
{
//ファイルがnullの場合は検証をスルー
if(value==null) return ValidationResult.Success;
//FormFile形式での取得
var postedFile = value as IFormFile;
//カスケード分類器のパスを指定します。
var faceCascadeXml = System.IO.Path.Join(((IWebHostEnvironment)context.GetService(typeof(IWebHostEnvironment))).ContentRootPath, "Data","OpenCvSharp","haarcascade_frontalface_default.xml");
using (MemoryStream ms = new MemoryStream())
{
postedFile.CopyTo(ms);
//ストリーム現在位置を戻す
ms.Seek(0, SeekOrigin.Begin);
var mat = Mat.FromStream(ms,ImreadModes.Color);
//カスケード分類器の読み込み
using (CascadeClassifier cascade = new CascadeClassifier(faceCascadeXml))
{
//一人以上の顔が見つかったらOK
if (cascade.DetectMultiScale(mat).Length > 0)
{
return ValidationResult.Success;
}
}
}
return new ValidationResult(ERROR_MSG);
}
}
モデル
public class PhotoEntryViewModel
{
[PhotoFaceValidation()]
public IFormFile File { get; set; }
}
実行してみると、人の顔が映っていない画像の時のみ検証に失敗すると思います。
はじめてAIを用いた処理を実装しましたが、専門知識無しでも簡単に実装できることに感動しました。
OpenCvSharpは今後の汎用性も高いと思ったので、次は、カスケード分類器を自作して、好きな画像だけ検出するような処理も作っていけたらなと思います。