OpenCVによる顔認識(コンソール モード)
OpenCV による顔認識が、一時期、流行っていたようなので、此処で取り上げてみました。
OpenCVのインストール方法は、このホームページのOpenCV_WabCamera.htmlと、https://github.com/Laex/Delphi-OpenCVを参照してください。
プログラムは、https://github.com/Laex/Delphi-OpenCVからダウンロードしたファイルの"Samples"ホルダーの中に入っているものです。
Delphi XE7 の場合、オリジナルのまゝでは動作しないので、一部修正をしています。
プログラムの起動時、
カメラから画像を取得するための設定が完了するのに時間がかかり、その時間分、遅延タイマーで待たないとダメなようです。
特に、コンソールモード(コマンドプロンプトモード)の時に顕著に表れるようで、通常のモードの時は遅延タイマーは必要ないようです。
(XE7のバージョンでしか確認していません、オリジナルには遅延タイマーは無いので他のバージョンだと問題ないのかもしれません。)
此処でのプログラム例でのDLLは、VC2013の場合です。
新しく delphi用のopenCvをダウンロードすると、新しいバージョンのVCのDLLを使用しているのでそちらを使用してください。
無料のVC2017をインストールした場合は、***2413.dllのみでOKです。
Release 時
msvcp120.dll,msvcr120.dll,opencv_core2413.dll,opencv_highgui2413.dll,opencv_imgproc2413.dll,opencv_objdetect2413.dll
Debug 時
msvcp120d.dll,msvcr120d.dll,opencv_core2413d.dll,opencv_highgui2413d.dll,opencv_imgproc2413d.dll,opencv_objdetect2413d.dll
msvcp120.dll,msvcr120.dllは、既にインストールされている可能性があり、不要な場合があります。
プログラム行中の
cvEqualizeHist(gray, gray);
は無いほうが良い場合があります。
又、グレイに変換しなくても、特に問題なく顔認識は出来るようです、色々試してみると良いと思います。
サンプルプログラム 1
program FaceDetect; {$APPTYPE CONSOLE} {$R *.res} uses System.Character, System.SysUtils, System.Math, ocv.core.types_c, ocv.core_c, ocv.highgui_c, ocv.objdetect_c, ocv.imgproc_c, ocv.imgproc.types_c, ocv.utils; const // ******************************************************************************/ // setup the cameras properly based on OS platform CAMERA_INDEX = CV_CAP_ANY; // ******************************************************************************/ windowName = 'Haar Cascade Detection'; // window name cascade_name: pCVChar = 'cascades/haarcascade_frontalface_alt.xml'; // cascade file // cascade_name: pCVChar = 'cascades/haarcascade_eye.xml'; // cascade file var img: pIplImage = nil; // image object frame: pIplImage = nil; capture: pCvCapture = nil; // capture object detected_objects: pCvSeq = nil; // list of detected items key: Integer; // user input EVENT_LOOP_DELAY: Integer = 100; // delay for GUI window 40 ms equates to 1000ms/25fps=40ms per frame cascade: pCvHaarClassifierCascade; storage: pCvMemStorage; gray: pIplImage; imgcopy: pIplImage; i: Integer; r: pCvRect; isCapture: Boolean = false; begin try // if command line arguments are provided try to read image/video_name // otherwise default to capture from attached H/W camera if ParamCount = 1 then begin img := cvLoadImage(c_str(ParamStr(1)), CV_LOAD_IMAGE_UNCHANGED); if not Assigned(img) then begin capture := cvCreateFileCapture(c_str(ParamStr(1))); isCapture := True; end; end; if (not Assigned(img)) and (not Assigned(capture)) then begin capture := cvCreateCameraCapture(CAMERA_INDEX); isCapture := True; end; if not Assigned(capture) then Halt(1); // 画像のサイズ設定 カメラによって値を修正します 追加 cvSetCaptureProperty(Capture, CV_CAP_PROP_FRAME_WIDTH, 1280); cvSetCaptureProperty(Capture, CV_CAP_PROP_FRAME_HEIGHT, 720); // create window object (use flag:=0 to allow resize, 1 to auto fix size) cvNamedWindow(windowName, CV_WINDOW_AUTOSIZE); // 此処で少し待たないとエラーになります 追加 cvWaitkey(3000); // cvResizeWindow(windowName, 640, 360); // load the trained haar cascade classifier from file // and create storage required for detections cascade := cvLoad(cascade_name, nil, nil, nil); if Assigned(cascade) then Writeln('LOADED : ', cascade_name) else begin Writeln('ERROR: Could not load classifier cascade : ', cascade_name); Halt(1); end; try storage := cvCreateMemStorage(0); // if capture object in use (i.e. video/camera) // get initial image from capture object if Assigned(capture) then begin // cvQueryFrame is just a combination of cvGrabFrame // and cvRetrieveFrame in one call. img := cvQueryFrame(capture); if not Assigned(img) then begin if ParamCount = 1 then Writeln('End of video file reached') else Writeln('ERROR: cannot get next fram from camera'); Halt(0); end; end; // create a greyscale image upon which to run the classifier gray := cvCreateImage(cvSize(img^.width, img^.height), img^.depth, 1); // create a copy of the image upon which to do detection and box drawing imgcopy := cvCloneImage(img); // start main loop while True do begin // if capture object in use (i.e. video/camera) // get image from capture object if Assigned(capture) then begin // cvQueryFrame is just a combination of cvGrabFrame // and cvRetrieveFrame in one call. frame := cvQueryFrame(capture); if not Assigned(frame) then begin if ParamCount = 1 then Writeln('End of video file reached') else Writeln('ERROR: cannot get next fram from camera'); Halt(0); end; end else begin // if not a capture object set event delay to zero so it waits // indefinitely (as single image file, no need to loop) EVENT_LOOP_DELAY := 40; end; // N.B. as haar features are orientation dependent (and // the haar function operate directly on the pixel // array in memory) we'll just check the image // is using Top-Left origin in actual memory // and if not flip it (use a copy which is not the // capture object buffer) if (img^.origin = IPL_ORIGIN_TL) then begin cvCopy(frame, imgcopy); end else begin cvFlip(frame, imgcopy); imgcopy^.origin := IPL_ORIGIN_TL; end; gray^.origin := imgcopy^.origin; // convert input image to grayscale cvCvtColor(imgcopy, gray, CV_BGR2GRAY); // histogram equalize it also to maximize the region differences cvEqualizeHist(gray, gray); // run the haar cascade detection // with parameters scale:=1.2, neighbours := 4 and with Canny pruning // turned on with minimum detection scale 30x30 pixels detected_objects := cvHaarDetectObjects(gray, cascade, storage, 1.2, 2, CV_HAAR_DO_CANNY_PRUNING, cvSize(50, 50), cvSize(0, 0)); // draw a red rectangle around any detected objects i := 0; While i < ifthen(Assigned(detected_objects), detected_objects^.total, 0) do begin r := pCvRect(cvGetSeqElem(detected_objects, i)); cvRectangle(imgcopy, cvPoint(r^.x, r^.y), cvPoint((r^.x) + (r^.width), (r^.y) + (r^.height)), CV_RGB(255, 0, 0), 2, 8, 0); Inc(i); end; // if Assigned(detected_objects) then // cvClearSeq(detected_objects); // cvClearMemStorage(storage); // display image in window cvShowImage(windowName, imgcopy); // start event processing loop (very important,in fact essential for GUI) // 40 ms roughly equates to 1000ms/25fps := 4ms per frame key := cvWaitKey(EVENT_LOOP_DELAY); if key >= 1 then begin // if user presses 'ESC' then exit Writeln('Keyboard exit requested : exiting now - bye!'); Break; end; end; // destroy window objects // (triggered by event loop *only* window is closed) cvDestroyAllWindows(); cvReleaseImage(gray); cvReleaseImage(imgcopy); finally if Assigned(img) and (not isCapture) then cvReleaseImage(img); // destroy image objects (if it does not originate from a capture object) if Assigned(capture) then cvReleaseCapture(capture); if Assigned(storage) then cvReleaseMemStorage(storage); end; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.
サンプルプログラム 2
サンプルプログラム1との差は、Wabカメラが無いとき、Jpgファイルを読み込む様になっています。
旧バージョンの
cvCopyImage が cvCopy に変わっています。
此処でダウンロード出来るファイルは変更していませんので、新しいバージョンで使用する場合は、変更してください。
program FaceDetect; {$APPTYPE CONSOLE} {$R *.res} uses System.Character, System.SysUtils, ocv.highgui_c, ocv.core_c, ocv.core.types_c, ocv.imgproc_c, ocv.imgproc.types_c, ocv.objdetect_c; var // Create memory for calculations storage: pCvMemStorage = nil; // Create a new Haar classifier cascade: pCvHaarClassifierCascade = nil; // Create a string that contains the cascade name cascade_name: AnsiString = 'cascades/haarcascade_frontalface_alt.xml'; //'haarcascade_russian_plate_number.xml'; //'haarcascade_frontalface_alt.xml'; // "haarcascade_profileface.xml"; // cascade_name: AnsiString = 'haarcascade_licence_plate_rus_16stages.xml'; //'haarcascade_russian_plate_number.xml';//'haarcascade_frontalface_alt.xml'; // "haarcascade_profileface.xml"; // Input file name for avi or image file. input_name: AnsiString = '0'; // Function prototype for detecting and drawing an object from an image procedure detect_and_draw(image: pIplImage); var scale: Integer; // temp: pIplImage; // two points to represent the face locations pt1, pt2: TCvPoint; i: Integer; faces: pCvSeq; r: pCvRect; begin scale := 1; // Create a new image based on the input image // temp := cvCreateImage(cvSize(image^.width div scale, image^.height div scale), 8, 3); // Clear the memory storage which was used before cvClearMemStorage(storage); // Find whether the cascade is loaded, to find the faces. If yes, then: if Assigned(cascade) then begin // There can be more than one face in an image. So create a growable sequence of faces. // Detect the objects and store them in the sequence faces := cvHaarDetectObjects(image,cascade,storage,1.1,2,CV_HAAR_DO_CANNY_PRUNING,cvSize(40, 40),cvSize(0, 0)); // Loop the number of faces found. for i := 1 to faces^.total do begin // Create a new rectangle for drawing the face r := pCvRect(cvGetSeqElem(faces, i)); // Find the dimensions of the face,and scale it if necessary pt1.x := r^.x * scale; pt2.x := (r^.x + r^.width) * scale; pt1.y := r^.y * scale; pt2.y := (r^.y + r^.height) * scale; // Draw the rectangle in the input image cvRectangle(image, pt1, pt2, CV_RGB(255, 0, 0), 3, 8, 0); end; end; // Show the image in the window named "result" cvShowImage('result', image); // Release the temp image created. // cvReleaseImage(temp); end; Var // Structure for getting video from camera or avi capture: pCvCapture = nil; // Images to capture the frame from video or camera or from file frame: pIplImage = nil; frame_copy: pIplImage = nil; const opt = '--cascade='; begin try // Check for the correct usage of the command line 変更 if ParamCount > 1 then begin cascade_name := Ansistring(ParamStr(1)); input_name := Ansistring(ParamStr(2)); end else if ParamCount = 1 then begin Writeln('Usage: facedetect --cascade=<cascade_path> [filename|camera_index]'); Halt(1); end; // check facedetect HaarClassifier file name if not FileExists(string(cascade_name)) then begin Writeln('Usage: facedetect --cascade=<cascade_path> [filename]'); Halt(1); end; // Load the HaarClassifierCascade cascade := cvLoad(pCVChar(@cascade_name[1]), nil, nil, nil); // Check whether the cascade has loaded successfully. Else report and error and quit if not Assigned(cascade) then begin Writeln('ERROR: Could not load classifier cascade'); Halt(1); end; // Allocate the memory storage storage := cvCreateMemStorage(0); // Find whether to detect the object from file or from camera. if isDigit(input_name, 1) and (Length(input_name) = 1) then capture := cvCreateCameraCapture(StrToInt(input_name)) else capture := cvCreateFileCapture(pCVChar(@input_name[1])); // 画像のサイズ設定 カメラによって値を修正します 追加 cvSetCaptureProperty(Capture, CV_CAP_PROP_FRAME_WIDTH, 1280); cvSetCaptureProperty(Capture, CV_CAP_PROP_FRAME_HEIGHT, 720); // Create a new named window with title: result cvNamedWindow('result', 1); // 実行時間待ち合わせ 画像サイズが大きい場合 長くします 追加 cvWaitKey(3000); // Find if the capture is loaded successfully or not. // If loaded succesfully, then: if Assigned(capture) then begin // Capture from the camera. While true do begin // Capture the frame and load it in IplImage frame := cvQueryFrame(capture); if not Assigned(frame) then Break; // Allocate framecopy as the same size of the frame if not Assigned(frame_copy) then frame_copy := cvCreateImage(cvSize(frame^.width, frame^.height), IPL_DEPTH_8U, frame^.nChannels); // Check the origin of image. If top left, copy the image frame to frame_copy. if (frame^.origin = IPL_ORIGIN_TL) then cvCopy(frame, frame_copy) // cvCopyImage(frame, frame_copy, nil) 旧バージョンでは "cvCopyImage" // Else flip and copy the image else cvFlip(frame, frame_copy, 0); // Call the function to detect and draw the face detect_and_draw(frame_copy); // Wait for a while before proceeding to the next frame if (cvWaitKey(1) >= 0) then Break; end; // Release the images, and capture memory cvReleaseImage(frame_copy); cvReleaseCapture(capture); end else // If the capture is not loaded succesfully, then: begin // Assume the image to be lena.jpg, or the input_name specified input_name := 'lenna.jpg'; // Load the image from that filename frame := cvLoadImage(pCVChar(@input_name[1]), 1); // If Image is loaded succesfully, then: if Assigned(frame) then begin // Detect and draw the face detect_and_draw(frame); // // Wait for user input cvWaitKey(0); // Release the image memory cvReleaseImage(frame); end; end; // Destroy the window previously created with filename: "result" cvDestroyWindow('result'); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.