2013-09-12 12:50:55 +02:00
|
|
|
// *****************************************************************
|
|
|
|
// Delphi-OpenCV Class Demo
|
|
|
|
// Copyright (C) 2013 Project Delphi-OpenCV
|
|
|
|
// ****************************************************************
|
|
|
|
// Contributor:
|
|
|
|
// laentir Valetov
|
|
|
|
// email:laex@bk.ru
|
|
|
|
// ****************************************************************
|
|
|
|
// 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.
|
|
|
|
// *******************************************************************
|
|
|
|
// Original:
|
|
|
|
// http://public.cranfield.ac.uk/c5354/teaching/ml/examples/c/knn_ex/
|
|
|
|
// *******************************************************************
|
|
|
|
|
2014-04-04 19:14:06 +02:00
|
|
|
program clsKNN;
|
2013-09-12 12:50:55 +02:00
|
|
|
|
|
|
|
{$APPTYPE CONSOLE}
|
|
|
|
{$POINTERMATH ON}
|
|
|
|
{$R *.res}
|
|
|
|
|
|
|
|
uses
|
|
|
|
System.SysUtils,
|
|
|
|
System.Classes,
|
|
|
|
Core.types_c,
|
|
|
|
core_c,
|
|
|
|
ml;
|
|
|
|
|
2014-04-04 19:14:06 +02:00
|
|
|
{ DEFINE Test }
|
2013-09-12 12:50:55 +02:00
|
|
|
|
|
|
|
Const
|
|
|
|
CarTrain_FileName = 'resource\car.train';
|
|
|
|
{$IFDEF Test}
|
2014-04-04 19:14:06 +02:00
|
|
|
CarTest_FileName = 'resource\car.test';
|
2013-09-12 12:50:55 +02:00
|
|
|
NUMBER_OF_TESTING_SAMPLES = 345;
|
|
|
|
{$ELSE}
|
2014-04-04 19:14:06 +02:00
|
|
|
CarTest_FileName = 'resource\car.data';
|
2013-09-12 12:50:55 +02:00
|
|
|
NUMBER_OF_TESTING_SAMPLES = 1728;
|
|
|
|
{$ENDIF}
|
2014-04-04 19:14:06 +02:00
|
|
|
ATTRIBUTES_PER_SAMPLE = 6; // not the last as this is the class
|
|
|
|
NUMBER_OF_CLASSES = 4; // classes 0->3
|
2013-09-12 12:50:55 +02:00
|
|
|
Classes: array [0 .. NUMBER_OF_CLASSES - 1] of String = ('unacc', 'acc', 'good', 'vgood');
|
2014-04-04 19:14:06 +02:00
|
|
|
NUMBER_OF_TRAINING_SAMPLES = 1383;
|
2013-09-12 12:50:55 +02:00
|
|
|
|
|
|
|
function hash(str: AnsiString): Single;
|
|
|
|
Var
|
|
|
|
i, R: Integer;
|
|
|
|
begin
|
2014-04-04 19:14:06 +02:00
|
|
|
R := 5381;
|
|
|
|
for i := 1 to Length(str) do
|
|
|
|
R := (R shl 5) + R + ord(str[i]);
|
2013-09-12 12:50:55 +02:00
|
|
|
Result := R;
|
|
|
|
end;
|
|
|
|
|
|
|
|
function read_data_from_csv(const filename: String; Var data, _classes: TCvMat): Boolean;
|
|
|
|
var
|
|
|
|
i, line, attribute: Integer;
|
2014-04-04 19:14:06 +02:00
|
|
|
S: TStringList;
|
|
|
|
Sp: TStringList;
|
2013-09-12 12:50:55 +02:00
|
|
|
begin
|
|
|
|
if not FileExists(filename) then
|
|
|
|
begin
|
2014-04-04 19:14:06 +02:00
|
|
|
WriteLn('ERROR: cannot read file ', filename);
|
2013-09-12 12:50:55 +02:00
|
|
|
Exit(False); // all not OK
|
|
|
|
end;
|
2014-04-04 19:14:06 +02:00
|
|
|
S := TStringList.Create;
|
2013-09-12 12:50:55 +02:00
|
|
|
Sp := TStringList.Create;
|
|
|
|
try
|
|
|
|
S.LoadFromFile(filename);
|
|
|
|
// for each sample in the file
|
|
|
|
for line := 0 to S.Count - 1 do
|
|
|
|
begin
|
|
|
|
Sp.CommaText := S[line];
|
|
|
|
// for each attribute on the line in the file
|
|
|
|
for attribute := 0 to Sp.Count - 1 do
|
|
|
|
if attribute = 6 then
|
|
|
|
// last attribute is the class
|
|
|
|
begin
|
|
|
|
// find the class number and record this
|
|
|
|
for i := 0 to NUMBER_OF_CLASSES - 1 do
|
|
|
|
if SameText(Classes[i], Sp[attribute]) then
|
|
|
|
PSingle(CV_MAT_ELEM(_classes, CV_32FC1, line, 0))^ := i;
|
|
|
|
end
|
|
|
|
else
|
|
|
|
// for all other attributes just read in the string value
|
|
|
|
// and use a hash function to convert to to a float
|
|
|
|
// (N.B. openCV uses a floating point decision tree implementation!)
|
|
|
|
begin
|
2013-09-15 14:02:26 +02:00
|
|
|
PSingle(CV_MAT_ELEM(data, CV_32FC1, line, attribute))^ := hash(AnsiString(Sp[attribute]));
|
2013-09-12 12:50:55 +02:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
finally
|
|
|
|
S.Free;
|
|
|
|
Sp.Free;
|
|
|
|
end;
|
|
|
|
Result := True;
|
|
|
|
end;
|
|
|
|
|
|
|
|
Var
|
|
|
|
k, i: Integer;
|
|
|
|
training_data, //
|
|
|
|
training_classifications: pCvMat;
|
|
|
|
testing_data, //
|
|
|
|
testing_classifications: pCvMat;
|
|
|
|
|
2014-04-04 19:14:06 +02:00
|
|
|
var_type: pCvMat;
|
|
|
|
resultNode: Float; // node returned from a prediction
|
|
|
|
knn: TCvKNearest;
|
|
|
|
tsample: Integer;
|
2013-09-12 12:50:55 +02:00
|
|
|
test_sample: TCvMat;
|
|
|
|
|
2014-04-04 19:14:06 +02:00
|
|
|
correct_class: Integer;
|
|
|
|
wrong_class: Integer;
|
2013-09-12 12:50:55 +02:00
|
|
|
false_positives: array [0 .. NUMBER_OF_CLASSES - 1] of Integer;
|
|
|
|
|
|
|
|
begin
|
|
|
|
try
|
|
|
|
// lets just check the version first
|
|
|
|
// WriteLn(Format('OpenCV version %s (%d.%d.%d)', [CV_VERSION, CV_MAJOR_VERSION, CV_MINOR_VERSION,CV_SUBMINOR_VERSION]);
|
|
|
|
k := 10;
|
|
|
|
// define training data storage matrices (one for attribute examples, one
|
|
|
|
// for classifications)
|
2014-04-04 19:14:06 +02:00
|
|
|
training_data := cvCreateMat(NUMBER_OF_TRAINING_SAMPLES, ATTRIBUTES_PER_SAMPLE, CV_32FC1);
|
|
|
|
training_classifications := cvCreateMat(NUMBER_OF_TRAINING_SAMPLES, 1, CV_32FC1);
|
2013-09-12 12:50:55 +02:00
|
|
|
// define testing data storage matrices
|
2014-04-04 19:14:06 +02:00
|
|
|
testing_data := cvCreateMat(NUMBER_OF_TESTING_SAMPLES, ATTRIBUTES_PER_SAMPLE, CV_32FC1);
|
|
|
|
testing_classifications := cvCreateMat(NUMBER_OF_TESTING_SAMPLES, 1, CV_32FC1);
|
2013-09-12 12:50:55 +02:00
|
|
|
|
|
|
|
// define all the attributes as categorical (i.e. categories)
|
|
|
|
// alternatives are CV_VAR_CATEGORICAL or CV_VAR_ORDERED(=CV_VAR_NUMERICAL)
|
|
|
|
// that can be assigned on a per attribute basis
|
|
|
|
// this is a classification problem (i.e. predict a discrete number of class
|
|
|
|
// outputs) so also the last (+1) output var_type element to CV_VAR_CATEGORICAL
|
|
|
|
|
2014-04-04 19:14:06 +02:00
|
|
|
var_type := cvCreateMat(ATTRIBUTES_PER_SAMPLE + 1, 1, CV_8U);
|
|
|
|
cvSet(var_type, cvScalarAll(CV_VAR_CATEGORICAL)); // all inputs are categorical
|
2013-09-12 12:50:55 +02:00
|
|
|
try
|
|
|
|
// load training and testing data sets
|
|
|
|
if read_data_from_csv(CarTrain_FileName, training_data^, training_classifications^) and
|
|
|
|
read_data_from_csv(CarTest_FileName, testing_data^, testing_classifications^) then
|
|
|
|
begin
|
|
|
|
|
|
|
|
// train K-Nearest Neighbour classifier (using training data)
|
|
|
|
|
2014-04-04 19:14:06 +02:00
|
|
|
WriteLn('Using training database: ', CarTrain_FileName);
|
2013-09-12 12:50:55 +02:00
|
|
|
knn := CreateCvKNearest;
|
|
|
|
|
2014-04-04 19:14:06 +02:00
|
|
|
knn.train(training_data, training_classifications, nil, False, k);
|
2013-09-12 12:50:55 +02:00
|
|
|
|
|
|
|
// perform classifier testing and report results
|
|
|
|
|
|
|
|
correct_class := 0;
|
2014-04-04 19:14:06 +02:00
|
|
|
wrong_class := 0;
|
|
|
|
FillChar(false_positives, SizeOf(false_positives), 0);
|
|
|
|
WriteLn('Using testing database: ', CarTest_FileName);
|
2013-09-12 12:50:55 +02:00
|
|
|
|
|
|
|
for tsample := 0 to NUMBER_OF_TESTING_SAMPLES - 1 do
|
|
|
|
begin
|
|
|
|
// extract a row from the testing matrix
|
2014-04-04 19:14:06 +02:00
|
|
|
cvGetRow(testing_data, @test_sample, tsample);
|
2013-09-12 12:50:55 +02:00
|
|
|
// run decision tree prediction
|
|
|
|
|
2014-04-04 19:14:06 +02:00
|
|
|
resultNode := knn.find_nearest(@test_sample, k, nil, nil, nil);
|
2013-09-12 12:50:55 +02:00
|
|
|
|
|
|
|
WriteLn(Format('Testing Sample %d -> class result %s', [tsample, Classes[Trunc(resultNode)]]));
|
|
|
|
|
|
|
|
// if the prediction and the (true) testing classification are the same
|
|
|
|
// (N.B. openCV uses a floating point decision tree implementation!)
|
|
|
|
|
|
|
|
if abs(resultNode - PSingle(CV_MAT_ELEM(testing_classifications^, CV_32FC1, tsample, 0))^) >= FLT_EPSILON then
|
|
|
|
begin
|
|
|
|
// if they differ more than floating point error => wrong class
|
|
|
|
Inc(wrong_class);
|
|
|
|
Inc(false_positives[Trunc(resultNode)]);
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
// otherwise correct
|
|
|
|
Inc(correct_class);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-04-04 19:14:06 +02:00
|
|
|
WriteLn(Format('Results on the testing database: %s'#13#10#9'Correct classification: %d (%4.2f%%)'#13#10#9 + 'Wrong classifications: %d (%4.2f%%)',
|
|
|
|
[CarTest_FileName, correct_class, correct_class * 100 / NUMBER_OF_TESTING_SAMPLES, wrong_class, wrong_class * 100 / NUMBER_OF_TESTING_SAMPLES]));
|
2013-09-12 12:50:55 +02:00
|
|
|
|
|
|
|
for i := 0 to NUMBER_OF_CLASSES - 1 do
|
|
|
|
begin
|
2014-04-04 19:14:06 +02:00
|
|
|
WriteLn(Format(#9'Class %s false postives %d (%4.2f%%)', [Classes[i], false_positives[i], false_positives[i] * 100 / NUMBER_OF_TESTING_SAMPLES]));
|
2013-09-12 12:50:55 +02:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
finally
|
|
|
|
// free all memory
|
2014-04-04 19:14:06 +02:00
|
|
|
ReleaseCvKNearest(knn);
|
2013-09-12 12:50:55 +02:00
|
|
|
cvReleaseMat(training_data);
|
|
|
|
cvReleaseMat(training_classifications);
|
|
|
|
cvReleaseMat(testing_data);
|
|
|
|
cvReleaseMat(testing_classifications);
|
|
|
|
cvReleaseMat(var_type);
|
|
|
|
|
|
|
|
WriteLn('Press Enter to Exit');
|
|
|
|
Readln;
|
|
|
|
end;
|
|
|
|
except
|
|
|
|
on E: Exception do
|
2014-04-04 19:14:06 +02:00
|
|
|
WriteLn(E.ClassName, ': ', E.Message);
|
2013-09-12 12:50:55 +02:00
|
|
|
end;
|
|
|
|
|
|
|
|
end.
|