Você está na página 1de 40

F O R D E L P H I, L A Z A R U S, A N D P A S C A L

R E L A T E D L A N G U A G E S / A N D R O I D,
I O S, M A C , W I N D O W S & L I N U X
P R I N T E D, P D F, & O N L I N E V I E W

BLAISE PASCAL MAGAZINE 58

GENERICS PART III: CONTAINERS


BY MICHAEL VAN CANNEYT
FASTREPORT 6 HOW TO SPEED UP YOUR FIREMONKEY APPLICATION FOR
WINDOWS AND MAC OS
BY DEN ZUBOV
COMBINATIONS IN DISCRETE MATHEMATICS
BY DAVID DIRKSE
SUDOKU
BY DAVID DIRKSE
ARTIFICIAL INTELLGENCE PART III
BOIAN MITOV

PRINTED ISSUE PRICE € 15,00


DOWNLOAD ISSUE PRICE € 5,00
BLAISE PASCAL MAGAZINE 58
D E L P H I, L A Z A R U S, S M A R T M O B I L E S T U D I O,
A N D P A S C A L R E L A T E D L A N G U A G E S
F O R A N D R O I D, I O S, M A C, W I N D O W S & L I N U X

CONTENTS
ARTICLES:

GENERICS PART III: CONTAINERS 1/5 PAGE 5


BY MICHAEL VAN CANNEYT
A SUDOKU HELPER/SOLVER 1/4 PAGE 10
BY DAVID DIRKSE
FASTREPORT 6 HOW TO SPEEDUP YOUR FIREMONKEY APPLICATION 1/4 PAGE 14
FOR WINDOWS AND MAC OS
BY DEN ZUBOV
COMBINATIONS IN DISCRETE MATHEMATICS 1/2 PAGE 19
BY DAVID DIRKSE
ARTIFICIAL INTELLGENCE PART III
FACIAL RECOGNITION IMPROVED 1/18 PAGE 21
BY BOIAN MITOV

Tetraaniline in Full Bloom – A SEM image of a thin-sheet


network composed of doped aniline oligomers.
The aggregated sheets in the upper right corner forms a cluster
that mimics the look of a flower, whereas other flexible sheets
represents leaves and stems. Scientifically, this morphology
combines high surface area and electrical conductivity, rendering
it ideal for organic supercapacitors and sensors. (Image courtesy
of the Materials Research Society Science as Art Competition and
Yue Wang, University of California, Los Angeles)

A scanning electron microscope (SEM) is a type of electron


microscope that produces images of a sample by scanning it with
a focused beam of electrons. The electrons interact with atoms in
the sample, producing various signals that contain information
about the sample's surface topography and composition. The
electron beam is generally scanned in a raster scan pattern, and
the beam's position is combined with the detected signal to
produce an image. SEM can achieve resolution better than 1
nanometer. Specimens can be observed in high vacuum, in low
vacuum, in wet conditions (in environmental SEM), and at a wide
range of cryogenic or elevated temperatures.

The most common SEM mode is detection of secondary electrons


emitted by atoms excited by the electron beam. The number of
secondary electrons that can be detected depends, among other
things, on specimen topography. By scanning the sample and
collecting the secondary electrons that are emitted using a special
detector, an image displaying the topography of the surface is
created.

ADVERTISERS
BARNSTEN PAGE 4
BLAISE ONLINE VIEW INTRODUCTION PAGE 18
COMPONENTS4DEVELOPERS PAGE 40
NEXUS PAGE 39

Publisher: Foundation for Supporting the Pascal Programming Language


in collaboration with the Dutch Pascal User Group (Pascal Gebruikers Groep)
© Stichting Ondersteuning Programmeertaal Pascal

2 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


Stephen Ball Peter Bijlsma -Editor Dmitry Boyarintsev
http://delphiaball.co.uk peter @ blaisepascal.eu dmitry.living @ gmail.com
@DelphiABall

Michaël Van Canneyt, Marco Cantù David Dirkse


michael @ freepascal.org www.marcocantu.com www.davdata.nl
marco.cantu @ gmail.com E-mail: David @ davdata.nl

Benno Evers Bruno Fierens Primož Gabrijelčič


b.evers www.tmssoftware.com www.primoz @ gabrijelcic.org
@ everscustomtechnology.nl bruno.fierens @ tmssoftware.com

Fikret Hasovic Cary Jensen Peter Johnson


fhasovic @ yahoo.com www.jensendatasystems.com http://delphidabbler.com
http://caryjensen.blogspot.nl delphidabbler@gmail.com

Max Kleiner
www.softwareschule.ch
max @ kleiner.com

John Kuiper Wagner R. Landgraf KimMadsen


Kim Madsen
john_kuiper @ kpnmail.nl wagner @ tmssoftware.com www.component4developers
kbm @ components4developers.com

Andrea Magni Boian Mitov Jeremy North


www.andreamagni.eu mitov @ mitov.com
andrea.magni @ gmail.com
jeremy.north @ gmail.com
www.andreamagni.eu/wp
Detlef Overbeek - Editor in Chief Howard Page Clark Heiko Rompel
www.blaisepascal.eu hdpc @ talktalk.net info@rompelsoft.de
editor @ blaisepascal.eu

Wim Van Ingen Schenau -Editor Peter van der Sman Rik Smit
wisone @ xs4all.nl sman @ prisman.nl rik @ blaisepascal.eu
www.romplesoft.de

Bob Swart B.J. Rao Daniele Teti


www.eBob42.com contact@intricad.com www.danieleteti.it
Bob @ eBob42.com d.teti @ bittime.it

Anton Vogelaar Siegfried Zuhr


ajv @ vogelaar-electronics.com siegfried @ zuhr.nl

Editor - in - chief
Detlef D. Overbeek, Netherlands Tel.: +31 (0)30 890.66.44 / Mobile: +31 (0)6 21.23.62.68
News and Press Releases email only to editor@blaisepascal.eu

Editors
Peter Bijlsma, W. (Wim) van Ingen Schenau, Rik Smit,
Correctors
Howard Page-Clark, James D. Duff
Trademarks
All trademarks used are acknowledged as the property of their respective owners.
Caveat Whilst we endeavour to ensure that what is published in the magazine is correct, we cannot accept responsibility for any errors or omissions.
If you notice something which may be incorrect, please contact the Editor and we will publish a correction where relevant.
Subscriptions ( 2013 prices )
1: Printed version: subscription € 80.-- Incl. VAT 6 % (including code, programs and printed magazine,
10 issues per year excluding postage).
2: Electronic - non printed subscription € 50.-- Incl. VAT 21% (including code, programs and download magazine)

Subscriptions can be taken out online at www.blaisepascal.eu or by written order, or by sending an email to office@blaisepascal.eu
Subscriptions can start at any date. All issues published in the calendar year of the subscription will be sent as well.
Subscriptions run 365 days. Subscriptions will not be prolonged without notice. Receipt of payment will be sent by email.
Subscriptions can be paid by sending the payment to:
ABN AMRO Bank Account no. 44 19 60 863 or by credit card: Paypal
Name: Pro Pascal Foundation-Foundation for Supporting the Pascal Programming Language (Stichting Ondersteuning Programeertaal Pascal)
IBAN: NL82 ABNA 0441960863 BIC ABNANL2A VAT no.: 81 42 54 147 (Stichting Programmeertaal Pascal)
Subscription department Edelstenenbaan 21 / 3402 XA IJsselstein, The Netherlands / Tel.: + 31 (0) 30 890.66.44 / Mobile: + 31 (0) 6 21.23.62.68
office@blaisepascal.eu

Copyright notice
All material published in Blaise Pascal is copyright © SOPP Stichting Ondersteuning Programeertaal Pascal unless otherwise noted and may
not be copied, distributed or republished without written permission. Authors agree that code associated with their articles will be made
available to subscribers after publication by placing it on the website of the PGG for download, and that articles and code will be placed on
distributable data storage media. Use of program listings by subscribers for research and study purposes is allowed, but not for commercial
purposes. Commercial use of program listings and code is prohibited without the written permission of the author.

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 3


Delphi 10.1 Berlin Advanced Training
9-10 februari 2017 - Utrecht
This training is meant for Delphi Developers that want to
update their knowledge kennis to the latest standard and
newest techniques, methods and platforms.
After this training you will be capable to migrate/upgrade
your applications towards the Windows 10 platform with
complete Windows 10 look-and-feel. You also will learn
to code much quicker and be able to create a much
better and enhanced code so you can be responsive to
the latest developments according the trends and
demands of your customers.

4 Certified trainer and experienced Delphi MVP


4 Interaction is open
4 Small classes
4 Participation-certificate after training

Day 1 - IDE, Language, Parallel Programming, Windows 10, FireMonkey


Dag 2 - Unicode, FireDAC, Livebindings, Touch and Gestures

€ 995,-- excl. VAT


Including course material, coffee/thee/lunch/refreshments

Location: Utrecht - NETHERLANDS Dates: 9+10 februari 2017

Landing page: https://www.barnsten.com/nl/events?___from_store=defaultore=nl

4 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


GENERICS PART III: CONTAINERS PAGE 1/5
AN INTRODUCTION TO GENERICS: CONTAINERS
BY MICHAEL VAN CANNEYT
starter expert &
Delphi and Lazarus Ideally, it should not be necessary to have to call
Arguably one of the biggest uses of generics is for an inherited method.
container classes: lists, collections, dictionaries. It is also clear that TList or TObjectList
In this article we’ll explain how to use generics for cannot be used with arbitrary records or arrays.
such classes.
Generics to the rescue
INTRODUCTION The solution to the problem outlined above
Every Object Pascal programmer sooner or later would of course be that the element type for
comes in contact with the classes unit or the TList can be specified when declaring a list.
contnrs unit. It contains some container classes: This is of course exactly what generics are for.
TList, TStringList, TCollection, We'll attempt to create a generic list class, and
TObjectList TStack and TQueue.
see what kind of problems pop up when trying to
These are all examples of container classes,
implement it.
which are used to maintain lists or collections
In its simplest form, a list class is of course
of objects. In general, they work well in daily use
nothing more than an Array, with some methods
cases, for specialized use it is probably
attached to it. So, basically a list class would look
better to write your own.
like this:
There are 2 disadvantages to these classes: unit list1;
1. They work only with pointers or classes.
2. When creating a descendent, one almost interface
invariably ends up doing typecasts. Type
Let us illustrate this with an example: TList<ET> = Class(TObject)
Assume a class TCar, and we want to create a Private
list FItems : Array of ET;
Function GetE(AIndex : Integer) : ET;
of cars : TCarList, where the default array Procedure SetE(AIndex : Integer; AValue : ET);
property is of type Tcar. Public
Nothing could be simpler: Property Items [AIndex : Integer] : ET Read GetE
Write SetE; default;
uses contnrs; end;
Type
TCar = Class(TObject) Implementation
Brand : String;
Seats : Integer; Function TList<ET>.GetE(AIndex : Integer) : ET;
end; begin
TCarList = Class(TObjectList) Result:=FItems[AIndex];
Function GetC (AIndex : Integer): TCar; end;
Procedure SetC (AIndex : Integer; AValue : TCar);
Public Procedure TList<ET>.SetE(AIndex : Integer; AValue : ET);
Property Cars[AIndex] : TCar Read GetC begin
Write SetC;default; FItems[AIndex]:=AValue;
end; end;

