サンプルプログラム IplImage To TBitmap
最初のプログラムは、IplImageからTBitmapに変換してDelphiのVCL画面に表示をしています。
顔認識プログラムを使用した例です。
顔認識は、IplImageで行いTBitmapに変換した後、顔部分の四角領域のTBitmap上に書き込んで、Timageに変倍表示しています。
描画の基準原点は、左上なので、OpenCVで得た結果をそのまま使用して、顔を認識した位置を四角で表示することが出来ます。
顔認識時、グレイに変換し、更にヒストグラムによるコントラスト調整をして認識率を上げていますが、この画像を表示する場合は、TBitmapを8ビットとし、グレーのカラーパレットを作成する必要があります。
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, system.UITypes, Dialogs, ExtCtrls, ocv.core_c, ocv.core.types_c, ocv.compat, ocv.highgui_c, ocv.haar, ocv.legacy, ocv.objdetect_c,ocv.imgproc_c, ocv.imgproc.types_c; type TForm1 = class(TForm) Image1: TImage; Timer1: TTimer; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Timer1Timer(Sender: TObject); private { Private 宣言 } Capture: PCvCapture; CameraBmp: TBitmap; Storage: PCvMemStorage; Xml: ansistring; Cascade2: PCvHaarClassifierCascade; public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.dfm} var fgray : PIplImage; fFormset : Boolean; const // カメラ仕様に依存します VideoWidth = 1280; VideoHeight = 960; {----------------------------------------------------------------------------- Procedure: IplImageToBitmap Date: 28-Feb-2017 Arguments: iplImg: PIplImage; bitmap: TBitmap Description: convert a IplImage to a Windows bitmap -----------------------------------------------------------------------------} procedure IplImageToBitmap(iplImg: PIplImage; var bitmap: TBitmap); var j: integer; offset: integer; dataByte: PByte; RowIn: PByte; begin try // data 8bit BGR 3byte color の確認 assert((iplImg.Depth = 8) and (iplImg.NChannels = 3), 'IplImageToBitmap: Not a 24 bit color iplImage!'); bitmap.Height := iplImg.Height; bitmap.Width := iplImg.Width; // メモリーの画像の上下合っている場合 if (iplImg.ChannelSeq = 'BGR') and (iplimg.Origin = IPL_ORIGIN_BL) then begin RowIn := Bitmap.Scanline[bitmap.height - 1]; dataByte := pbyte(iplimg.ImageData); {direct copy of the iplImage row bytes to bitmap row} CopyMemory(rowin, dataByte, iplImg.ImageSize); exit; end // メモリーの画像の上下が合っていない場合 else begin for j := 0 to Bitmap.Height - 1 do begin RowIn := Bitmap.Scanline[j]; offset := integer(iplimg.ImageData) + iplImg.WidthStep * j; dataByte := pbyte(offset); CopyMemory(rowin, dataByte, iplImg.WidthStep); end; end; except on E: Exception do begin Form1.Timer1.Enabled := False; MessageDlg('IplImageToBitmap failed - ' + e.Message, mtError, [mbOK], 0); end; end; end; procedure TForm1.FormCreate(Sender: TObject); begin fFormset := False; fgray := nil; Form1.Top := (Screen.Height - Form1.Height) div 2; Form1.Left := (Screen.Width - Form1.Width) div 2; // cvCreateCameraCaptureのindexはカメラの番号を設定してください Capture := cvCreateCameraCapture(0); if not Assigned(Capture) then begin MessageDlg('カメラエラー', mtError, [mbOK], 0); Exit; end; cvSetCaptureProperty(Capture, CV_CAP_PROP_FRAME_HEIGHT, VideoHeight); cvSetCaptureProperty(Capture, CV_CAP_PROP_FRAME_WIDTH, VideoWidth); // ダブルバッファでちらつき防止 DoubleBuffered := True; // コンポーネント表示用のビットマップ CameraBmp := TBitmap.Create; CameraBmp.PixelFormat := pf24bit; Storage := cvCreateMemStorage(0); Xml := 'haarcascade_frontalface_alt.xml'; // Xml := 'haarcascade_frontalface_default.xml'; // default Cascade2 := PCvHaarClassifierCascade(cvLoad(PAnsiChar(Xml), nil, nil, nil)); // デフォルトからの変更プロパティ with Image1 do begin Align := alClient; Center := True; Proportional := True; Stretch := True; end; Timer1.Interval := 20; Timer1.Enabled := True; end; procedure TForm1.FormDestroy(Sender: TObject); begin cvReleaseCapture(Capture); FreeAndNil(CameraBmp); cvReleaseImage(fgray); end; procedure TForm1.Timer1Timer(Sender: TObject); var frame: PIplImage; r: PCvRect; faces: PCvSeq; i: Integer; pt1, pt2: TCvPoint2D32f; begin // 画像の取得 frame := cvQueryFrame(Capture); if not Assigned(frame) then Exit; // 表示サイズ設定 if not fFormset then begin Form1.ClientHeight := frame.height div 2; Form1.ClientWidth := frame.width div 2; Form1.Top := (Screen.Height - Form1.Height) div 2; Form1.Left := (Screen.Width - Form1.Width) div 2; fFormset := true; end; // グレー用メモリー確保 if fgray = nil then fgray := cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 1); // カラーグレー変換 cvCvtColor(frame, fgray, CV_BGR2GRAY); // グレーヒストグラム調整 cvEqualizeHist(fgray, fgray); // メモリストレージをクリア cvClearMemStorage(Storage); // 画像から物体検出 faces := cvHaarDetectObjects( fgray, Cascade2, Storage, 1.2, 3, CV_HAAR_DO_CANNY_PRUNING, CvSize(50, 50), CvSize(0, 0)); { faces := cvHaarDetectObjects( frame, Cascade2, Storage, 1.2, 4, CV_HAAR_DO_CANNY_PRUNING, CvSize(50, 50), CvSize(720, 720)); } // OpenCV -> delphi bitmap IplImageToBitmap(frame, CameraBmp); // 顔部赤枠 CameraBmp.Canvas.Pen.Color := clRed; CameraBmp.Canvas.Pen.Width := 3; CameraBmp.Canvas.Brush.Style:= bsClear; for i := 0 to faces.total - 1 do begin r := PCvRect(cvGetSeqElem(Faces, i)); pt1.x := r.x; pt2.x := r.x + r.width; pt1.y := r.y; pt2.y := r.y + r.height; // 矩形描画 CameraBmp.Canvas.Rectangle(Round(pt1.x), Round(pt1.y), Round(pt2.x), Round(pt2.y)); end; Image1.Canvas.StretchDraw(Image1.Canvas.ClipRect, CameraBmp); end; end.
サンプルプログラム TBitmap To IplImage
Webカメラの画像表示にAVICAP32.DLLを利用した場合のプログラムに、OpenCVの顔認識を追加してみました。
この場合、TBitmapをIPlImageに変換して顔認識を行います。
顔認識を行う場合、OpenCVのメモリー上データーは、左上原点にしないと、顔認識は出来ません。
AVICAP32.DLLを利用して、TBitmapに画像を取り込む場合は、一旦クリップボードにコピーして、クリップボードから取り込む必要があります。
クリップボードからの取り込みは、TPictureのTBitmapを利用します。
クリップボードからカラー画像を取り込むと、BitmapのPixelFormatがpfDeviceとなります。
デフォルトは32bitフォーマットのようですので、pf24bitに変換してからIPlImageに変換しています。
顔認識は、32bitフォーマットでも問題ないようです。
クリップボードを利用しているので、写真をクリップボードに貼り付ければ、写真の顔認識も可能です。
unit unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls, StrUtils, Clipbrd, Vcl.Buttons, Vcl.ExtDlgs; type TForm1 = class(TForm) btnConnectClick: TButton; Button1: TButton; Button2: TButton; Button3: TButton; Button4: TButton; Image1: TImage; Button5: TButton; SavePictureDialog1: TSavePictureDialog; Button6: TButton; ListBox1: TListBox; procedure btnConnectClickClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); procedure Button4Click(Sender: TObject); procedure Button5Click(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Button6Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; const WM_CAP_DRIVER_CONNECT = WM_USER + 10; WM_CAP_DRIVER_DISCONNECT = WM_USER + 11; WM_CAP_FILE_SET_CAPTURE_FILEA = WM_USER + 20; WM_CAP_SAVEDIB = WM_USER + 25; WM_CAP_SEQUENCE = WM_USER + 62; WM_CAP_GRAB_FRAME = WM_USER + 60; WM_CAP_SEQUENCE_NOFILE = WM_USER + 63; WM_CAP_EDIT_COPY = WM_USER + 30; WM_CAP_SET_OVERLAY = WM_USER + 51; WM_CAP_SET_SCALE = WM_USER + 53; WM_CAP_SET_PREVIEWRATE = WM_USER + 52; WM_CAP_SET_PREVIEW = WM_USER + 50; WM_CAP_STOP = WM_USER + 68; WM_CAP_SET_CALLBACK_VIDEOSTREAM = WM_USER + 6; WM_CAP_SET_CALLBACK_ERROR = WM_USER + 2; WM_CAP_SET_CALLBACK_STATUSA = WM_USER + 3; WM_CAP_SET_CALLBACK_FRAME = WM_USER + 5; WM_CAP_GET_VIDEOFORMAT = WM_USER + 44; WM_CAP_SET_VIDEOFORMAT = WM_USER + 45; WM_CAP_DLG_VIDEOSOURCE = WM_USER + 42; WM_CAP_DLG_VIDEOFORMAT = WM_USER + 41; WM_CAP_DLG_VIDEODISPLAY = WM_USER + 43; var Form1: TForm1; implementation {$R *.dfm} uses ocv.core_c, ocv.core.types_c, ocv.imgproc_c, ocv.imgproc.types_c, ocv.objdetect_c; var CaptureWnd : HWND; Filename : ansistring; lpBmpInfo : Bitmapinfo; Xml: ansistring; Cascade2: PCvHaarClassifierCascade; Storage: PCvMemStorage; const str_Connect = 'Webカメラに接続'; str_Disconn = '接続解除'; CaptureLeft = 160; CaptureTop = 8; // webカメラに合わせて設定します CaptureWidth = 1280; // ok CaptureHeight = 720; // CaptureWidth = 640; // ok // CaptureHeight = 480; // CaptureWidth = 640; // ok // CaptureHeight = 360; // このサイズは動作しない原因不明 // バッファサイズが確保できないのか // CaptureWidth = 1280; // NG // CaptureHeight = 960; function capCreateCaptureWindow(WindowName: PChar; dwStyle: Cardinal; x,y, nWidth,nHeight: Integer; ParentWin: HWnd; nID: Integer): HWnd; stdcall; external 'AVICAP32.DLL' name 'capCreateCaptureWindowW'; function capGetDriverDescription(wDriverIndex: DWORD; lpszName: LPWSTR; cbName : integer; lpszVer : LPWSTR; cbVer : Integer): Boolean; stdcall; external 'AVICAP32.DLL' name 'capGetDriverDescriptionW'; {----------------------------------------------------------------------------- Procedure: BitmapToIplImage Arguments: iplImg: PIplImage; bitmap: TBitmap Description: create a new IplImage and convert a Windows bitmap (24bit) to it -----------------------------------------------------------------------------} procedure BitmapToIplImage(iplImg: PIplImage; bitmap: TBitmap); var dataByte: PByte; // dataByte: pByteArray; RowIn: PByte; // RowIn: pByteArray; j : integer; offset: integer; begin try // 24ビットフォーマットの確認 assert((iplImg.Depth = 8) and (iplImg.NChannels = 3), // assert((iplImg.Depth = 8) and (iplImg.NChannels = 4), 'Bitmap2IplImage: Not a 24 bit color iplImage!'); assert((bitmap.pixelFormat = pf24bit), // assert((bitmap.pixelFormat = pfdevice), 'Bitmap2IplImage: Not a 24 bit color BMP bitmap!'); // 画像の先頭ライン設定 iplimg.Origin := IPL_ORIGIN_TL; // iplimg.Origin := IPL_ORIGIN_BL; iplImg.ChannelSeq := 'BGR'; // 画像データーのコピー for j := 0 to Bitmap.Height - 1 do begin RowIn := Bitmap.Scanline[j]; offset := integer(iplimg.ImageData) + iplImg.WidthStep * j; dataByte := pbyte(offset); CopyMemory(dataByte, rowin, iplImg.WidthStep); end; // RowIn := Bitmap.Scanline[bitmap.height - 1]; // dataByte := pbytearray(iplimg.ImageData); {direct copy of the bitmap row bytes to iplImage rows} // CopyMemory(dataByte, rowin, iplimg.ImageSize); except end; end; procedure TForm1.btnConnectClickClick(Sender: TObject); begin if (Sender as TButton).Caption = str_Connect then begin // デバイスの設定とハンドルの取得 CaptureWnd := capCreateCaptureWindow('Window', WS_CHILD or WS_VISIBLE, CaptureLeft, CaptureTop, CaptureWidth, CaptureHeight, Handle, 0); if CaptureWnd <> 0 then begin // SendMessage(CaptureWnd, WM_CAP_SET_CALLBACK_VIDEOSTREAM, 0, 0); // SendMessage(CaptureWnd, WM_CAP_SET_CALLBACK_ERROR, 0, 0); // SendMessage(CaptureWnd, WM_CAP_SET_CALLBACK_STATUSA, 0, 0); //デバイスへの接続 SendMessage(CaptureWnd, WM_CAP_DRIVER_CONNECT, 0, 0); // カメラ画像の調整 // SendMessage(CaptureWnd, WM_CAP_DLG_VIDEOSOURCE, 0, 0); // カメラの解像度とフォーマットの指定 // WM_CAP_DLG_VIDEOFORMATは、4:3 で最大サイズ640×480です。 // SendMessage(CaptureWnd, WM_CAP_DLG_VIDEOFORMAT, 0, 0); // Video フォーマットの取得 SendMessage(CaptureWnd, WM_CAP_GET_VIDEOFORMAT, sizeof(lpBmpInfo), Cardinal(@lpBmpInfo)); // 解像度の設定 with lpbmpInfo.bmiHeader do begin biWidth := CaptureWidth; biHeight := CaptureHeight; biSizeImage := biWidth * biHeight * biBitCount div 8; // biCompression := BI_RGB; end; // Video フォーマットの設定 SendMessage(CaptureWnd, WM_CAP_SET_VIDEOFORMAT, sizeof(lpBmpInfo), Cardinal(@lpBmpInfo)); //プレビュースケール 変倍有効設定 SendMessage(CaptureWnd, WM_CAP_SET_SCALE, 1, 0); //プレビューのコマ数(ミリ秒) SendMessage(CaptureWnd, WM_CAP_SET_PREVIEWRATE, 35, 0); // 表示サイズの指定 SetWindowPos(CaptureWnd, HWND_BOTTOM, 0, 0, CaptureWidth div 2, CaptureHeight div 2, SWP_NOMOVE or SWP_NOZORDER); // プレビュー開始 SendMessage(CaptureWnd, WM_CAP_SET_PREVIEW, 1, 0); end; (Sender as TButton).Caption:= str_Disconn end else begin if CaptureWnd <> 0 then begin // デバイスへの接続解除 SendMessage(CaptureWnd, WM_CAP_DRIVER_DISCONNECT, 0, 0); // CaptureようWindowの破棄 DestroyWindow(CaptureWnd); // ハンドルのクリア CaptureWnd := 0; (Sender as TButton).Caption:= str_Connect; end; end; end; // クリップボードへコピー procedure TForm1.Button1Click(Sender: TObject); begin if CaptureWnd <> 0 then SendMessage(CaptureWnd, WM_CAP_EDIT_COPY, 1, 0 ); end; // ファイルへ保存 // SendMessageで送るファイル名はAnsiStringの必要があります。 procedure TForm1.Button2Click(Sender: TObject); begin if CaptureWnd <> 0 then begin SavePictureDialog1.Filter := '画像ファイル|*.bmp'; if SavePictureDialog1.Execute then begin Filename := ansistring(ChangeFileExt(SavePictureDialog1.FileName,'.bmp')); end else exit; SendMessage(CaptureWnd, WM_CAP_SAVEDIB, 0, Cardinal(pAnsiChar(Filename))); end; end; // ファイルへの動画保存開始 // SendMessageで送るファイル名はAnsiStringの必要があります。 procedure TForm1.Button3Click(Sender: TObject); begin if CaptureWnd <> 0 then begin SavePictureDialog1.Filter := '動画ファイル|*.avi'; if SavePictureDialog1.Execute then begin Filename := ansistring(ChangeFileExt(SavePictureDialog1.FileName,'.avi')); end else exit; SendMessage(CaptureWnd, WM_CAP_FILE_SET_CAPTURE_FILEA, 0, Cardinal(pAnsichar (Filename))); SendMessage(CaptureWnd, WM_CAP_SEQUENCE, 0, 0); end; end; // 動画保存の停止 procedure TForm1.Button4Click(Sender: TObject); begin if CaptureWnd <> 0 then SendMessage(CaptureWnd, WM_CAP_STOP, 0, 0); end; // クリップボードから画像貼り付け 顔検出 procedure TForm1.Button5Click(Sender: TObject); var bitmm: TPicture; frame, fgray : PIplImage; faces: PCvSeq; pt1, pt2: TCvPoint2D32f; i : integer; r: PCvRect; begin if Clipboard.HasFormat(CF_DIB) then begin bitmm := TPicture.Create; try // クリップボードから画像取得 bitmm.Bitmap.Assign(Clipboard); // デフォルトはpfDevice (pf32bit) // 24ビットフォーマットに変換 bitmm.Bitmap.PixelFormat := pf24bit; // Opencv IplImage 24ビットフォーマット設定 frame := cvCreateImage(cvSize(bitmm.Bitmap.Width, bitmm.Bitmap.Height), IPL_DEPTH_8U, 3); // frame := cvCreateImage(cvSize(bitmm.Bitmap.Width, bitmm.Bitmap.Height), IPL_DEPTH_8U, 4); // frame.Origin := IPL_ORIGIN_BL; // bitmap to IplImage 変換 BitmapToIplImage(frame, bitmm.Bitmap); // カラーグレー変換 fgray := cvCreateImage(cvGetSize (frame), IPL_DEPTH_8U, 1); cvCvtColor(frame, fgray, CV_BGR2GRAY); // グレーヒストグラム調整 cvEqualizeHist(fgray, fgray); // メモリストレージをクリア cvClearMemStorage(Storage); // 画像から物体検出 faces := cvHaarDetectObjects(fgray, Cascade2, Storage, 1.2, 3, CV_HAAR_DO_CANNY_PRUNING, CvSize(50, 50), CvSize(0, 0)); bitmm.Bitmap.Canvas.Pen.Color := clRed; bitmm.Bitmap.Canvas.Pen.Width := 4; bitmm.Bitmap.Canvas.Brush.Style:= bsClear; for i := 0 to faces.total - 1 do begin r := PCvRect(cvGetSeqElem(Faces, i)); pt1.x := r.x; pt2.x := r.x + r.width; pt1.y := r.y; pt2.y := r.y + r.height; // 矩形描画 bitmm.Bitmap.Canvas.Rectangle(Round(pt1.x),Round(pt1.y), Round(pt2.x), Round(pt2.y)); end; Image1.Canvas.StretchDraw(rect(0, 0, CaptureWidth div 4, CaptureHeight div 4), bitmm.Bitmap); finally bitmm.Free; end; end; end; // ドライバーバージョンの取得 procedure TForm1.Button6Click(Sender: TObject); var Indx : Cardinal; szName, szVersion: array[0..MAX_PATH] of Char; begin ListBox1.Clear; for Indx := 0 to 9 do begin if capGetDriverDescription(Indx, @szName, sizeof(szName), @szVersion, sizeof(szVersion)) then begin ListBox1.Items.Add(szName + ' (' + szVersion + ')'); end; end; end; procedure TForm1.FormCreate(Sender: TObject); begin top := (screen.Height - Height) div 2; left := (screen.Width - Width) div 2; btnConnectClick.Caption := str_Connect; CaptureWnd := 0; Storage := cvCreateMemStorage(0); Xml := 'haarcascade_frontalface_alt.xml'; // Xml := 'haarcascade_frontalface_default.xml'; // default Cascade2 := PCvHaarClassifierCascade(cvLoad(PAnsiChar(Xml), nil, nil, nil)); end; procedure TForm1.FormDestroy(Sender: TObject); begin if CaptureWnd <> 0 then begin SendMessage(CaptureWnd, WM_CAP_DRIVER_DISCONNECT, 0, 0); DestroyWindow(CaptureWnd); end; end; end.