Mam's WebSite
Delphiでお手軽プログラミング

トップページ⇒Delphiでカメラ画像から顔認識するAndroid用アプリケーションを作る

Delphiでカメラ画像から顔認識するAndroid用アプリケーションを作成します。



顔認識の基本ユニット

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.

権限設定







Mam's WebSite