end.
However, the uglyness is in the implementation:
Function TCarList.GetC (AIndex : Integer): TCar;
begin
Result:=TCar(Items[AIndex]); The getter and setter do not contain any code for
end;
checking the validity of the index, for simplicity.
It is clear from the code that the access to the
A typecast is necessary to cast the inherited Items elements in the array is direct, and that no
property (of type TObject) to TCar. It also typecasts are necessary because the elements are
requires a call to an inherited method. kept in a dynamic array of the correct type:
The setter is less problematic, as no typecast is FItems : Array of ET;
needed: To use this, one would write something like this:
Function TCarList.SetC (AIndex : Integer; AValue: TCar); Type
begin TCar = record
Items[AIndex]:=AValue; Brand : String;
end; Seats : Smallint;
end;
TCarList = TList<TCar>;

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 5


GENERICS PART III: CONTAINERS PAGE 2/5
AN INTRODUCTION TO GENERICS: CONTAINERS

Or declare a variable as: With implementation


Var Procedure TList<ET>.Insert(AIndex : Integer; AValue : ET);
Cars : TList<TCar>; Var L : Integer;
begin
L:=Length(FItems);
At the first use of this class, it would result Setlength(FItems,L+1);
in an access violation, as there is no way to Move(FItems[AIndex],FItems[Aindex+1],SizeOf(ET)*(L-Aindex));
add elements to the array: the setter can FItems[AIndex]:=AValue;
end;
only set the items at an existing index in the
array, and the length of the array is zero to
begin with. Again, nothing special in this code, except the use of
To remedy this, we add a method called SizeOf: as can be seen, it can be used with template
Add to our class: Procedure Add(AValue : ET); types in generics: when specializing, the compiler
With implementation: knows the size of the type used to specialize the list
with.
Procedure TList<ET>.Add(AValue : ET);
Var L : Integer; The opposite method of insert, Delete, requires a bit
begin more care:
L:=Length(FItems); Procedure Delete(AIndex : Integer);
Setlength(FItems,L+1);
FItems[L]:=AValue;
end; With implementation:
Procedure TList<ET>.Delete(Aindex : integer);
Var L : Integer;
This will not make for the fastest TList begin
implementation, but it serves the purpose. FItems[Aindex]:=Default(ET);
The code is nothing special: if it was not for the type L:=Length(FItems);
template ET, there is little to indicate that this is a Move(FItems[AIndex+1],FItems[Aindex],
generic method. SizeOf(ET)*(L-1-Aindex));
Dec(L);
Now the generic list class becomes usable: FillChar(FItems[L], SizeOf(ET), 0);
SetLength(FItems,L);
Var end;
MyCars : TList<TCar>;
C : TCar;
begin There are two tricky parts in this code.
MyCars:=TCarList.Create; The first line is needed to make sure that the item
try
C.Brand:='BMW'; to be deleted is properly finalized: if we would
C.Seats:=2; omit this line, managed types in ET such as strings
MyCars.Add(C); (or ET itself, if it is a managed type) would end up
With MyCars[0] do having wrong reference counts, resulting in
Writeln('Brand: ',Brand,', Seats:', Seats); memory leaks or access violations.
Finally
MyCars.Free;
For this, the Default compiler intrinsic is used.
end; This function can be represented as:
end. Function Default(T : AType) : AType;

it produces an empty value for the type T that is


This will work as expected: it will print the name passed to it. In essence, it returns
of a German car brand.
Result:=AType(FillChar(Result,SizeOf(AType),0));
INITIALIZING/FINALIZING VARIABLES OF By assigning an empty value to FItems[AIndex],
UNKNOWN TYPE we ensure that it is properly cleared. Only then we
Adding an element to the list presents no move all the other values in the array to a lower
difficulties, we hardly notice the use of generics. position in the array.
How about inserting an element in the list at some For the last element in the array, we do not want to
arbitrary position? To check, we add a method properly clear it. Instead, we just want to zero it
called Insert to our class: out, so no finalization can occur when the array is
Procedure Insert(AIndex : Integer; AValue : ET); cleared or resized. For this, we need the
FillChar(FItems[L], SizeOf(ET), 0);
statement.

6 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


GENERICS PART III: CONTAINERS PAGE 3/5
AN INTRODUCTION TO GENERICS: CONTAINERS

INDEXOF AND SORT: COMPARING ELEMENTS The following will then work:
Often, one needs to find the position of an element program cars5;
in the list (e.g. in order to remove it using delete). uses generics.defaults,list5;
Finding the position is usually handled with
IndexOf: Type
TCar = record
function IndexOf(const AValue: ET): Integer; Brand : String;
Seats : Smallint;
A naive implementation would be: end;
TCarList = TList<TCar>;
Function TList<ET>.IndexOf(Const AValue : ET) : Integer;
begin Var
Result:=Length(FItems)-1; MyCars : TList<TCar>;
While (Result>=0) and Not (AValue=FItems[Result]) do C1,C2 : TCar;
Dec(Result);
end; begin
MyCars:=TCarList.Create;
However, the compiler will complain about the try
C1.Brand:='BMW';
comparison, telling us that it doesn't know how to
C1.Seats:=2;
compare cars: MyCars.Add(C1);
list5.pas(30,36) C2.Brand:='Peugeot';
Error: Operator is not overloaded: "TCar" = "TCar" C2.Seats:=4;
MyCars.Add(C2);
In Free Pascal, the solution is to implement an MyCars.Delete(0);
operator overload of the "='' operatior for the TCar Writeln('Peugeot : ',MyCars.IndexOf(C2));
type. In Delphi, this is not sufficient. Instead, a Writeln('BMW : ',MyCars.IndexOf(C1));
comparer interface is needed. The comparer Finally
interface is defined in the Generics.Defaults unit MyCars.Free;
end;
(also supplied with Free Pascal): end.
IComparer<T> = interface
function Compare(constref Left, Right: T): Integer; overload;
end;

TComparer<T> = class(TInterfacedObject, IComparer<T>)


