Color Tracking
Wabカメラ画像の中から指定した色を抽出します。
又、その色の部分の移動の軌跡を描画します。
カラー画像(RGB)をHue(色相)、Saturation(彩度)、Value(明度)に変換し、HSV各々の上限下限を設定し、その条件を満足する色の画像を抽出します。
移動の軌跡は、その色の中心点の移動をカメラ画像の中に線画します。
プログラムは、https://github.com/Laex/Delphi-OpenCVからダウンロードしたファイルの"Samples"ホルダーの中に入っているものを修正したものです。
OpenCVのインストール方法は、このホームページのOpenCV_WabCamera.htmlと、https://github.com/Laex/Delphi-OpenCVを参照してください。
Delphiのみで、作成をしても出来ない事はないのですが、労力を考え、OpenCVを利用しています。
一般的には Hue(色相) 0~360°、Saturation(彩度) 0~100%(0~1)、Value(明度)0~100%(0~1)で表しますが、OpenCVでは、Hue(色相) 0~180、Saturation(彩度) 0~255、Value(明度)0~255 で表しています。(Hue色相はリングなので、360°は0°と同じです。OPenCVの場合は180と0は同じことになりますが検索する場合180と設定しても0は検索されません。)
各データーを1バイトで表現する為です。
色の抽出は、H、S、Vの各値の上限と下限を設定し、その中に入るものを抽出します。
抽出判定は、関数cvInRangeSを使用しますが、問題はHue(色相)の判定で、特に赤(色相0)の時です。
赤にもバラつきがあり、0を挟んで、±5~10位なので、その範囲を指定したいのですが、関数cvInRangSは、HSV処理に限った関数ではないのでHue(色相)のリング処理はされません。
360°表記で0±10°の場合OpenCVの場合、175~180と、0~5の範囲で検出し、2つの検出結果を合算して、検出結果とする必要があります。
プログラムでの入力は、上限値を360+10の370とするか、10とし、下限値を350として入力すると、0±10°の範囲で検出が出来る様になっています。
下限値-10°でのマイナス入力処理はしません、必要であればプログラムを修正してください。
色相は0~360°の値で入力しますが、上限が360°を超える場合リングなので0に戻りますので、下限350°で上限10°の場合、下限350°、上限370°として入力します。
彩度と、明度は、0~1の値です。
マウスで、左の画像部をクリックすると、その部分の色のHSVの値が表示されますので、その値を参考にHSVの検出範囲を設定します。
移動軌跡の表示色の変更は、プログラムの変更が必要です。
プログラム
unit uMainForm; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, ocv.highgui_c, ocv.core.types_c, Vcl.StdCtrls, Vcl.ExtCtrls, System.UIConsts, System.Math; type TMainForm = class(TForm) pb1: TPaintBox; pb2: TPaintBox; rb1: TRadioButton; rb2: TRadioButton; CapSttBtn: TButton; CapStpBtn: TButton; LabeledEdit1: TLabeledEdit; LabeledEdit2: TLabeledEdit; LabeledEdit3: TLabeledEdit; Memo1: TMemo; LabeledEdit4: TLabeledEdit; LabeledEdit5: TLabeledEdit; LabeledEdit6: TLabeledEdit; procedure FormDestroy(Sender: TObject); procedure pb1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure CapSttBtnClick(Sender: TObject); procedure CapStpBtnClick(Sender: TObject); procedure FormCreate(Sender: TObject); private procedure OnIdle(Sender: TObject; var Done: Boolean); procedure DetectingRedObjects(img: pIplImage); procedure TrackingRedObjects(img: pIplImage); procedure trackObject(imgThresh: pIplImage); procedure RGBtoHSV(Red, Green, Blue: Integer; var Hue, Saturation, Value: Double); procedure Dispmess(mess: string); public end; var MainForm: TMainForm; implementation {$R *.dfm} uses ocv.core_c, ocv.imgproc_c, ocv.imgproc.types_c, ocv.utils; var capture : pCvCapture; lastX, lastY : Integer; imgTracking : pIplImage; Hmax,Smax,Vmax : Double; Hmin,Smin,Vmin : Double; // This function threshold the HSV image and create a binary image function GetThresholdedImage(imgHSV: pIplImage; FZERO: boolean): pIplImage; begin Result := cvCreateImage(cvGetSize(imgHSV), IPL_DEPTH_8U, 1); // Convet a video into a binary image based on the any color. // Any color area of the video is assined to '1' and other area is assigned to '0' in the binary image if not FZERO then cvInRangeS(imgHSV, cvScalar(HMIN, SMIN, VMIN), cvScalar(HMAX, SMAX, VMAX), Result) else cvInRangeS(imgHSV, cvScalar(0, SMIN, VMIN), cvScalar(HMAX - 180, SMAX, VMAX), Result) end; procedure TMainForm.DetectingRedObjects(img: pIplImage); var imgHSV : pIplImage; imgThresh : pIplImage; frame : pIplImage; imgThreshOr : pIplImage; begin frame := cvCloneImage(img); // smooth the original image using Gaussian kernel cvSmooth(frame, frame, CV_GAUSSIAN, 3, 3); imgHSV := cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 3); // Change the color format from BGR to HSV cvCvtColor(frame, imgHSV, CV_BGR2HSV); imgThresh := GetThresholdedImage(imgHSV, False); // 色相のリング対策 if HMAX > 180 then begin imgThreshOr := GetThresholdedImage(imgHSV, True); cvOr(imgThreshOr, imgThresh, imgThresh, NIl); cvReleaseImage(imgThreshOr); end; // smooth the binary image using Gaussian kernel cvSmooth(imgThresh, imgThresh, CV_GAUSSIAN, 3, 3); ipDraw(pb1.Canvas.Handle, frame, pb1.ClientRect); ipDraw(pb2.Canvas.Handle, imgThresh, pb1.ClientRect); // Clean up used images cvReleaseImage(imgHSV); cvReleaseImage(imgThresh); cvReleaseImage(frame); end; procedure TMainForm.trackObject(imgThresh: pIplImage); var moments : pCvMoments; moment10, moment01, area: Double; posX, posY: Integer; begin // Calculate the moments of 'imgThresh' moments := allocMem(sizeof(TCvMoments)); cvMoments(imgThresh, moments, 1); moment10 := cvGetSpatialMoment(moments, 1, 0); moment01 := cvGetSpatialMoment(moments, 0, 1); area := cvGetCentralMoment(moments, 0, 0); // if the area<1000, I consider that the there are no object in the image and it's because of the noise, the area is not zero if (area > 1000) then begin // calculate the position of the ball posX := Trunc(moment10 / area); posY := Trunc(moment01 / area); if (lastX >= 0) and (lastY >= 0) and (posX >= 0) and (posY >= 0) then // Draw a yellow line from the previous point to the current point cvLine(imgTracking, cvPoint(posX, posY), cvPoint(lastX, lastY), cvScalar(0, 0, 255), 4); lastX := posX; lastY := posY; end; freemem(moments); end; procedure TMainForm.TrackingRedObjects(img: pIplImage); var imgHSV : pIplImage; imgThresh : pIplImage; frame : pIplImage; imgThreshOr : pIplImage; begin frame := cvCloneImage(img); cvSmooth(frame, frame, CV_GAUSSIAN, 3, 3); // smooth the original image using Gaussian kernel imgHSV := cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 3); cvCvtColor(frame, imgHSV, CV_BGR2HSV); // Change the color format from BGR to HSV imgThresh := GetThresholdedImage(imgHSV, False); // 色相のリング対策 if HMAX > 180 then begin imgThreshOr := GetThresholdedImage(imgHSV, true); cvOr(imgThreshOr, imgThresh, imgThresh, NIl); cvReleaseImage(imgThreshOr); end; cvSmooth(imgThresh, imgThresh, CV_GAUSSIAN, 3, 3); // smooth the binary image using Gaussian kernel // track the possition of the ball trackObject(imgThresh); // Add the tracking image and the frame cvAdd(frame, imgTracking, frame); ipDraw(pb1.Canvas.Handle, frame, pb1.ClientRect); ipDraw(pb2.Canvas.Handle, imgThresh, pb1.ClientRect); // Clean up used images cvReleaseImage(imgHSV); cvReleaseImage(imgThresh); cvReleaseImage(frame); end; procedure TMainForm.OnIdle(Sender: TObject; var Done: Boolean); var frame: pIplImage; begin if Assigned(capture) then begin frame := cvQueryFrame(capture); if Assigned(frame) then begin if rb2.Checked then DetectingRedObjects(frame) else if rb1.Checked then begin if not Assigned(imgTracking) then begin // create a blank image and assigned to 'imgTracking' which has the same size of original video imgTracking := cvCreateImage(cvGetSize(frame), IPL_DEPTH_8U, 3); cvZero(imgTracking); // covert the image, 'imgTracking' to black end; TrackingRedObjects(frame); end; end; end; Done := False; end; // RGB から HSV 変換ルーチン Hsu 色相 Saturation 彩度 Value 明度 procedure TMainForm.RGBtoHSV(Red, Green, Blue: Integer; var Hue, Saturation, Value: Double); var CMax, CMin, CG: Integer; begin Hue := 0; CMax := Max(Red, Max(Green, Blue)); // 最大値検索 CMin := Min(Red, Min(Green, Blue)); // 最小値検索 Value := CMax / 255; // 最大値が明度の値 // Value(明度) = 0 の時 if Value = 0 then begin Saturation := 0; exit; end; // Value(明度) > 0 の時 Saturation := (CMax - CMin) / CMax; // 彩度の計算 if CMax > CMin then begin // 色相の計算 CMAXとCMINが等しい場合は H(色相) = 0 CG := CMax - CMin; if Red = CMax then Hue := 60 * (Green - Blue) / CG else if Green = CMax then Hue := 60 * (Blue - Red) / CG + 120 else if Blue = CMax then Hue := 60 * (Red - Green) / CG + 240; if Hue < 0 then Hue := Hue + 360; end; end; // 表示画像からのHSV取得 procedure TMainForm.pb1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var RGBColor: Tcolor; H,S,V : Double; R,G,B : integer; begin RGBColor := pb1.Canvas.Pixels[x,y]; B := RGBColor and $00FF0000; B := B shr 16; G := RGBColor and $0000FF00; G := G shr 8; R := RGBColor and $000000FF; RGBtoHSV(R, G, B, H, S, V); memo1.Clear; memo1.Lines.Add('Hue ' + floatTostrF(H, ffFixed, 3, 0)); memo1.Lines.Add('Saturation ' + floatTostrF(S, ffFixed, 1, 3)); memo1.Lines.Add('Value ' + floatTostrF(V, ffFixed, 1, 3)); end; procedure TMainForm.CapStpBtnClick(Sender: TObject); begin Application.OnIdle := nil; if Assigned(capture) then cvReleaseCapture(capture); if Assigned(imgTracking) then cvReleaseImage(imgTracking); CapStpBtn.Enabled := False; CapSttBtn.Enabled := True; end; procedure TMainForm.Dispmess(mess: string); begin ShowMessagePos(mess, MainForm.Left + 20, MainForm.Top + 30); end; // 色相0~360°彩度0~1.0 明度0~1.0 が一般的ですが // OpenCVでは、色相0~180° 彩度0~255 明度0~255 です。 // 角値を1byteで表現しています procedure TMainForm.CapSttBtnClick(Sender: TObject); var ch: integer; begin val(LabeledEdit1.Text, HMAX, ch); if ch <> 0 then begin Dispmess('Hue(色相)上限に間違いがあります。'); exit; end; val(LabeledEdit2.Text, SMAX, ch); if ch <> 0 then begin Dispmess('Saturation(彩度)上限に間違いがあります。'); exit; end; val(LabeledEdit3.Text, VMAX, ch); if ch <> 0 then begin Dispmess('Value(明度)上限に間違いがあります。'); exit; end; val(LabeledEdit4.Text, HMIN, ch); if ch <> 0 then begin Dispmess('Hue(色相)下限に間違いがあります。'); exit; end; val(LabeledEdit5.Text, SMIN, ch); if ch <> 0 then begin Dispmess('Saturation(彩度)下限に間違いがあります。'); exit; end; val(LabeledEdit6.Text, VMIN, ch); if ch <> 0 then begin Dispmess('Value(明度)下限に間違いがあります。'); exit; end; if HMAX > 510 then begin Dispmess('Hue(色相)の上限の値が大きすぎます。'); exit; end; if HMIN > 360 then begin Dispmess('Hue(色相)の下限の値が大きすぎます。'); exit; end; if (HMAX < 0) or (HMIN < 0) then begin Dispmess('Hue(色相)の上限又は下限の値が0以下です。'); exit; end; if SMAX < SMIN then begin Dispmess('Saturation(彩度)の上限と下限の値が逆です。'); exit; end; if VMAX < VMIN then begin Dispmess('Value(明度)の上限と下限の値が逆です。'); exit; end; if HMAX < HMIN then HMAX := 360 + HMAX; // opencv では色相(Hue)の値は0~180° HMAX := HMAX / 2; HMIN := HMIN / 2; // 彩度(Saturation)の値は0~255 SMAX := SMAX * 255; SMIN := SMIN * 255; // 明度(Valu)の値は0~255 VMAX := VMAX * 255; VMIN := VMIN * 255; lastX := -1; lastY := -1; capture := cvCreateCameraCapture(CV_CAP_ANY); if Assigned(capture) then begin // 画像のサイズ設定 カメラの値に合わせて下さい。 cvSetCaptureProperty(Capture, CV_CAP_PROP_FRAME_WIDTH, 640); cvSetCaptureProperty(Capture, CV_CAP_PROP_FRAME_HEIGHT, 480); Application.OnIdle := OnIdle; CapStpBtn.Enabled := True; CapSttBtn.Enabled := False; end; end; procedure TMainForm.FormCreate(Sender: TObject); begin CapStpBtn.Enabled := False; Memo1.Clear; Memo1.Font.Height := 15; end; procedure TMainForm.FormDestroy(Sender: TObject); begin if Assigned(capture) then cvReleaseCapture(capture); if Assigned(imgTracking) then cvReleaseImage(imgTracking); end; end.