カメラ画像から顔認識する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.
権限設定