public
class function Default: IComparer<T>; static; It will nicely print
function Compare(constref ALeft, ARight: T): Integer; virtual; abstract; overload; Peugeot : 0
end; BMW : -1
The Default class function of TComparer will create
It first sight, all is well implemented, but
a comparer interface that checks equality of two
unfortunately, this is not completely the case.
elements of the type T. For brevity, two class
For example, the following will not work:
functions were omitted from the TComparer class,
we'll get back to them further in the text. MyCars.Add(C2);
Using this interface, we can implement our IndexOf MyCars.Delete(0);
function to compare two cars: UniqueString(C2.Brand);
Writeln('Peugeot :
Function TList<ET>.IndexOf(Const AValue : ET) : Integer;
Var C : IComparer<ET>;
',MyCars.IndexOf(C2));
begin Writeln('BMW :
C:=TComparer<ET>.Default; ',MyCars.IndexOf(C1));
Result:=Length(FItems)-1;
While (Result>=0) and (C.Compare(AValue,FItems[Result])<>0) do
Dec(Result);
end; this code prints
Peugeot : -1
BMW : -1

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 7


GENERICS PART III: CONTAINERS PAGE 4/5
AN INTRODUCTION TO GENERICS: CONTAINERS

The code reports that no car brand Peugeot can be But this is cumbersome to call. Also, looking
found ! ahead it is reasonable to assume that we would
The reason is that the default comparer will add a Sort method to the list, and that will also
compare 2 records byte by byte. The first field of need a comparer interface. So, it is better to add
TCar (Brand) is a string, which in essence is a it as a field to the class, and pass it on in a
pointer to an array of characters. This means that constructor:
comparing the records byte by byte will compare Type
2 pointers. The UniqueString makes sure the TList<ET> = Class(TObject)
pointer to the characters Peugeot is unique, Private
FComparer : IComparer<ET>;
hence the default comparer is returning false, Public
even though the value of the string is the same. Constructor Create; overload;
To solve this properly, we need in essence to Constructor Create(AComparer : IComparer<ET>);
provide a proper comparer. This can be done overload;
end;
with the TComparer.Construct class function,
which we omitted from the earlier declaration:
Type
TComparisonFunc<T> = function(constref Left, Right: T): Integer;

TComparer<T> = class(TInterfacedObject, IComparer<T>)


public
class function Default: IComparer<T>; static;
function Compare(constref ALeft, ARight: T): Integer; virtual; abstract; overload;
class function Construct(const AComparison: TComparisonFunc<T>): IComparer<T>; overload;
end;

Construct takes a function which compares 2 (other fields, methods and properties have been
variables of type T, and constructs an IComparer emitted for brevity). The implementation of the
interface for the type T. (the same can be done with a constructors is quite simple:
method instead of a plain function)
Thus, if we write a function Constructor TList<ET>.Create(AComparer :
IComparer<ET>);
Function CompareCars(constref C1,C2 : TCar) : integer; begin
begin FComparer:=AComparer;
if C1.Brand<C2.Brand then end;
Result:=-1
else if C1.Brand>C2.Brand then Constructor TList<ET>.Create;
Result:=1;
else begin
Result:=C1.Seats-C2.Seats; FComparer:=TComparer<ET>.Default;
end; end;

We can construct a comparer as follows:


CC : IComparer<TCar>; The result of this code (using two overloaded
begin constructors) is that we can construct a list with
CC:=TComparer<TCar>.Construct(@CompareCars); a default comparer (suitable for simple types such
end; as integers, enumerators etc), or we can pass on a
comparer of our own, as shown in the following
We still need somehow to pass the comparer to the example:
IndexOf function. One way of doing this would be
to pass it to the IndexOf function:
function IndexOf(const AValue: ET; AComparer : IComparer<ET>): Integer;

8 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


GENERICS PART III: CONTAINERS PAGE 5/5
AN INTRODUCTION TO GENERICS: CONTAINERS
program cars6;

uses generics.defaults,list6;

Type
TCar = record
Brand : String;
Seats : Smallint;
end;
TCarList = TList<TCar>;

Function CompareCars(constref C1,C2 : TCar) : integer;


begin
if C1.Brand<C2.Brand then
Result:=-1
else if C1.Brand>C2.Brand then
Result:=1
else
Result:=C1.Seats-C2.Seats;
end;

Var
MyCars : TList<TCar>;
C1,C2 : TCar;
CC : IComparer<TCar>;

begin
CC:=TComparer<TCar>.Construct(@CompareCars);
MyCars:=TCarList.Create(CC);
try
C1.Brand:='BMW';
C1.Seats:=2;
MyCars.Add(C1);
C2.Brand:='Peugeot';
C2.Seats:=4;
MyCars.Add(C2); Conclusion
MyCars.Delete(0); In this article, we discussed a use case for
Writeln('Peugeot : ',MyCars.IndexOf(C2)); generics which is quite common: creating a
Writeln('BMW : ',MyCars.IndexOf(C1)); container class. In implementing a sample
C1.Brand:='Peugeot'; container class, we encountered 2 common
C1.Seats:=4; problems; initializing a variable of unknown
UniqueString(C1.Brand); type, and comparing 2 variables of unknown
Writeln('Peugeot (2) : ',MyCars.IndexOf(C1)); type. The solution to the first problem led
Finally us to the use of the Default function, the
MyCars.Free; solution of the second problem led us to the
end; use of the IComparer interface and the
end. TComparer class. Luckily, one does not need
to implement such container classes
A nice side effect of using a comparer is that we manually: they are implemented in the
can construct two different lists, once with the generics.collections unit.
compare above, and for instance once with a
case insensitive comparer:
Function CompareCarsCI(constref C1,C2 : TCar) : integer;
begin
Result:=CompareText(C1.Brand,C2.Brand);
If Result=0 then
Result:=C1.Seats-C2.Seats;
end;

Using this comparer, the IndexOf function will


be case insensitive.

Issue Nr 10
9 2016
2016
BLAISE
BLAISE
PASCAL
PASCAL
MAGAZINE
MAGAZINE 9
9
SE
DIRK html

DAVID
mes.

DAVID DIRKSE COMPUTER MATH & GAMES IN PASCAL


h_Ga
rMat
pute
/Com
irkse
vidD
les at u/Da
cal.e
presa epas
.blais
www

HOWARD PAGE-CLARK
A SUDOKU HELPER/SOLVER PAGE 1/4
BY DAVID DIRKSE proce
var
dure
;

begin := 1 to
for i
begin

end ;
9 do

starter
end;

expert H&
R MAT
PUTE PASCAL
COM
ES IN
GAM
DATA STRUCTURES
This article describes a Delphi project to assist in the type TMove = (mtFree,mtOrg,mtPlay);
solving of sudoku puzzels.
This assistance includes : TField = record
● entry, saving and opening of puzzles mt : TMove;
● display of options move : byte;
● finding solutions by adding/removing numbers options : word;
or options end;
● full solution by computer and test for uniquness.
● game analyses: show possible option reductions var game : array[1..9,1..9] of Tfield;

For each field there is a record of type Tfield.


mt= mtOrg :
field holds original number (color is brown)
mt=mtPlay :
field contains number entered by puzzler (color is
black)
move : the number 1..9 (0 if no number)
options : bit n = “1” if this field allows for
number “n” or contains number “n”
A puzzle without numbers will have all options
1..9 set for each field so options = $3fe

In above picture:
kolom = column;
rij = row;
veld = field;
groep = group Below is a picture of the program at work.
Note:
A Sudoku puzzle is a square of 9*9 fields. nieuw = new;
tonen = show;
These fields may be regarded as 9 rows,
berekenen = calculate;
9 columns or 9 groups of 9 fields each. oplossen = solve;
opties = options
A solved puzzle has numbers 1..9 in
each field of a row, column or group.
So a number appears just once in each
row, column or group.
A puzzle comes with 17 to about
30 numbers already filled in.
The puzzler has to fill in the remaining
open fields using logic reasoning.
Sudoku puzzles come in great variety
of difficulty.
The ones in daily papers are easy 2(**)
to hard 4(****).

In above figure the fields are numbered


1..81 as well.
This has only meaning within the
program. The puzzler does not notice.

10 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


SE
DIRK html

DAVID
mes.

DAVID DIRKSE COMPUTER MATH & GAMES IN PASCAL


h_Ga
rMat
pute
/Com
irkse
vidD
les at u/Da
cal.e
presa epas
.blais
www

HOWARD PAGE-CLARK
A SUDOKU HELPER/SOLVER PAGE 2/4 proce
var
dure
;

begin := 1 to
for i
begin
9 do

end ;
end;

H&
R MAT
PUTE PASCAL
COM
ES IN
The brown numbers are part of the original This approach allows for solving 2(**) GAM

puzzle. (type mtOrg ). The checkbox (right to 4(****) puzzles. The last method is needed
column) is checked to display the options in more frequently in case of the 4(****) puzzles.
empty fields. The above puzzle is very difficult, Now follows an interesting conclusion:
9 stars. The button below the checkbox allows above methods are both simple examples of one
for recalculation of all options. general rule:
This is how recalculation works:
procedure recalcOptions; If K fields (of a row, column or group) together
//recalculate all option fields have exactly K opitons, these options cannot
var rowopts : array[1..9] of word; //word per row occur in the other (9-K) fields.
colopts : array[1..9] of word; //word per column
grpopts : array[1..9] of word; //word per group We solve puzzles by reducing options.
cc,rr,i,grp : byte; //cc:column; rr:row; grp:group
If each field has only one option left,
mask : word; //shifting "0"
begin the puzzle is solved.
for i := 1 to 9 do
begin
colopts[i] := $3fe; //all options EXAMPLES:
rowopts[i] := $3fe; K=1
grpopts[i] := $3fe;
end;
for i := 1 to 81 do //per field
begin
grp := field2Group(i); //group of field i
field2CR(cc,rr,i); //column, row of field i
with game[cc,rr] do Row 1, column 4 has single option 7 so this
if move <> 0 then option may be removed from the other columns.
begin
mask := (1 shl move) xor $3fe; K=8
colopts[cc] := colopts[cc] and mask;
rowopts[rr] := rowopts[rr] and mask;
grpopts[grp] := grpopts[grp] and mask;
end;
end;
for i := 1 to 81 do
begin Column 2 is the only field with option 4 in
grp := field2Group(i); row 4. The other options of this field must be
field2CR(cc,rr,i); removed.
with game[cc,rr] do
In other words: columns 1,3,4,5,6,7,8,9 have
if move = 0
then options := colopts[cc] and rowopts[rr] all options other than 4, so all options other
and grpopts[grp] than 4 outside these fields may be deleted.
else options := 1 shl move; Note: a number in a field counts as a single
end; option.
end;
K=2

Some functions provide conversion of field


number to row, column or group.
Please look at the source code for details.
This situation is more difficult.
STRATEGY
Columns 7,9 of row 2 contain options 5,8 only.
How to solve a Sudoku puzzle?
There is no place for these numbers outside
A start may be like this: choose an empty field
these fields. Other columns than 7,9 cannot
and try numbers 1..9. If only one number is
have options 5,8.
allowed, this number must be entered in the
field.
K=7
Instead of starting with a field we may start
Even more difficult.
looking at a number and try this number at
the empty fields of a row, column or group.
If the number fits only in one field, the
number can be placed there.

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 11


SE
DIRK html

DAVID
mes.

DAVID DIRKSE COMPUTER MATH & GAMES IN PASCAL


h_Ga
rMat
pute
/Com
irkse
vidD
les at u/Da
cal.e
presa epas
.blais
www

HOWARD PAGE-CLARK
A SUDOKU HELPER/SOLVER PAGE 3/4 proce
var
dure
;

begin := 1 to
for i
begin
9 do

end ;
end;

H&
R MAT
PUTE PASCAL
COM
ES IN
The 7 options 1,3,4,5,6,7,9 occupy exactly 7 GAM

columns (not columns 2, 8). In columns 2 and 8


we may scrap all other options than 2,8.
(columns and options being the same is a
coincidence or a joke of the creator)

In a previous article I have described a


combinations counter. These procedures are
applied here to select fields of a row, column or
group for examination.
For K=1 we choose 1 field of 9, for K=2 two
fields of nine......
Note: drietallen = triples; horizontaal =
It is important to know the number of options in horizontal; vertikaal = vertical
a field.
Function PopCount(w : word) returns this count: If certain options are not present in triples B and
the number of “1” bits in word w. C, these numbers must occur in triple A.
function popcount(w : word) : byte; But in that case, they cannot occur in triples D
//count bits set in w and E. (see figure before)
var mask : word; Starting with triple A we call triple pairs B,C
begin and D,E complementary to A and the rule is:
mask := $2; //position bit 1
result := 0;
while mask < $400 do complementary pairs of triples have the same
begin options
if mask and w > 0 then inc(result);
mask := mask shl 1; An option being absent in D,E cannot occur in
end;
end; B,C and vice versa.
Of course this is true as well for vertical triples.
This rule is needed spuriously in 4(****) puzzles
Procedure analyseX(K,X : byte) searches for and some more in the extreme puzzles.
redundant options, but this procedure is too
long to be printed here. Please refer to the
An example:
source code.
K has values 1..4 (and automatically
also 8..5 complementary) . X counts 1..27.
For X=1..9 rows 1..9 are tested,
for X=10..18 columns 1..9 and
for X=19..27 groups 1..9
Tests take place in word array[1..9]
Xtest.
Rows, columns and groups are
loaded in Xtest for examination.

This is the first series of tests for option


reduction. Row 5: triples 2,3 have no option 8. This option
K=1 and K=8 take place sequentially may be removed from triples 1,3 of group 4.
just as K=2 and K=7.
This search is done by functions
A second way of option reduction is realized by checkHtriple(tt,rr: byte) and
examining the interaction of a group with checkVtriple(tt,cc:byte) , horizontally and
rows/columns. vertically. Triple tt runs from 1..3
We need to look a little different at the puzzle: Row rr and column cc count 1..9.
consider triples of numbers.
A row has three horizontal triples. A column
has three vertical triples and a group has both.

12 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


SE
DIRK html

DAVID
mes.

DAVID DIRKSE COMPUTER MATH & GAMES IN PASCAL


h_Ga
rMat
pute
/Com
irkse
vidD
les at u/Da
cal.e
presa epas
.blais
www

HOWARD PAGE-CLARK
A SUDOKU HELPER/SOLVER PAGE 3/4 proce
var
dure
;

begin := 1 to
for i
begin
9 do

end ;
end;

H&
R MAT
PUTE PASCAL
COM
ES IN

Not all puzzles can be solved with these tricks. Boolean flags remember options being GAM

In some cases the interaction between rows, scrapped. Again: these procedures are too long
columns and groups is more complicated. to be printed here. Please see the source code.
I have added following approach: if methods
before fail to reduce options, an option is simply All reduction methods are called by procedure
filled in and the K=1,2... reduction is applied. AnalyseGame.
If the number inserted was wrong, these This procedure switches back to the most simple
reductions may remove all options from a field reduction method after options were scrapped.
somewhere making the puzzle unsolvable. We keep things as simple as possible.
So, this filled in option must be removed. The project also contains a function
For these tests the puzzle options are copied to a SolveBruteForce which solves every
special array[1..9,1..9] of word : Zgame. Sudoku puzzle however difficult. Numbers are
(Z of zero option search). systematically filled in until the solution is
Reason is not to modify the game option fields. encountered. The disadvantage of this method is
obvious: it does not add to our understanding or
Below is an example. logic reasoning at all.
Field 2 of row 1 has option 1 filled in. Following The SOLVE (oplossen) button may be pressed
option reduction resulted in zero options for the repeatedly to search for additional solutions.
field at column 9, row 2. If more then one solution is found, the puzzle is
Option “1” in column 2, row 1 must be deleted. wrong. Good Sudoku puzzles have one unique
solution.

THE DELPHI PROJECT CONTAINS :


unit1: buttons, events, paintbox for the
puzzle, paint procedures, game control
game_unit: procedures and functions for
option reduction
io_unit : opening and saving of puzzle.
File extension is *.sdk .

Drawing is directly in paintbox1


on form1.

The io unit uses a slightly different format


than the puzzle.
Reason is compatibility with older
Sudoku programs.

Postscript
This Sudoku helper/solver has some
extras:
1. display of options facilitate the
solution of difficult puzzels
2. options may be removed by a right
mouseclick
3. analysis shows logic reasoning of
This work is done by function searchZfield : step by step option reduction
boolean which has several helpers:
loadZOptions :
copies options to Zgame[ , ] array
dropZOption : deletes options in row,
column and group of a field after filling in a
number
checkZrows : test rows, calls reduceXZ for
K=1,2 reductions
checkZcolumns : same for columns
checkZgroups : same for rows

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 13


HOW TO SPEEDUP YOUR FIRE MONKEY APP FOR WINDOWS AND MAC OS PAGE 1/4
BY DEN ZUBOW
This is the story about how Fast Report for Fire
monkey became Slow Report and how we made it
“Fast” again.
I really like The Fire monkey framework, there are
Fast Reports
not so many other good Multi platform frameworks Reporting must be Fast!
especially for Pascal. I started to work with the
framework from its birth at an early version of XE2 Windows 7 with Intel i7 4770:
and it's nice to see the improvements it made during this code shows about 14 ms.
the years. The same code on Mac OS with Intel i5
But with each new version, functionality grew and takes about 150 ms!
that forced developers to re-design an old
10 TIMES SLOWER!
architecture. It works for standard cases like form
application with standard controls. What if we would
What to do in this situation?
like to draw something or make a custom draw Developers who are familiar with the latest Delphi
control? The answer is a headache. A terrifying one. versions can say “Use generics,
ThashedStringList or similar class” and they
In this article I want to reveal some pitfalls I faced on
my path from XE2 and up to RAD Studio Berlin. There are right, partially …
are a lot of differences between frameworks and
some interfaces may looks different, but I want to Generics not always suits the needs and if you
focus on performance in general (mostly on Mac OS). moving an old code it will create quite some
trouble.
JUST FORGET WHAT YOU USED BEFORE. ThashedStringList has the same problem
With the release of RAD Studio 2010 we got one of when it's not case sensitive (it calls
the most anticipated feature: Unicode support, but AnsiUpperCase). But there is a way to repair
it also brought some problems for developers who that. In case we know that the list operates only
used old (Delphi 7 ) programming style. with numbers or Symbols of the English alphabet
I know many components which are using we can make new class inherited from
TStringList for an associative array. TstringList, override the CompareStrings
What dangers can be there ? function and use CompareText/CompareStr
FireMonkey uses full Unicode support (because for comparison.
of having the same RTL). The problem shows when
TfrxStringList = class(TStringList)
we start using the find method for string protected
comparison. It calls AnsiCompareStr. That function CompareStrings(const S1, S2: string):
function wraps the OS API and in Windows it works Integer; override;
pretty good, but not on Mac OS. What is so terrible end;
this function does? function TfrxStringList.CompareStrings(const
It calls the Mac API and creates string references for S1, S2: string): Integer;
CFString and calls begin
CFStringCompareWithOptionsAndLocale if CaseSensitive then
function and that’s not the fastest method to do Result := CompareStr(S1, S2)
else
that. When an application has a lot of calls to Result := CompareText(S1, S2);
AnsiCompareStr the whole application slows end;
down.
This simple code demonstrates the problem:

var List: TStringList; i: Integer;


Stopwatch: Tstopwatch; Elapsed: TTimeSpan;
begin
List := TStringList.Create;
List.Sorted := True;
for i := 0 to 4999 do
List.AddObject(IntToStr(i) + 'Item', TObject(i)); // add 5k elements
Stopwatch := TStopwatch.StartNew;
for i := 0 to 4999 do // search middle item 5k times
List.IndexOf('2500Item');
Elapsed := Stopwatch.Elapsed;
ShowMessage('Elapsed msec ' + IntToStr(Elapsed.Milliseconds));
List.Free;
end;

14 Fast Reports Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


Reporting must be Fast!
NEW VERSION OF FASTREPORT 6 PAGE 2/4 Fast Reports
Reporting must be Fast!

Let's run our sample code - but at this time we are I'd like to create collators once in the module
going to create TfrxStringList, so we need to initialization:
change one line:
List := TfrxStringList.Create; var
collatorRef: Pointer;
Same machines results: collatorRefIgnoreCase: Pointer;
Windows 7 – 2ms.
Mac OS – 3 ms!
initialization
UCCreateCollator(nil, 0,
QUITE EASY? kUCCollateCaseInsensitiveMask,
And we can use the old code with the associative @collatorRefIgnoreCase); // ignore case sensetive
TstringList. This trick will work for most UCCreateCollator(nil, 0, 0, @collatorRef); // default
cases when TstringList was used as finalization
associative array(name cache, parameters and so on). UCDisposeCollator(@collatorRefIgnoreCase);
But can we speed up Unicode comparison for Mac UCDisposeCollator(@collatorRef);
OS? There is another function in he Mac API which
works directly with null terminated strings and it
works much faster.

CarbonCore provides low level functions to work


with strings. At this moment we need only one
UCCompareText. It's good that we can import
any function just inside the source code.

We will need a few constants:


const
kUCCollateComposeInsensitiveMask = 1 shl 1;
kUCCollateWidthInsensitiveMask = 1 shl 2;
kUCCollateCaseInsensitiveMask = 1 shl 3;
kUCCollateDiacritInsensitiveMask = 1 shl 4;
kUCCollatePunctuationSignificantMask = 1 shl 15;
kUCCollateDigitsOverrideMask = 1 shl 16;
kUCCollateDigitsAsNumberMask = 1 shl 17;

and link to the library:


const
libUnicodeCore = '/System/Library/Frameworks/
CoreServices.framework/Frameworks/
CarbonCore.framework/CarbonCore';

UCCompareText uses Collator type as flag


here, so we also need
UCCreateCollator/UCDisposeCollator
functions:
function UCCreateCollator(locale: Pointer; opVariant: PUInt32; options: Uint32;
collatorRef: Pointer): OSStatus; cdecl; external libUnicodeCore name '_UCCreateCollator';

function UCDisposeCollator(collatorRef: Pointer): OSStatus; cdecl; external libUnicodeCore name


'_UCDisposeCollator';

function UCCompareText(collatorRef: Pointer; text1Ptr: PWideChar; text1Length: Uint32;


text2Ptr: PWideChar; text2Length: UInt32; equivalent: PBoolean; order: Pinteger):
OSStatus; cdecl; external libUnicodeCore name '_UCCompareText';

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 15


NEW VERSION OF FASTREPORT 6 PAGE 3/4 Fast Reports
Reporting must be Fast!

We are ready to make our own text compare:


function frxCompareStr(const S1, S2: string): Integer;
{$IFDEF MACOS}
var
Res: Boolean;
begin
Result := -1;
Res := False;

try
UCCompareText(collatorRef, PWideChar(s1), Length(s1), PWideChar(s2), Length(s2), @Res, @Result);
if Res then
Result := 0;
finally

end;
end;
{$ELSE}
begin
Result := CompareStr(s1, s2);
end;

and non case sensitive:


function frxCompareText(s1: String; s2: String): Integer;
{$IFDEF MACOS}
var
Res: Boolean;
begin
Result := -1;
Res := False;

try
UCCompareText(collatorRefIgnoreCase, PWideChar(s1), Length(s1), PWideChar(s2), Length(s2), @Res,
@Result);
if Res then
Result := 0;
finally

end;
end;
{$ELSE}
begin
Result := CompareText(s1, s2);
end;
{$ENDIF}

And the last step


– modify our class comparison function:
function TfrxStringList.CompareStrings(const S1, S2: string): Integer;
begin
if CaseSensitive then
Result := frxCompareStr(S1, S2)
else
Result := frxCompareText(S1, S2);
end;

16 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


NEW VERSION OF FASTREPORT 6 PAGE 4/4 Fast Reports
Reporting must be Fast!

The same Mac OS machine presents now 61 ms There is another way. We can cache used fonts in
(150ms with original FMX code) with the same lists and select them when draw something.
example code and Unicode support in comparison For GDI plus canvas it's not hard to do and we
(call to Mac API). Not so bad at all! can use some hacks.
Too bad that FireMonkey framework uses such type
slow string functions even in the core functionality TTextLayoutHack = class(TTextLayout);
like serialization and unfortunately we can't change // create layout once (or you can make a list
that. But at least we can speed up our code a bit. // of layouts with different settings)
Layont := TTextLayoutManager
SUCH A LOT OF WORDS. .TextLayoutByCanvas(Canvas.ClassType)
Another important thing in my opinion is the .Create(Canvas);
text output. If we look at the canvas Layont.BeginUpdate;
Layont.Font := Canvas.Font;
implementation we will see that FillText is
Layont.WordWrap := WordWrap;
deprecated and we need to use TTextLayout. Layont.HorizontalAlign := TextAlign;
Let us consider the situation when we need to Layont.VerticalAlign := VTextAlign;
output a lot of text in different places (from 20 – Layont.RightToLeft := [];
100 text outputs). What does FireMonkey Layont.EndUpdate;
Layont.BeginUpdate; // that's need to force
framework suggest?
// layout from front update
Create TTextLayout set parameters and do the // now we can draw text as many as we want
same for another text output, but here is a // and where we need.
hidden pitfall. When we change a text or other // Draw cycle start
parameter of TextLayout it will force Layout to Layont.Color := Canvas.Fill.Color;
recreate everything include Font. Font creation Layont.Opacity := Opacity;
Layont.TopLeft := Arect.TopLeft; // position of the text
is one of the most performance consuming
Layont.MaxSize := PointF(ARect.Width, Arect.Height);
operation for both Windows and Mac OS. If you // size of the text box
develop something for VCL you may still Layont.Text := Atext; // new text to output
remember how we usually draw some text on TTextLayoutHack(Layont).DoDrawLayout(Canvas);
canvas it still is simple:
Canvas.Font := AssignFontProperties;
Canvas.TextOut();
Canvas.TextOut(); Unfortunately it works only for GDI Plus canvas ,
Canvas.TextOut(); for Mac OS we have to write all the text draw.
Canvas.TextOut(); Thats what we've done with FastReport FMX , the
code is way too large to show it here and partially
We were able to create Font only once! Now it's duplicates Framework output but with fonts
not possible, because all implementations cached in the list. That allows significant increase
hidden inside the exact platform classes. text output for Mac OS.
In the FastReport-Designer we have a simple You can check everything written here with the
Ruler-control and even with this control we new version of FastReport FMX and of course look
have a performance hit. Here how it looks like. at the source codes.

Now just imagine two Ruler-controls in the report These are not all the problems we can face using
designer can make the Report Designer almost the FireMonkey framework , but it is a good start to
unsuitable for Mac OS. understand its weak points and design your
The Framework has only two options: application more carefully.

1. Creates Layout once and assigns draw


properties every time, but this doesn't
speedup. Every draw call will recreate fonts
again. That's slow, especially on Mac OS.
2. Create layout for every text output.
It works with a form when you have 10-20
text objects. But when we need to draw a lot
more - thousands?
It consumes a lot of memory and we will
run out of graphic descriptors very fast.

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 17


BLAISE PASCAL MAGAZINE

3 4
1 2 5
6 10 12
7 8 9 11
13 18 19
14 15 16 17 27
20 25 26
21
28 22 23 24 35 36 37
29 30 31+ 33 34
32
32 40 41 42
38 39 45+ 43
46 47 48 49
44
50 51 53 56
58
52
54+ 57
55

N
S OO
NG
O MI SOON
C NG
MI ON
CO NG SO
I N
COMG SOO
IN
COM SOON
ING
COM SOON
WINDOWS 10

ING
COM SOON
NG
COMI
OON
COMING S
INTRODUCES THE NEW
O N L I N E V I E W
L I B R A R Y 2 0 1 7

http://www.blaisepascal.eu/OnlineView/Overview/OnlineView_Overview.html

18 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


SE
DIRK html

DAVID
mes.

DAVID DIRKSE COMPUTER MATH & GAMES IN PASCAL


h_Ga
rMat
pute
/Com
irkse
vidD
les at u/Da
cal.e
presa epas
.blais
www

COMBINATIONS PAGE 1/2

HOWARD PAGE-CLARK
BY DAVID DIRKSE proce
var
dure
;

begin := 1 to
for i
9 do

begin

end ;

starter
end;

expert H&
R MAT
PUTE PASCAL
COM
ES IN
Delphi 7 including BERLIN GAM

say: N choose k, do not draw a fraction line.


The word “combination” in discrete mathematics
means a choice. In this case a choice of k elements In this project the elements to be chosen are the
is made from a number of N elements. Each bit positions of a word.
element may be chosen once and the sequence of A “1” indicates a choice. A bit position that was
choice is unimportant.
not chosen has value “0”.
An example is where we select 3 out of 12 people to
clean a room or wash dishes.
This article describes a project which systematically For clarity I number elements starting at 1.
selects combinations. The operation (1 shl n) now positions the “1”
The program was needed to analyse sudoku puzzles. bit over element n, which is very convenient.
Let's refresh some theory first. Below is pictured a word with the first possible
combination of 3 out of 12.:
The characters of the word watchdog can be (persons 1,2,3)
written in 8! = 8*7*6*5*4*3*2*1 ways.
Just recall: 8 choices for the first
character, 7 for the next, etc.
Say factorial eight for 8! , which is a
shorthand notation.
The program calculates the next sequential
Now consider the word paraguay. Problem are selection for a certain N and k.
the characters a. First a procedure is needed for reset.
So let's first make these characters different by
procedure resetSH;
applying a color to them. Then there are 8! = //set SH to first combination of K out of N
40320 sequences. The a's have 3! = 6 sequences var i : byte;
that are the same in reality. So, 40320 has 6 mask : word;
times too many sequences. begin
SH := 0;
The characters of the word paraguay therefore
mask := 1;
can be written in 8! / 3! = 6720 sequences. for i := 1 to K do
begin
Back to the original problem: selecting 3 people mask := mask shl 1;
out of 12. We attach a paper to each person with SH := SH or mask;
end;
either the character Y or N. This reduces our end;
problem to: how many sequences can be written
using the characters of the “word”
YYYNNNNNNNNN. SH stands for SHIFTER.
For all different characters the answer is 12! The ResetSH produces a row of k “1” bits starting at
Y characters make 3! sequences which are equal position 1.
in reality and for the N characters this is 9!
sequences. There also is a function called advanceSH :
So, 12! must be divided by 3! as well as 9! The boolean to calculate the next combination of k
answer is 220. From a group of 12 people 3 may elements from N. This process involves shifting
be chosen in 220 different combinations. some “1” bits left.

The mathematical notation is (for a choice of k


from N)

C(N,k) = N! / (k! * (N-k)! ) also written as:

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 19


SE
DIRK html

DAVID
mes.

DAVID DIRKSE COMPUTER MATH & GAMES IN PASCAL


h_Ga
rMat
pute
/Com
irkse
vidD
les at u/Da
cal.e
presa epas
.blais
www

HOWARD PAGE-CLARK
COMBINATIONS PAGE 1/2 proce
var
dure
;

begin := 1 to
for i
begin
9 do

end ;
end;

H&
R MAT
PUTE PASCAL
COM
ES IN
function advanceSH : boolean; Using and we can select bits, xor GAM

//SH shift counter increment complements bits. Two examples of the


//return true if advanced advanceSH procedure:
//use N,K
1. Take the case where N=4 (4 elements)
var count,pos,i : byte;
mask : word; and k=1 ( 1 choice)
Done : boolean; After reset SH = 0001x , binary, (x is bit 0,
begin not used) The new value of SH = 0010x.
result := false;
Pos = 4
count := 0;
pos := N; count = 0
Done := false; mask = 1000x (binary)
repeat test for SH bit = 1, if not test lower bit.
mask := (1 shl pos); If “1 “ bit found:
if SH and mask > 0 then // test SH bit
test for space to shift bit left.
begin
SH := SH xor mask; // remove 1
inc(count); // count removed 1 2. Take N=4, k=2
if pos + count <= N then // test space to shift Say SH = 1001x. (elements 1,4 chosen)
begin Next combination is SH = 0110x.
mask := mask shl 1; // next bit Bit 4 must be removed, bit 1 shifted 1 place left to
Done := true;
position 2, then removed bit is inserted
result := true;
end at position 3.
else
if count = K then Done := true Pos=4
else dec(pos); count=0
end
else dec(pos);
mask=1000x
until Done; SH and mask > 0 , remove bit, increment count
for i := count downto 1 do // add removed bits No room for shift.... pos - 1 = 3
begin loop: mask = 0100x, 0010x,0001x...until “1”
SH := SH xor mask; encountered
mask := mask shl 1;
end; shift 0001x, SH = 0010x
end; Insert removed bit: SH = 0110x.

Below is a picture of the program at work:

The advanceSH function exits “true” if the next


combination is generated in SH. If the last
combination is reached the function exits
“false”. (SH= 111000000000).

We notice following variables:

• SH : the shifter
• mask: only 1 bit set tot select an element.
(mask = 1 shl pos)
• Pos: the position of the mask bit.
• Count: the number of removed “1” bits
in SH, to be inserted later

Logic operations include and , xor


For completeness: this is what they do:

A B A and B A xor B
0 0 0 0
0 1 0 1
1 0 0 1
1 1 1 0

20 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART III: PAGE 1 / 18
FACIAL RECOGNITION IMPROVED
BY BOIAN MITOV
Start a new VCL Form application:
starter expert

In the previous article, I showed you how you can


combine VisionLab and IntelligenceLab to create
application recognizing faces at different locations and
with different rotations inside the image.
The recognition was not great, as we used a
relatively lazy approach of training with non rotated
image, and used the TVLSampler to extract rotated
images for the recognition. This leads to a large
number of false detections, as a rotated face can look
like another face to the classifier. In this article we will
use a more advanced approach.
A number of the IntelligenceLab classifiers
including the Radial Basis Function classifier has
the option to train and recognize based on weighted
features.
To use this feature we need to To create the training we will need 2 images.
provide both features and One will contain the face, and the other will
corresponding weight for each contain the mask to be applied over the
feature. This gives us the option features (Image pixels).
to mask the areas of the image
First we will add the components necessary to
that we are not interested in.
To detect a rotated face, we can process the training image.
train with the image at different
Type “vlgeneric” in the Tool Palette search box,
angles of rotation, and some surrounding area,
masking the area with weight of 0. Here is example of then select TVLGenericFilter from the
such rotated image: palette:

And drop it on the form.


Type “rotate” in the Tool Palette search box,
then select TVLRotate from the palette:

As you can see if we have big enough area to


accommodate the rotated image, we can simply
mask the area outside the image – the black
region. and train with some angle of rotation step
– as example 10 degrees. To further improve the
training we can also train for the background
with completely black image.
Creating such training with the help of the
VideoLab components is not difficult. VideoLab
contains the TVLRotate component that can be
used to perform the rotation task.
As with the previous project before we recognize,
we need to create the training application.
And drop it on the form.

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 21


ARTIFICIAL INTELLIGENCE PART III: PAGE 2 / 18
FACIAL RECOGNITION IMPROVED

We will need to specify a bigger area for the


rotated image so the corners will always fit And drop it on the form:
after the rotation. We will specify a
size of 46x46 pixels.
In the Object Inspector expand
the Size property.
Set the value of the Mode sub-property
of the Size property to asCustom. Set
the values of the Height and Width
sub-properties of the Size property
to 46:

When training and recognizing images, it usually


is better to reduce the size of the image. This will
remove noise from the image, will reduce the size
of the training, and will improve the
performance. The best size can be discovered
experimentally. Here we will resize the image to
14x14 pixels.
Type “resize” in the Tool Palette search box, then
select TVLResize from the palette:

In the Object Inspector set the values of the


Height and Width properties to 14:

Next we will add ImageDisplay to display the


rotated images during the training.
Type “imagedisplay” in the Tool Palette search box,
then select TVLImageDisplay from the palette:

Finally we need a generic filter where we can


receive and process the image in the code.
Type “vlgeneric” in the Tool Palette search box,
then select TVLGenericFilter from the palette:

22 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART III: PAGE 3 / 18
FACIAL RECOGNITION IMPROVED

Next we will add the components to process the


rotation of the mask. Switch back to the Form
Editor by clicking on the “Design” tab.
First we will add image generator to generate the
mask bitmap. Type “imagegen” in the Tool Palette
search box, then select TVLImageGen from the
palette:

And drop it on the form:

Now we can connect the components.


Click on the “OpenWire” tab to switch to the
OpenWire Editor view , and connect the components
as shown on the picture:

Yes right 2 functions with the same result.

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 23


ARTIFICIAL INTELLIGENCE PART III: PAGE 4 / 18
FACIAL RECOGNITION IMPROVED

And drop it on the form.


The TVLImageGen by default is configured to
keep generating video frames with specified
frame rate. It can also be configured to generate
a frame at a time, by calling Pump method from
code. In our case we will generate a single
frame for the recognition, and will use the Pump
method. To configure the component to this
mode we need to set the value of the
ClockSource property in the Object Inspector
to csExternal.
Double-click on the VLImageGen1 component
to open the picture editor dialog:

Click on the OK button of the Picture edito r to close


it, and store the picture in the VLImageGen1
component:

In the picture editor, click on the “Load...” button:

Next we will add a TVLRotate component to


rotate the bitmap.
Type “rotate” in the Tool Palette search box , then
select TVLRotate from the palette:

In the Load Picture dialog , select image containing and drop it on the Form. We will need to expand
the mask, then click on the “Open” button: the size of the generated mask to allow for it to
always fit after the rotation and to match the size
for the rotated training image.
In the Object Inspector expand the Size property.
Set the value of the Mode sub-property of the Size
property to asCustom. Set the values of the Height
and Width sub-properties of the Size property to
46: (Next page top left)

24 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART III: PAGE 5 / 18
FACIAL RECOGNITION IMPROVED

Finally we need a generic filter where we will


receive the rotated and resized image in our
code.
Type “vlgeneric” in the Tool Palette search box,
then select TVLGenericFilter from the palette:

We also need to resize the mask to 14x14 pixels to


match the size of the training image.
Type “resize” in the Tool Palette search box, then
select TVLResize from the palette:

And drop it on the form:

And drop it on the form. In the Object Inspector set


the values of the Height and Width properties
to 14:

Click on the “OpenWire” tab to switch to the


OpenWire Editor view, and connect the
components as shown on the picture:

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 25


ARTIFICIAL INTELLIGENCE PART III: PAGE 6 / 18
FACIAL RECOGNITION IMPROVED

Switch back to the Form Editor by clicking on the We will also add a Label to display the progress
“Design” tab. Finally we need to add the of the training: Type “label” in the Tool Palette
classifier. Type “radial” in the Tool Palette search box, then select TLabel from the palette:
search box, then select
TILRadialBasisFunctionNetwork from the
palette:

And drop it on the form. We need to add a button


to start the training. Type “button” in the Tool
Palette search box , then select TButton from the
palette:

And drop it on the form:

And drop it on the form.


Set the Caption of the Button1 to “Train”:
Now it's time to start working on the code.
First we need to add 2 fields to the Form1 to
hold the training Category and the Mask
Image:
TForm1 = class(TForm)

private
FCategory : Integer;
FMaskImage : IVLImageBuffer;

Next we need to write the event handler


when the button is pressed. Switch back to
the Form Editor by clicking on the “Design” tab.
In the Form Editor , double-click on the
Button1 to generate OnClick event
handler:

26 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART III: PAGE 7 / 18
FACIAL RECOGNITION IMPROVED

First we create a bitmap, and buffer that will store


the image for the training:
ABitmap := TBitmap.Create();
AImageBuffer := TVLImageBuffer.CreateSize( 32, 32 );

Next we send a Start command to the


VLGenericFilter1 specifying the size of the
images that will be processed – 32x32 pixels.
The last parameter in the Start is the period
between frames, and in our case is not important.
Any value other than 0 is fine.
VLGenericFilter1.SendStartCommand( 32,
32, 1000 );
We will iterate trough the training images per face:
In the event handler, add the following code: for J := 1 to 9 do
procedure TForm1.Button1Click(Sender: TObject);
var I, J, AAngle : Integer; And we will iterate trough faces,
ABitmap : TBitmap; and trough the special 0 index that
AImageBuffer : IVLImageBuffer;
will be back background:
begin
ABitmap := TBitmap.Create(); for I := 0 to 40 do
AImageBuffer := We will set the category:
TVLImageBuffer.CreateSize( 32, 32 ); FCategory := I;

VLGenericFilter1.SendStartCommand(
Display the training progress:
32, 32, 1000 );
Label1.Caption :=
for J := 1 to 9 do
J.ToString() + ' ' +
begin
FCategory.ToString();
for I := 0 to 40 do
begin
FCategory := I; If the training is for the
Label1.Caption := J.ToString() + ' ' background we will set the
+ FCategory.ToString(); AImageBuffer to be black,
if( I = 0 ) then and the Category to -1:
begin
AImageBuffer.SetGrayScale( 0 );
FCategory := -1;
end

else
begin
ABitmap.LoadFromFile(
'C:\Program Files (x86)\
Embarcadero\Studio\18.0\
LabPacks\Demos\
AVIFiles\Faces\Train_' + I.ToString() + '_'
+ J.ToString() + '.bmp' );
AImageBuffer.FromBitmap( ABitmap );
end;
AImageBuffer.Format := vfGrayScale;
for AAngle := 0 to 36 do
begin
VLRotate1.Angle := AAngle * 10;
VLRotate2.Angle := AAngle * 10;
VLImageGen1.Pump();
VLGenericFilter1.SendData( AImageBuffer );
Application.ProcessMessages();
end;
end;
end;
ABitmap.Free();
VLGenericFilter1.SendStopCommand();
ILRadialBasisFunctionNetwork1.SaveToFile( 'C:\Files\
MaskedRotatedFaceTraining14x14_BKG.ctd' );
end;

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 27


ARTIFICIAL INTELLIGENCE PART III: PAGE 8 / 18
FACIAL RECOGNITION IMPROVED
if( I = 0 ) then
begin
AImageBuffer.SetGrayScale( 0 );
FCategory := -1;
end

Otherwise we will load the image from the


bitmap file:
else
begin
ABitmap.LoadFromFile( 'C:\Program Files
(x86)\Embarcadero\Studio\18.0\
LabPacks\Demos\AVIFiles\Faces\Train_'
+ I.ToString() + '_' + J.ToString() + '.bmp' );
AImageBuffer.FromBitmap( ABitmap );
end; In the event handler, add the following code:
procedure TForm1.VLGenericFilter3ProcessData(
Next we will set the image format to be 8 bit gray- Sender: Tobject; InBuffer: IVLImageBuffer;
scale: AImageBuffer.Format := vfGrayScale; var OutBuffer: IVLImageBuffer;
var SendOutputData: Boolean);
begin
We will iterate trough the angles with a step of 10 InBuffer.Format := vfGrayScale;
degrees, and assign the angles to the VLRotate1 FMaskImage := TVLImageBuffer.CreateCopy( InBuffer );
and VLRotate2: end;
for AAngle := 0 to 36 do
begin
VLRotate1.Angle := AAngle * 10; Here we just set the Format of the InBuffer to
VLRotate2.Angle := AAngle * 10; vfGrayScale and assign it to the FMaskImage
We will force the VLImageGen1 to generate a mask field by creating copy of the object. The
image: VLImageGen1.Pump(); VideoLab buffers perform “shallow copy” of the
data, so the creation of the new object is a very
And send the training image buffer to fast operation and uses very little memory.
VLGenericFilter1 for processing: Finally we need to train with the rotated image
VLGenericFilter1.SendData( AImageBuffer ); and mask. The processed image will arrive in the
InBuffer parameter of the
Finally we will call VLGenericFilter1.OnProcessData
Application.ProcessMessages to allow the GUI event handler.
to be refreshed: Switch back to the Form Editor by clicking on the
Application.ProcessMessages(); “Design” tab. In the Form Editor, double-click on
end; the VLGenericFilter3 to generate
end;
OnProcessData event handler:
After the processing is done, we will free the
bitmap, and stop the VLGenericFilter1:
ABitmap.Free();
VLGenericFilter1.SendStopCommand();

Finally we will save the generated training to a file:


ILRadialBasisFunctionNetwork1.SaveToFile(
'C:\Files\MaskedRotatedFaceTraining14x14_BKG.ctd' );

Next we need to save the processed mask to the


FMaskImage field of Form1.
The processed mask will arrive in the InBuffer
parameter of the
VLGenericFilter3.OnProcessData event handler.
Switch back to the Form Editor by clicking on the
“Design” tab. In the Form Editor, double-click on the
VLGenericFilter3 to generate OnProcessData
event handler:

28 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART III: PAGE 9 / 18
FACIAL RECOGNITION IMPROVED
procedure TForm1.VLGenericFilter2ProcessData(Sender: TObject; After a while all images will be
InBuffer: IVLImageBuffer; var OutBuffer: IVLImageBuffer;
var SendOutputData: Boolean); processed and the display will
var stop updating showing only the
AFeaturesBuffer : ISLBlockBuffer; last image:
AMaskBuffer : ISLBlockBuffer;

begin
InBuffer.Format := vfGrayScale;
AFeaturesBuffer := TSLBlockBuffer.CreateData( InBuffer.Read(),
InBuffer.ByteSize );
AMaskBuffer := TSLBlockBuffer.CreateData( FMaskImage.Read(),
FMaskImage.ByteSize );
ILRadialBasisFunctionNetwork1.Train( AFeaturesBuffer,
AMaskBuffer, FCategory );
end;

Here we set the Format of the InBuffer to At this point you will have the training saved in
vfGrayScale, then copy the data from InBuffer the “MaskedRotatedFaceTraining14x14_BKG.ctd” file
and FMaskImage to ISLBlockBuffer buffers: in the “C:\Files” directory.
AFeaturesBuffer := TSLBlockBuffer.CreateData( Here is the complete code for the training project:
InBuffer.Read(), InBuffer.ByteSize ); unit Unit1;
AMaskBuffer := TSLBlockBuffer.CreateData( interface
FMaskImage.Read(), FMaskImage.ByteSize );
uses
Winapi.Windows,Winapi.Messages, System.SysUtils,
and train the classifier with the 2 buffers and the System.Variants, System.Classes, Vcl.Graphics,
Fcategory: ILRadialBasisFunctionNetwork1.Train( Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VLResize,
AFeaturesBuffer, AMaskBuffer, FCategory ); Mitov.VCLTypes, VCL.LPControl,
SLControlCollection, VLCommonDisplay,
VLImageDisplay, VLCommonFilter,
In our previous applications we use Real Buffers to VLRotate, Mitov.Types, LPComponent,
train and recognize. The IntelligenceLab classifier SLCommonFilter, VLBasicGenericFilter,
can also work with Block Buffers containing bytes, VLGenericFilter, Vcl.StdCtrls, ILBasicClassifier,
and automatically will normalize the buffers. ILRadialBasisFunctionNetwork, SLCommonGen,
The training in this case is smaller, and the
VLCommonGen, VLImageGen;
processing is faster.
type
TForm1 = class(TForm)
VLGenericFilter1: TVLGenericFilter;
The project is ready and we can run it. VLRotate1: TVLRotate;
Before you run it, make sure to create the VLImageDisplay1: TVLImageDisplay;
“C:\Files” directory where the training will be VLResize1: TVLResize;
saved. VLGenericFilter2: TVLGenericFilter;
VLImageGen1: TVLImageGen;
VLRotate2: TVLRotate;
If you click on the Train button of the running VLResize2: TVLResize;
project, you will see the rotated training images VLGenericFilter3: TVLGenericFilter;
displayed, and the Label will show you the indexes ILRadialBasisFunctionNetwork1:
TILRadialBasisFunctionNetwork;
of the bitmap file that is being processed:
Button1: TButton;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
procedure VLGenericFilter2ProcessData(
Sender: Tobject; InBuffer: IVLImageBuffer;
var OutBuffer: IVLImageBuffer;
var SendOutputData: Boolean);
procedure VLGenericFilter3ProcessData(
Sender: Tobject; InBuffer: IVLImageBuffer;
var OutBuffer: IVLImageBuffer;
var SendOutputData: Boolean);
private
FCategory : Integer;
FMaskImage : IVLImageBuffer;
public
{ Public declarations }
end;
var
Form1: Tform1; see further next page

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 29


ARTIFICIAL INTELLIGENCE PART III: PAGE 10 / 18
FACIAL RECOGNITION IMPROVED

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);


