mirror of
https://github.com/Laex/Delphi-OpenCV.git
synced 2024-11-16 00:05:52 +01:00
371 lines
12 KiB
ObjectPascal
371 lines
12 KiB
ObjectPascal
(* /*****************************************************************
|
||
// Delphi-OpenCV Demo
|
||
// Copyright (C) 2013 Project Delphi-OpenCV
|
||
// ****************************************************************
|
||
// Contributor:
|
||
// Mikhail Grigorev
|
||
// email: sleuthhound@gmail.com
|
||
// ****************************************************************
|
||
// You may retrieve the latest version of this file at the GitHub,
|
||
// located at git://github.com/Laex/Delphi-OpenCV.git
|
||
// ****************************************************************
|
||
// The contents of this file are used with permission, subject to
|
||
// the Mozilla Public License Version 1.1 (the "License"); you may
|
||
// not use this file except in compliance with the License. You may
|
||
// obtain a copy of the License at
|
||
// http://www.mozilla.org/MPL/MPL-1_1Final.html
|
||
//
|
||
// Software distributed under the License is distributed on an
|
||
// "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||
// implied. See the License for the specific language governing
|
||
// rights and limitations under the License.
|
||
******************************************************************* *)
|
||
// JCL_DEBUG_EXPERT_GENERATEJDBG OFF
|
||
// JCL_DEBUG_EXPERT_INSERTJDBG OFF
|
||
// JCL_DEBUG_EXPERT_DELETEMAPFILE OFF
|
||
program HandsDetect;
|
||
|
||
{$APPTYPE CONSOLE}
|
||
{$POINTERMATH ON}
|
||
{$R *.res}
|
||
|
||
uses
|
||
System.SysUtils,
|
||
uLibName in '..\..\..\include\uLibName.pas',
|
||
highgui_c in '..\..\..\include\highgui\highgui_c.pas',
|
||
imgproc.types_c in '..\..\..\include\imgproc\imgproc.types_c.pas',
|
||
imgproc_c in '..\..\..\include\imgproc\imgproc_c.pas',
|
||
legacy in '..\..\..\include\legacy\legacy.pas',
|
||
calib3d in '..\..\..\include\calib3d\calib3d.pas',
|
||
imgproc in '..\..\..\include\imgproc\imgproc.pas',
|
||
haar in '..\..\..\include\objdetect\haar.pas',
|
||
objdetect in '..\..\..\include\objdetect\objdetect.pas',
|
||
tracking in '..\..\..\include\video\tracking.pas',
|
||
core in '..\..\..\include\core\core.pas',
|
||
Core.types_c in '..\..\..\include\core\Core.types_c.pas',
|
||
core_c in '..\..\..\include\core\core_c.pas';
|
||
|
||
const
|
||
NUM_FINGERS = 5;
|
||
NUM_DEFECTS = 8;
|
||
|
||
type
|
||
pCtx = ^TCtx;
|
||
|
||
TCtx = packed record
|
||
capture: pCvCapture; // Capture handle
|
||
image: pIplImage; // Input image
|
||
thr_image: pIplImage; // After filtering and thresholding
|
||
temp_image1: pIplImage; // Temporary image (1 channel)
|
||
temp_image3: pIplImage; // Temporary image (3 channels)
|
||
contour: pCvSeq; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
hull: pCvSeq; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
hand_center: TCvPoint;
|
||
fingers: pCvPointArray; // Detected fingers positions
|
||
defects: pCvPointArray; // Convexity defects depth points
|
||
hull_st: pCvMemStorage;
|
||
contour_st: pCvMemStorage;
|
||
temp_st: pCvMemStorage;
|
||
defects_st: pCvMemStorage; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
kernel: pIplConvKernel; // Kernel for morph operations
|
||
NUM_FINGERS: Integer;
|
||
hand_radius: Integer;
|
||
NUM_DEFECTS: Integer;
|
||
end;
|
||
|
||
var
|
||
key: Integer;
|
||
MyCtx: pCtx;
|
||
|
||
procedure InitCapture(const Ctx: pCtx);
|
||
var
|
||
width: Double;
|
||
height: Double;
|
||
begin
|
||
Ctx.capture := cvCreateCameraCapture(CV_CAP_ANY);
|
||
if not Assigned(Ctx.capture) then
|
||
begin
|
||
WriteLn('[i] Error initializing capture.');
|
||
Halt;
|
||
end;
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>
|
||
width := cvGetCaptureProperty(Ctx.capture, CV_CAP_PROP_FRAME_WIDTH);
|
||
height := cvGetCaptureProperty(Ctx.capture, CV_CAP_PROP_FRAME_HEIGHT);
|
||
// WriteLn(Format('[i] %.0f x %.0f', [width, height]));
|
||
Ctx.image := cvQueryFrame(Ctx.capture);
|
||
WriteLn('[i] Start capture.');
|
||
end;
|
||
|
||
procedure InitWindows;
|
||
begin
|
||
cvNamedWindow('output', CV_WINDOW_AUTOSIZE);
|
||
cvNamedWindow('thresholded', CV_WINDOW_AUTOSIZE);
|
||
cvMoveWindow('output', 50, 50);
|
||
cvMoveWindow('thresholded', 700, 50);
|
||
end;
|
||
|
||
procedure InitCtx(const Ctx: pCtx);
|
||
begin
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
Ctx.thr_image := cvCreateImage(cvGetSize(Ctx.image), IPL_DEPTH_8U, 1);
|
||
Ctx.temp_image1 := cvCreateImage(cvGetSize(Ctx.image), IPL_DEPTH_8U, 1);
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 3-<2D> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
Ctx.temp_image3 := cvCreateImage(cvGetSize(Ctx.image), IPL_DEPTH_8U, 3);
|
||
Ctx.kernel := cvCreateStructuringElementEx(9, 9, 4, 4, CV_SHAPE_RECT, nil);
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
Ctx.contour_st := cvCreateMemStorage(0);
|
||
Ctx.hull_st := cvCreateMemStorage(0);
|
||
Ctx.temp_st := cvCreateMemStorage(0);
|
||
Ctx.fingers := AllocMem((NUM_FINGERS + 1) * sizeof(TCvPoint));
|
||
Ctx.defects := AllocMem(NUM_DEFECTS * sizeof(TCvPoint));
|
||
end;
|
||
|
||
procedure FilterAndThreshold(const Ctx: pCtx);
|
||
begin
|
||
// Soften image
|
||
cvSmooth(Ctx.image, Ctx.temp_image3, CV_GAUSSIAN, 11, 11, 0, 0);
|
||
// Remove some impulsive noise
|
||
cvSmooth(Ctx.temp_image3, Ctx.temp_image3, CV_MEDIAN, 11, 11, 0, 0);
|
||
cvCvtColor(Ctx.temp_image3, Ctx.temp_image3, CV_BGR2HSV);
|
||
{ * Apply threshold on HSV values
|
||
* Threshold values should be customized according to environment
|
||
* }
|
||
cvInRangeS(Ctx.temp_image3, cvScalar(0, 0, 160, 0), cvScalar(255, 400, 300, 255), Ctx.thr_image);
|
||
// Apply morphological opening
|
||
cvMorphologyEx(Ctx.thr_image, Ctx.thr_image, nil, Ctx.kernel, CV_MOP_OPEN, 1);
|
||
cvSmooth(Ctx.thr_image, Ctx.thr_image, CV_GAUSSIAN, 3, 3, 0, 0);
|
||
end;
|
||
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
procedure FindContour(const Ctx: pCtx);
|
||
var
|
||
area, max_area: Double;
|
||
contours, tmp, contour: pCvSeq;
|
||
cont: pIplImage;
|
||
begin
|
||
area := 0.0;
|
||
max_area := 0.0;
|
||
contours := nil;
|
||
tmp := nil;
|
||
contour := nil;
|
||
|
||
// cvFindContours modifies input image, so make a copy
|
||
cvCopy(Ctx.thr_image, Ctx.temp_image1, nil);
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
cvFindContours(Ctx.temp_image1, Ctx.temp_st, @contours, sizeof(TCvContour), CV_RETR_EXTERNAL,
|
||
CV_CHAIN_APPROX_NONE { CV_CHAIN_APPROX_SIMPLE } , cvPoint(0, 0));
|
||
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
cont := cvCreateImage(cvGetSize(Ctx.temp_image1), IPL_DEPTH_8U, 3);
|
||
// Select contour having greatest area
|
||
tmp := contours;
|
||
while (tmp <> nil) do
|
||
begin
|
||
area := abs(cvContourArea(tmp, CV_WHOLE_SEQ, 0));
|
||
if area > max_area then
|
||
begin
|
||
// WriteLn(Format('[i] area = %.1f', [area]));
|
||
max_area := area;
|
||
contour := tmp;
|
||
end;
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
cvDrawContours(cont, tmp, CV_RGB(255, 216, 0), CV_RGB(0, 0, 250), 0, 1, 8, cvPoint(0, 0));
|
||
tmp := tmp.h_next;
|
||
end;
|
||
// Approximate contour with poly-line
|
||
if contour <> nil then
|
||
begin
|
||
contour := cvApproxPoly(contour, sizeof(TCvContour), Ctx.contour_st, CV_POLY_APPROX_DP, 2, 1);
|
||
Ctx.contour := contour;
|
||
end;
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
cvDrawContours(cont, contour, CV_RGB(52, 201, 36), CV_RGB(36, 201, 197), 0, 2, 8, cvPoint(0, 0));
|
||
cvNamedWindow('ContT', CV_WINDOW_AUTOSIZE);
|
||
cvShowImage('ContT', cont);
|
||
end;
|
||
|
||
procedure FindConvexHull(const Ctx: pCtx);
|
||
var
|
||
defects: pCvSeq;
|
||
defect_array: pCvConvexityDefect;
|
||
i: Integer;
|
||
d, x, y: Integer;
|
||
dist: Double;
|
||
begin
|
||
x := 0;
|
||
y := 0;
|
||
dist := 0;
|
||
|
||
Ctx.hull := nil;
|
||
|
||
if not Assigned(Ctx.contour) then
|
||
Exit;
|
||
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
Ctx.hull := cvConvexHull2(Ctx.contour, Ctx.hull_st, CV_CLOCKWISE, 0);
|
||
if Assigned(Ctx.hull) then
|
||
begin
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
defects := cvConvexityDefects(Ctx.contour, Ctx.hull, Ctx.defects_st);
|
||
|
||
if Assigned(defects) and (defects.total <> 0) then
|
||
begin
|
||
defect_array := AllocMem(defects.total * sizeof(TCvConvexityDefect));
|
||
cvCvtSeqToArray(defects, defect_array, CV_WHOLE_SEQ);
|
||
|
||
// Average depth points to get hand center
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>, <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
i := 0;
|
||
while (i < defects.total) and (i < NUM_DEFECTS) do
|
||
begin
|
||
x := x + defect_array[i].depth_point.x;
|
||
y := y + defect_array[i].depth_point.y;
|
||
Ctx.defects[i] := cvPoint(defect_array[i].depth_point.x, defect_array[i].depth_point.y);
|
||
Inc(i);
|
||
// WriteLn(Format('[i] x = %d, y = %d', [Ctx.defects[i].x, Ctx.defects[i].y]));
|
||
// WriteLn(Format('[i] defects.total: %d', [defects.total]));
|
||
end;
|
||
|
||
x := x div defects.total;
|
||
y := y div defects.total;
|
||
|
||
Ctx.NUM_DEFECTS := defects.total;
|
||
Ctx.hand_center := cvPoint(x, y);
|
||
// WriteLn(Format('[i] defects.total: %d', [defects.total]));
|
||
// WriteLn(Format('[i] hand_center: x = %d, y = %d', [Ctx.hand_center.x, Ctx.hand_center.y]));
|
||
|
||
// Compute hand radius as mean of distances of defects' depth point to hand center
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
|
||
for i := 0 to defects.total - 1 do
|
||
begin
|
||
d := (x - defect_array[i].depth_point.x) * (x - defect_array[i].depth_point.x) +
|
||
(y - defect_array[i].depth_point.y) * (y - defect_array[i].depth_point.y);
|
||
dist := dist + sqrt(d);
|
||
end;
|
||
|
||
Ctx.hand_radius := Trunc(dist) div defects.total;
|
||
// WriteLn(Format('[i] hand_radius: %d', [Ctx.hand_radius]));
|
||
|
||
FreeMem(defect_array);
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
procedure FindFingers(const Ctx: pCtx);
|
||
var
|
||
n, i, cx, cy, dist, dist1, dist2: Integer;
|
||
// points: pCvPoint;
|
||
points: pCvPointArray;
|
||
max_point: TCvPoint;
|
||
finger_distance: array [0 .. NUM_FINGERS + 1] of Integer;
|
||
begin
|
||
dist1 := 0;
|
||
dist2 := 0;
|
||
Ctx.NUM_FINGERS := 0;
|
||
|
||
if (not Assigned(Ctx.contour)) or (not Assigned(Ctx.hull)) then
|
||
Exit;
|
||
|
||
n := Ctx.contour.total;
|
||
points := AllocMem(n * sizeof(TCvPoint));
|
||
|
||
cvCvtSeqToArray(Ctx.contour, points, CV_WHOLE_SEQ);
|
||
|
||
{ * Fingers are detected as points where the distance to the center
|
||
* is a local maximum
|
||
* }
|
||
for i := 0 to n - 1 do
|
||
begin
|
||
cx := Ctx.hand_center.x;
|
||
cy := Ctx.hand_center.y;
|
||
|
||
dist := (cx - points[i].x) * (cx - points[i].x) + (cy - points[i].y) * (cy - points[i].y);
|
||
|
||
if (dist < dist1) and (dist1 > dist2) and (max_point.x <> 0) and (max_point.y < cvGetSize(Ctx.image).height - 10)
|
||
then
|
||
begin
|
||
finger_distance[Ctx.NUM_FINGERS] := dist;
|
||
Inc(Ctx.NUM_FINGERS);
|
||
Ctx.fingers[Ctx.NUM_FINGERS] := max_point;
|
||
if Ctx.NUM_FINGERS >= NUM_FINGERS + 1 then
|
||
Break;
|
||
end;
|
||
|
||
dist2 := dist1;
|
||
dist1 := dist;
|
||
max_point := points[i];
|
||
end;
|
||
|
||
FreeMem(points);
|
||
end;
|
||
|
||
procedure Display(const Ctx: pCtx);
|
||
var
|
||
i: Integer;
|
||
begin
|
||
if Ctx.NUM_FINGERS = NUM_FINGERS then
|
||
begin
|
||
// ifdef SHOW_HAND_CONTOUR
|
||
// cvDrawContours(ctx.image, ctx.contour, CV_RGB(0,0,255), CV_RGB(0,255,0), 0, 1, CV_AA, cvPoint(0,0));
|
||
// #endif
|
||
cvCircle(Ctx.image, Ctx.hand_center, 5, CV_RGB(255, 0, 255), 1, CV_AA, 0);
|
||
cvCircle(Ctx.image, Ctx.hand_center, Ctx.hand_radius, CV_RGB(255, 0, 0), 1, CV_AA, 0);
|
||
for i := 0 to Ctx.NUM_FINGERS - 1 do
|
||
begin
|
||
cvCircle(Ctx.image, Ctx.fingers[i], 10, CV_RGB(0, 255, 0), 3, CV_AA, 0);
|
||
cvLine(Ctx.image, Ctx.hand_center, Ctx.fingers[i], CV_RGB(255, 255, 0), 1, CV_AA, 0);
|
||
end;
|
||
for i := 0 to Ctx.NUM_DEFECTS - 1 do
|
||
cvCircle(Ctx.image, Ctx.defects[i], 2, CV_RGB(200, 200, 200), 2, CV_AA, 0);
|
||
end;
|
||
cvShowImage('output', Ctx.image);
|
||
cvShowImage('thresholded', Ctx.thr_image);
|
||
end;
|
||
|
||
procedure ClearRes(const Ctx: pCtx);
|
||
begin
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
cvReleaseCapture(Ctx.capture);
|
||
// cvReleaseImage(Ctx.image);
|
||
cvReleaseImage(Ctx.thr_image);
|
||
cvReleaseImage(Ctx.temp_image1);
|
||
cvReleaseImage(Ctx.temp_image3);
|
||
cvReleaseMemStorage(Ctx.hull_st);
|
||
cvReleaseMemStorage(Ctx.contour_st);
|
||
cvReleaseMemStorage(Ctx.defects_st);
|
||
cvReleaseMemStorage(Ctx.temp_st);
|
||
cvReleaseStructuringElement(Ctx.kernel);
|
||
FreeMem(Ctx.fingers);
|
||
FreeMem(Ctx.defects);
|
||
cvDestroyAllWindows();
|
||
end;
|
||
|
||
begin
|
||
try
|
||
MyCtx := AllocMem(sizeof(TCtx));
|
||
InitCapture(MyCtx);
|
||
InitWindows;
|
||
InitCtx(MyCtx);
|
||
while true do
|
||
begin
|
||
MyCtx.image := cvQueryFrame(MyCtx.capture);
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
FilterAndThreshold(MyCtx);
|
||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||
FindContour(MyCtx);
|
||
FindConvexHull(MyCtx);
|
||
FindFingers(MyCtx);
|
||
Display(MyCtx);
|
||
key := cvWaitKey(33);
|
||
if (key = 27) then
|
||
Break;
|
||
end;
|
||
ClearRes(MyCtx);
|
||
FreeMem(MyCtx);
|
||
except
|
||
on E: Exception do
|
||
WriteLn(E.ClassName, ': ', E.Message);
|
||
end;
|
||
|
||
end.
|