カメラ画像から顔認識するAndroidアプリケーションを作る ~Delphiソースコード集
顔認識の基本ユニット
unit UMamFaceDetect; interface uses //System.SysUtils System.Types, FMX.Graphics; type TFaceDetectData=record Rect:TRectF; //大体の顔の輪郭 LeftEye:TPointF; //左目座標 RightEye:TPointF; //右目座標 Distance:Single; //目の間隔 Confidence:Single;//信頼度 0~1 //X,Y,Z軸に対する顔の傾き 0.0しか返さない EULER_X,EULER_Y,EULER_Z:Single; end; TFaceDetectDataArr=TArray<TFaceDetectData>; function FaceDetectAndroid(SrcBmp:TBitmap;MaxFaceCount:Integer):TFaceDetectDataArr; implementation uses FMX.Types ,System.UITypes ,Androidapi.JNI.Media, Androidapi.Bitmap ,Androidapi.JNIBridge, Androidapi.JNI ,Androidapi.JNI.GraphicsContentViewText; function FaceDetectAndroid(SrcBmp:TBitmap;MaxFaceCount:Integer):TFaceDetectDataArr; var SrcBmpData: TBitmapData; FaceDetector: JFaceDetector; jBmp:JBitmap; Src: PCardinal;//32bit dst: PWord; //16bit r,g,b:Byte; x,y:integer; ac:TAlphaColor; i, FaceCount:Integer; SrcFace :JFaceDetector_Face; SrcFaces:TJavaObjectArray<JFaceDetector_Face>; JPt: JPointF; DstFace: TFaceDetectData; const EULER_X:integer=0; EULER_Y:integer=1; EULER_Z:integer=2; begin FaceDetector:=TJFaceDetector.JavaClass.init( SrcBmp.Width, SrcBmp.Height, MaxFaceCount ); jBmp := TJBitmap.JavaClass.createBitmap( SrcBmp.Width, SrcBmp.Height, TJBitmap_Config.JavaClass.RGB_565); AndroidBitmap_lockPixels( TJNIResolver.GetJNIEnv, (jBmp as ILocalObject).GetObjectID, @dst); SrcBmp.Map(TMapAccess.Read, SrcBmpData); try for y:=0 to SrcBmp.Height - 1 do begin for x:=0 to SrcBmp.Width - 1 do begin ac:=SrcBmpData.GetPixel(x,y); r := (ac shr (16 + 3)) and $1F; //上位5ビット取得 g := (ac shr ( 8 + 2)) and $3F; //上位6ビット取得 b := (ac shr ( 0 + 3)) and $1F; //上位5ビット取得 dst^ := (r shl 11) + (g shl 5) + b;//16ビットに構成 Inc(dst); end; end; finally SrcBmp.Unmap(SrcBmpData); end; AndroidBitmap_unlockPixels(TJNIResolver.GetJNIEnv, (JBmp as ILocalObject).GetObjectID); SrcFaces := TJavaObjectArray<JFaceDetector_Face>.Create(MaxFaceCount); FaceCount := FaceDetector.findFaces(JBmp, SrcFaces); SetLength(Result, FaceCount); jPt := TJPointF.Create; for i := 0 to FaceCount - 1 do begin SrcFace := TJFaceDetector_Face.Wrap(SrcFaces.GetRawItem(i)); //左目と右目の間の中心の座標 SrcFace.getMidPoint(jPt); //目と目の距離 DstFace.Distance := SrcFace.eyesDistance; DstFace.LeftEye := PointF(jPt.x- 0.5* DstFace.Distance, jPt.y); DstFace.RightEye := PointF(jPt.x+ 0.5* DstFace.Distance, jPt.y); //以下、適当に調整 DstFace.Rect := RectF( jPt.x-DstFace.Distance*1.2, jPt.y-0.7*DstFace.Distance, jPt.x+DstFace.Distance*1.2, jPt.y+1.8*DstFace.Distance); DstFace.Confidence:=SrcFace.confidence; DstFace.EULER_X:=SrcFace.pose(EULER_X); DstFace.EULER_Y:=SrcFace.pose(EULER_Y); DstFace.EULER_Z:=SrcFace.pose(EULER_Z); Result[i] := DstFace; end; end; end.
ソースコード
unit Unit1; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs ,System.Permissions ,FMX.Platform, FMX.Media, FMX.Objects, FMX.Edit ,FMX.Controls.Presentation ,FMX.StdCtrls ,UMamFaceDetect; type TForm1 = class(TForm) CameraComponent1: TCameraComponent; Image1: TImage; procedure FormCreate(Sender: TObject); procedure FormKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); procedure CameraComponent1SampleBufferReady(Sender: TObject; const ATime: TMediaTime); private { private 宣言 } FaceDetectDataArr: TFaceDetectDataArr; function AppEvent(iAppEvent:TApplicationEvent;iContext:TObject):Boolean; procedure PermissionRequestResult( Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>); procedure RequestPermissions(); procedure DrawFace(Faces:TFaceDetectDataArr; bmp:TBitmap); public { public 宣言 } end; var Form1: TForm1; implementation {$R *.fmx} uses Androidapi.Helpers, Androidapi.JNI.Os, Androidapi.Jni.Support, FMX.DialogService; function TForm1.AppEvent(iAppEvent: TApplicationEvent; iContext: TObject): Boolean; begin Result:=False; case iAppEvent of TApplicationEvent.BecameActive: begin //フォーカス取得時 CameraComponent1.Active:=true; Sleep(100); CameraComponent1.Active:=False; Sleep(500); //オートフォーカスモードに設定する CameraComponent1.FocusMode:=TFocusMode.ContinuousAutoFocus; //顔認識処理は負荷が高いので、中解像度に設定する CameraComponent1.Quality:=TVideoCaptureQuality.MediumQuality; CameraComponent1.Active:=True; end; TApplicationEvent.WillBecomeInactive: begin //フォーカス喪失時 CameraComponent1.Active:=False; end; end; end; procedure TForm1.CameraComponent1SampleBufferReady(Sender: TObject; const ATime: TMediaTime); begin TThread.Synchronize( TThread.CurrentThread, procedure var SrcBmp:TBitmap; begin SrcBmp:=TBitmap.Create; CameraComponent1.SampleBufferToBitmap(SrcBmp,true); FaceDetectDataArr:=FaceDetectAndroid(SrcBmp,5); DrawFace(FaceDetectDataArr,SrcBmp); Image1.Bitmap.Assign(SrcBmp); SrcBmp.DisposeOf; end ); end; procedure TForm1.DrawFace(Faces:TFaceDetectDataArr; bmp:TBitmap); var f:TFaceDetectData; begin bmp.Canvas.BeginScene(); bmp.Canvas.Stroke.Kind:=TBrushKind.Solid; bmp.Canvas.Stroke.Color:=$FFFF0000; bmp.Canvas.Stroke.Thickness:=8; for f in Faces do begin bmp.Canvas.DrawRect(f.Rect, 8, 8, AllCorners, 1); end; bmp.Canvas.EndScene; end; procedure TForm1.FormCreate(Sender: TObject); var APPEventService:IFMXApplicationEventService; i:integer; begin CameraComponent1.Kind:=TCameraKind.BackCamera; if TPlatformServices.Current.SupportsPlatformService( IFMXApplicationEventService) then begin try APPEventService:=IFMXApplicationEventService( TPlatformServices.Current.GetPlatformService( IFMXApplicationEventService ) ); except APPEventService:=nil; end; end; if (APPEventService<>nil) then APPEventService.SetApplicationEventHandler(AppEvent); RequestPermissions(); end; procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; var KeyChar: Char; Shift: TShiftState); begin if Key = vkHardwareBack then Key := 0; end; procedure TForm1.PermissionRequestResult(Sender: TObject; const APermissions: TArray<string>; const AGrantResults: TArray<TPermissionStatus>); begin //カメラの権限があるか if (AGrantResults[0]<>TPermissionStatus.Granted) then begin //カメラの権限がない場合 //「□今後は表示しない」チェックボックスにチェックが入っているか if (TJActivityCompat.JavaClass.shouldShowRequestPermissionRationale( TAndroidHelper.Activity, TJManifest_permission.JavaClass.CAMERA)) or (TJActivityCompat.JavaClass.shouldShowRequestPermissionRationale( TAndroidHelper.Activity, TJManifest_permission.JavaClass.WRITE_EXTERNAL_STORAGE)) then begin //「□今後は表示しない」チェックボックスにチェックが入っていない場合 // 非同期でダイアログを表示して説明と許可を要求 TDialogService.MessageDialog( '許可しないとアプリが動作しません。',TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK],TMsgDlgBtn.mbOk,0, procedure (const AResult: TModalResult) begin //2回目以降は「□今後は表示しない」チェックボックスが表示される RequestPermissions(); end ); end else begin //「□今後は表示しない」チェックボックスにチェックが入っている TDialogService.MessageDialog( '権限が無いため終了します。「設定⇒アプリと通知」から権限を設定してください。', TMsgDlgType.mtError,[TMsgDlgBtn.mbOK],TMsgDlgBtn.mbOK,0, procedure(const AResult:TModalResult) begin Application.Terminate; end ); end; end; end; procedure TForm1.RequestPermissions; var FPmsCamera: string; begin FPmsCamera := JStringToString(TJManifest_permission.JavaClass.CAMERA); PermissionsService.RequestPermissions( [FPmsCamera], PermissionRequestResult); end; end.