var I, J : Integer; ABitmap : Tbitmap;
AImageBuffer : IVLImageBuffer; AAngle : Integer;
begin
ABitmap := TBitmap.Create();
AImageBuffer := TVLImageBuffer.CreateSize( 32, 32 );
VLGenericFilter1.SendStartCommand( 32, 32, 1000 );

for J := 1 to 9 do begin // (5)


for I := 0 to 40 do
begin // (3)
FCategory := I;
Label1.Caption := J.ToString() + ' ' + FCategory.ToString();
if( I = 0 ) then
begin // (1)
AImageBuffer.SetGrayScale( 0 );
FCategory := -1;
end // (1)
else
begin // (2)
ABitmap.LoadFromFile( 'C:\Program Files
(x86)\Embarcadero\Studio\18.0\LabPacks\Demos\
AVIFiles\Faces\Train_' + I.ToString() + '_' + J.ToString() + '.bmp' );
AImageBuffer.FromBitmap( ABitmap );
end; // (2)
AImageBuffer.Format := vfGrayScale;
for AAngle := 0 to 36 do
begin // (4)
VLRotate1.Angle := AAngle * 10;
VLRotate2.Angle := AAngle * 10;
VLImageGen1.Pump();
VLGenericFilter1.SendData( AImageBuffer );
Application.ProcessMessages();
end; // (4)
end; // (3)
end; // (5)
ABitmap.Free();
VLGenericFilter1.SendStopCommand();
ILRadialBasisFunctionNetwork1.SaveToFile(
'C:\Files\MaskedRotatedFaceTraining14x14_BKG.ctd' );
end;

procedure TForm1.VLGenericFilter2ProcessData(Sender: TObject;


InBuffer: IVLImageBuffer; var OutBuffer: IVLImageBuffer;
var SendOutputData: Boolean);
var AFeaturesBuffer : ISLBlockBuffer;
AMaskBuffer : ISLBlockBuffer;
begin
InBuffer.Format := vfGrayScale;
AFeaturesBuffer := TSLBlockBuffer.CreateData( InBuffer.Read(), InBuffer.ByteSize );
AMaskBuffer := TSLBlockBuffer.CreateData( FMaskImage.Read(), FMaskImage.ByteSize );
ILRadialBasisFunctionNetwork1.Train( AFeaturesBuffer, AMaskBuffer, FCategory );
end;

procedure TForm1.VLGenericFilter3ProcessData(Sender: TObject;


InBuffer: IVLImageBuffer; var OutBuffer: IVLImageBuffer;
var SendOutputData: Boolean);
begin
InBuffer.Format := vfGrayScale;
FMaskImage := TVLImageBuffer.CreateCopy( InBuffer );
end;

end.

30 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART III: PAGE 11 / 18
FACIAL RECOGNITION IMPROVED

Now that we have the training ready, we can start


working on our recognition application.
Start a new VCL Form application:

Click on the OK button of the Picture editor to


close it, and store the picture in the
VLImageGen1 component:

We again will use the TVLImageGen component


to generate the video frame for the recognition.
Type “imagegen” in the Tool Palette search box,
then select TVLImageGen from the palette:

Set the value of the ClockSource property in


In the picture editor, click on the “Load...” button: the object inspector to csExternal:

We want the buffers we process to be in 8 bit


gray-scale format. We can use the
TVLChangeFormat component for this.
Type “change” in the Tool Palette search box,
then select TVLChangeFormat from the
palette:
In the Load Picture dialog , select image containing
faces to be recognized, then click on the “Open”
button:

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 31


ARTIFICIAL INTELLIGENCE PART III: PAGE 12 / 18
FACIAL RECOGNITION IMPROVED

In the Object Inspector set the value of the Align


property to alClient:

And drop it on the form. In the Object Inspector set


the value of the Format property to vlGrayScale:
Type “vlgeneric” in the Tool Palette search box,
then select TVLGenericFilter from the palette:

Click on the “OpenWire” tab to switch to the


OpenWire Editor view, and connect the components
as shown on the picture:
And drop it on the form:

Switch back to the Form Editor by clicking on the


“Design”tab. Type “vlgeneric” in the Tool Palette
search box, then select TVLGenericFilter
from the palette:
Next we will add ImageDisplay to display the
recognition. Type “imagedisplay” in the Tool
Palette search box, then select
TVLImageDisplay from the palette:

And drop it on the form.


Type “sampler” in the Tool Palette search box,
then select TVLSampler from the palette:
And drop it on the form:

And drop
it on the form.

32
ARTIFICIAL INTELLIGENCE PART III: PAGE 13 / 18
FACIAL RECOGNITION IMPROVED

To disable the rotation in the VLSampler1,


expand the Rotation property, and set the
value of its Max sub-property to 0:

And drop it on the form. In the Object Inspector set


the values of the Height and Width properties to 14:

Finally we need a generic filter to receive the


result. Type “vlgeneric” in the Tool Palette search
box, then select TVLGenericFilter from the
palette:

We also need to specify the size of the sample.


In the Object Inspector expand the Sample
property, and set the values of the Height and
Width sub-properties to 46 to match the size of
the face training images:
And drop it on the form:

Click on the “OpenWire” tab to switch to the


OpenWire Editor view, and connect the
components as shown on the picture:

Once the 46x46 sample has been extracted, we


need to resize it to 14x14 to match the resized
training. Type “resize” in the Tool Palette search
box, then select TVLResize from the palette:

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 33


ARTIFICIAL INTELLIGENCE PART III: PAGE 14 / 18
FACIAL RECOGNITION IMPROVED

Finally we need to ad the classifier. Switch back TDetection = record


public
to the Form Editor by clicking on the “Design” tab.
Index : Integer;
Type “radial” in the Tool Palette search box, then Position : TPoint;
select TILRadialBasisFunctionNetwork from Distance : Real;
the palette:
public
constructor Create( AIndex : Integer;
APosition : TPoint; ADistance : Real );
end;

The constructor implementation will simply


initialize the fields:
constructor TDetection.Create( AIndex :
And drop it on the form: Integer; APosition : TPoint; ADistance : Real );
begin
Index := AIndex;
Position := APosition;
Distance := ADistance;
end;

We will store the records in an array list. For this


we will need to add the Mitov.Containers.List
unit to the uses clause:
uses

Mitov.Containers.List;

We can load the Next we need to add the list of TDetection


ILRadialBasisFunctionNetwork1 training by records, and couple of other fields to the TForm1
code, but we can also load it at design time, and to store the detection locations, and the temporary
store it as part of the executable. position of the detection performed at each step:
To store the training in the executable, double TForm1 = class(TForm)
click on the ILRadialBasisFunctionNetwork1 …
to open the “File Open Dialog ” dialog. private
FDetectedPosition : TPoint;
In the “File Open Dialog” navigate to the folder FDetections : IArrayList<TDetection>;
where the training file was saved, select the file, …
and click on the “Open” button:
Switch back to the Form Editor by clicking on
the “Design” tab. In the object inspector click
on the “Events” tab.
Select the OnCreate event, and double click
on the value edit to generate event handler:

All the necessary components are now added and


configured, and we can start writing the code.
First, we will declare a record that will be used to
store the detection location and information so we
can paint it over the image:

34 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART III: PAGE 15 / 18
FACIAL RECOGNITION IMPROVED

In the event handler, add the procedure


TForm1.VLGenericFilter1ProcessData(Sender: TObject;
following code:
InBuffer: IVLImageBuffer; var OutBuffer:
procedure TForm1.FormCreate(Sender: TObject); IVLImageBuffer; var SendOutputData: Boolean);
begin var
FDetections := TArrayList<TDetection>.Create(); ABuffer : Tbitmap; AGraphics : IGPGraphics;
VLImageGen1.Pump(); AFont : IGPFont;
end; begin
VLGenericFilter2.SendData( InBuffer );
This will create array list of TDetection records,
and then will force the VLImageGen1 to generate ABuffer := TBitmap.Create();
one frame by calling the Pump() method. InBuffer.ToBitmap( ABuffer );
Switch to the Form Editor by clicking on the AGraphics := TIGPGraphics.Create( ABuffer.Canvas );
“Design” tab. AFont := TIGPFont.Create( 'Microsoft Sans Serif',
Select the VLGenericFilter1 component. 10, [ fsBold ] );
FDetections.Query().ForEach( // 1
procedure( ADetection : Tdetection )
begin
AGraphics.DrawRectangle( TIGPPen.Create( aclRed ),
ADetection.Position.X - 23, ADetection.Position.Y - 23,
46, 46 );
AGraphics.DrawString( ADetection.Index.ToString(),
AFont, ADetection.Position,
TIGPSolidBrush.Create( aclRed ));
end ); // 1

AGraphics := NIL;

OutBuffer.FromBitmap( ABuffer );
ABuffer.Free();
end;
Select the OnStart event, and double click on the
value edit to generate event handler:
procedure TForm1.VLGenericFilter1Start(Sender: TObject; var AWidth,
AHeight: Integer; AFrameDelay: Real);
begin
VLGenericFilter3.SendStartCommand( AWidth, AHeight, Round( AFrameDelay ));
end;

Here we will simply start the VLGenericFilter3 First we will send the arriving InBuffer to
with the same parameters we get in the event. VLGenericFilter2 component by calling
Switch to the Form Editor by clicking on the SendData:
“Design” tab.
Select the OnProcessData event, and double
VLGenericFilter2.SendData( InBuffer );
click on the value edit to generate event handler:
To paint on the buffer we will convert it to
temporary bitmap:
ABuffer := TBitmap.Create();
InBuffer.ToBitmap( ABuffer );

And we will construct IGDI+ graphics object


for the Bitmap, and font:
AGraphics := TIGPGraphics.Create(
ABuffer.Canvas );
AFont := TIGPFont.Create( 'Microsoft
Sans Serif', 10, [ fsBold ] );
In the event handler, add the following code:
Next we will iterate through the FDetections
records:
FDetections.Query().ForEach(
procedure( ADetection : TDetection )
begin

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 35


ARTIFICIAL INTELLIGENCE PART III: PAGE 16 / 18
FACIAL RECOGNITION IMPROVED

And for each detection will draw rectangle and The buffers that arrive from the VLSampler1
show detection information: component connected to the
AGraphics.DrawRectangle( TIGPPen.Create( aclRed ), VLGenericFilter3 contain additional
ADetection.Position.X - 23, ADetection.Position.Y - information about the location where they are
23, 46, 46 );
AGraphics.DrawString( Detection.Index.ToString(),
extracted from. We will access this information
AFont, ADetection.Position, and store it in the FDetectedPosition field:
TIGPSolidBrush.Create( aclRed )); ALocation := InBuffer.GetCustom( 0 ) as
IVLSamplerLocationData;
Finally we will release the AGraphics, convert FDetectedPosition := ALocation.Position;
the bitmap to the output buffer, and release the
bitmap: Next we will assign the pixels from the image to
AGraphics := NIL; an ISLBlockBuffer and will send the buffer
to ILRadialBasisFunctionNetwork1 for
OutBuffer.FromBitmap( ABuffer ); recognition:
ABuffer.Free();
AFeaturesBuffer := TSLBlockBuffer.CreateData(
To use the IGDI+, we will need to add the InBuffer.Read(), InBuffer.ByteSize );
following units to the uses clause: LRadialBasisFunctionNetwork1.Predict(
AFeaturesBuffer );
uses IGDIPlus, VCL.IGDIPlusExt;

Next we need to write code to process and send Finally we will call the Predict method of the
for recognition each extracted sample. ILRadialBasisFunctionNetwork1 to try to
Switch to the Form Editor by clicking on the recognize the image.
“Design” tab. The only remaining task is to get the results of
Select the VLGenericFilter3 component. the recognition from the classifier.
Select the OnProcessData event, and double click Switch to the Form Editor by clicking on the
on the value edit to generate event handler: “Design” tab.
Select the ILRadialBasisFunctionNetwork1
component.
Double click on the value of the OnResult
event to generate event handler:

In the event handler, add the following code:


procedure TForm1.VLGenericFilter3ProcessData(Sender: TObject; InBuffer: IVLImageBuffer; var
OutBuffer: IVLImageBuffer;
var SendOutputData: Boolean);
var
AFeaturesBuffer : ISLBlockBuffer;
ALocation : IVLSamplerLocationData;

begin
ALocation := InBuffer.GetCustom( 0 ) as IVLSamplerLocationData;
FDetectedPosition := ALocation.Position;

InBuffer.Format := vfGrayScale;

AFeaturesBuffer := TSLBlockBuffer.CreateData( InBuffer.Read(), InBuffer.ByteSize );


ILRadialBasisFunctionNetwork1.Predict( AFeaturesBuffer );
end;

36 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


ARTIFICIAL INTELLIGENCE PART III: PAGE 17 / 18
FACIAL RECOGNITION IMPROVED
In the event handler, add the following code: unit Unit1;
procedure
TForm1.ILRadialBasisFunctionNetwork1Result(ASender: interface
TObject;
uses
AFeatures: ISLRealBuffer; AResult:
Winapi.Windows, Winapi.Messages, System.SysUtils,
TILRadialBasisFunctionResult);
System.Variants, System.Classes, Vcl.Graphics,
var
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Mitov.Types,
ACategory : Integer;
SLCommonGen, VLCommonGen, VLImageGen,
begin Mitov.VCLTypes, VCL.LPControl, SLControlCollection,
if( AResult.Count = 0 ) then VLCommonDisplay, VLImageDisplay,
Exit; VLBasicGenericFilter, VLGenericFilter,
LPComponent, SLCommonFilter, VLCommonFilter,
ACategory := Round( AResult[ 0 ].Neuron.Category ); VLChangeFormat, ILBasicClassifier,
ILRadialBasisFunctionNetwork, VLResize,
if( ACategory = -1 ) then VLSinkFilter, VLSampler, Mitov.Containers.List;
Exit;
type
FDetections.Add( TDetection.Create( ACategory, TDetection = record
FDetectedPosition, AResult[ 0 ].Distance )); public
end; Index : Integer;
Position : TPoint;
Distance : Real;
In the code, first we check if there is successful
public
detection: if( AResult.Count = 0 ) then Exit; constructor Create( AIndex : Integer; APosition :
TPoint; ADistance : Real );
Next we extract the detection category: end;
//-----------------------------------------------------------------------
ACategory := Round( AResult[ 0 ].Neuron.Category ); TForm1 = class(TForm)
Check if the Category is background (-1): VLImageGen1: TVLImageGen;
if( ACategory = -1 ) then Exit; VLChangeFormat1: TVLChangeFormat;
VLGenericFilter1: TVLGenericFilter;
Finally in case of successful recognition we will VLImageDisplay1: TVLImageDisplay;
create and add TDetection record to the VLGenericFilter2: TVLGenericFilter;
Fdetections: VLSampler1: TVLSampler;
FDetections.Add( TDetection.Create( ACategory, VLResize1: TVLResize;
FDetectedPosition, AResult[ 0 ].Distance )); VLGenericFilter3: TVLGenericFilter;
ILRadialBasisFunctionNetwork1:
TILRadialBasisFunctionNetwork;
The application is ready. If we run it, after a procedure FormCreate(Sender: TObject);
while we will see the result: procedure VLGenericFilter1ProcessData(Sender:
As you can see, this project executes much faster TObject;
than the previous one, and gives quite good InBuffer: IVLImageBuffer; var OutBuffer:
results. On the right column and te page after IVLImageBuffer;
that, the complete code for the recognition var SendOutputData: Boolean);
procedure VLGenericFilter1Start(Sender: TObject;
project is shown: var AWidth,
AHeight: Integer; AFrameDelay: Real);
procedure VLGenericFilter3ProcessData(Sender:
TObject;
InBuffer: IVLImageBuffer; var OutBuffer:
IVLImageBuffer;
var SendOutputData: Boolean);
procedure
ILRadialBasisFunctionNetwork1Result(ASender:
TObject;
AFeatures: ISLRealBuffer; AResult:
TILRadialBasisFunctionResult);
private
FDetectedPosition : TPoint;
FDetections : IArrayList<TDetection>;

public
{ Public declarations }
end;

var
Form1: TForm1;

Issue Nr 10 2016 BLAISE PASCAL MAGAZINE 37


ARTIFICIAL INTELLIGENCE PART III: PAGE 18 / 18
FACIAL RECOGNITION IMPROVED
implementation
{$R *.dfm}
uses IGDIPlus, VCL.IGDIPlusExt;
procedure TForm1.FormCreate(Sender: TObject);
begin
FDetections := TArrayList<TDetection>.Create();
VLImageGen1.Pump();
end;
//------------------------------------------------------------------------------
procedure TForm1.ILRadialBasisFunctionNetwork1Result(ASender: TObject;
AFeatures: ISLRealBuffer; AResult: TILRadialBasisFunctionResult);
var ACategory : Integer;
begin
if( AResult.Count = 0 ) then Exit;
ACategory := Round( AResult[ 0 ].Neuron.Category );
if( ACategory = -1 ) then Exit;
FDetections.Add( TDetection.Create( ACategory, FDetectedPosition,
AResult[ 0 ].Distance ));
end;
//------------------------------------------------------------------------------
procedure TForm1.VLGenericFilter1ProcessData(Sender: TObject;
InBuffer: IVLImageBuffer; var OutBuffer: IVLImageBuffer; var SendOutputData: Boolean);
var ABuffer : Tbitmap; AGraphics : IGPGraphics; AFont : IGPFont;
begin
VLGenericFilter2.SendData( InBuffer ); ABuffer := TBitmap.Create();
InBuffer.ToBitmap( ABuffer ); AGraphics := TIGPGraphics.Create( ABuffer.Canvas );
AFont := TIGPFont.Create( 'Microsoft Sans Serif', 10, [ fsBold ] );
FDetections.Query().ForEach( // (1)
procedure( ADetection : Tdetection )
begin AGraphics.DrawRectangle( TIGPPen.Create( aclRed ),
ADetection.Position.X - 23, ADetection.Position.Y - 23, 46, 46 );
AGraphics.DrawString( ADetection.Index.ToString(), Afont,
ADetection.Position, TIGPSolidBrush.Create( aclRed ));
end ); // (1)
AGraphics := NIL;
OutBuffer.FromBitmap( ABuffer );
ABuffer.Free();
end;
//------------------------------------------------------------------------------
procedure TForm1.VLGenericFilter1Start(Sender: TObject; var Awidth, AHeight: Integer; AFrameDelay: Real);
begin
VLGenericFilter3.SendStartCommand( AWidth, AHeight, Round( AFrameDelay ));
end;
//------------------------------------------------------------------------------
procedure TForm1.VLGenericFilter3ProcessData(Sender: TObject;
InBuffer: IVLImageBuffer; var OutBuffer: IVLImageBuffer; var SendOutputData: Boolean);
var
AFeaturesBuffer : ISLBlockBuffer;
ALocation : IVLSamplerLocationData;
begin
ALocation := InBuffer.GetCustom( 0 ) as IVLSamplerLocationData;
FDetectedPosition := ALocation.Position;
InBuffer.Format := vfGrayScale;
AFeaturesBuffer := TSLBlockBuffer.CreateData( InBuffer.Read(), InBuffer.ByteSize );
ILRadialBasisFunctionNetwork1.Predict( AFeaturesBuffer );
end;
//------------------------------------------------------------------------------
constructor TDetection.Create( AIndex : Integer; APosition : TPoint; ADistance : Real );
begin
Index := Aindex;
Position := Aposition;
Distance := Adistance;
end;
//------------------------------------------------------------------------------
end.

In this article I showed you how to improve the recognition of rotated faces in a large image, by
using rotated training with weighted(masked) samples. We also reduced the false recognitions by
training for the background with black image. In all the examples until now we have used only the
image pixels as features. They are good features to use, but if we use higher level features, such as
already detected shapes, or regions in the image, we can improve the recognition. The processing
is also still relatively slow. In the following articles, I will show you how you can improve the
recognition results by using higher level features, and how you can improve the performance by
executing the recognition in parallel over multiple cores or in a GP GPU.

38 Issue Nr 10 2016 BLAISE PASCAL MAGAZINE


H A PP Y N EW
COMPONENTS
DEVELOPERS 4
YE
AR
20
17
FR
O
M
CO
M
P
O
N
EN
TS
4
DE
VE
LO
PER
S

- Now multimonitor remote desktop V5 (VCL and FMX)


- Rad studio and Delphi 10.1 BERLIN support
- High performance LZ4 and Jpeg compression
- Improved NextGen support including IOS 64 bit

- Native high performance 100% developer defined app kbmMemTable is the fastest and most feature rich in
server with support for loadbalancing and failover memory table for Embarcadero products.
- Native high performance JSON and XML (DOM and - Easily supports large datasets with millions of records
SAX) for easy integration with external systems - Easy data streaming support
- Optional to use native SQL engine
- Native support for RTTI assisted object marshalling to
- Supports nested transactions and undo
and from XML/JSON, now also with new fullfeatured XML - Native and fast build in M/D, aggregation /grouping,
schema (XSD) import range selection features
- High speed, unified database access (35+ supported - Advanced indexing features for extreme performance
database APIs) with connection pooling, metadata and
Whats new in v. 4.94.00
data caching on all tiers
- New improved XSD converter.
- Multi head access to the application server, via AJAX, - New automatic password generator algorithms
native binary, Publish/Subscribe, SOAP, XML, RTMP from (currently Mixer and Koremutake supported)
web browsers, embedded devices, linked application - Object marshalling support with custom enum
servers, PCs, mobile devices, Java systems and many definitions
more clients - New Logformatter framework for custom log formats
- Full FastCGI hosting support. Host PHP/Ruby/Perl/ - New REST CORS support
Python applications in kbmMW! - New random generator framework currently
supporting SplitMix (64 bit), Xoroshiro128+/
- AMQP support ( Advanced Message Queuing
1024 (64 bit), PCG (32 bit), Mersenne Twister
Protocol) (32 bit/64 bit) random generators.
- Added AMQP 0.91 client side gateway support and - Further improved scheduler framework, now also
sample. with runonce async functions.
- Fully end 2 end secure brandable Remote Desktop with - Much improved HTTP/REST header support
near REALTIME HD video, 8 monitor support, texture - Support for automatic log file backup and many
other log improvements
detection, compression and clipboard sharing.
- Support for async request with anonymous function
- Supports Delphi/C++ Builder/RAD Studio 2009 to 10.1 callback (and more) in WIB.
Berlin (32 bit/64 bit, Android, IOS 32/64 and OSX where - Improved advanced dataset resolver features
applicable)" - Improved XML SAX/DOM parser/generator

COMPONENTS
DEVELOPERS 4
EESB, SOA,MoM, EAI TOOLS FOR INTELLIGENT SOLUTIONS. kbmMW IS THE PREMIERE N-TIER PRODUCT FOR DELPHI /
C++BUILDER BDS DEVELOPMENT FRAMEWORK FOR WIN 32 / 64, .NET AND LINUX WITH CLIENTS RESIDING ON WIN32 / 64, .NET, LINUX, UNIX
MAINFRAMES, MINIS, EMBEDDED DEVICES, SMART PHONES AND TABLETS.

Você também pode gostar