Log in

View Full Version : [Delphi 7] Manipulating image from multiple threads for extra speed up


Atak_Snajpera
16th January 2013, 14:53
I've written small app to calculate average luma value for specific image. Speed is not bad thanks to Graphics32 (http://graphics32.org/wiki/) library. My Q6600@3Ghz can process 16MPix image in about 20s (with standard not thread safe image component (Synchronize needed) I had to wait 360s! However I would like to speed up everything by using multiple threads. Each thread will process specific area of the picture so on my quad core i could expect 4 times faster processing. Unfortunately even component from Graphics32 does not allow multiple threads. As soon as I activate second thread all color values report just white (255). I even tried working on separate image components and nothing. With one thread everything is ok. So here is my question. What to do to allow multithreaded processing on image? Transfer all data to array?

sample of my code


procedure TForm1.JvWorkerThread1Execute(Sender: TObject; Params: Pointer);
var Luma,AverageLumainRow,Sum,SumOfAverageLumas:double;
R,G,B:Byte;
Color:TColor;
x,y:integer;
begin

Average_color_THREAD1 := 0;
SumOfAverageLumas := 0;
AverageLumainRow := 0;

for y := 0 to 1000 - 1 do
begin

Sum := 0;

for x := 0 to 1000 - 1 do
begin

Color := form1.Image321.Bitmap.Canvas.Pixels[x,y];

R := GetRValue( Color );
G := GetGValue( Color );
B := GetBValue( Color );

Luma := 0.2126 * R + 0.7152 * G + 0.0722 * B; //convert to grey scale using Rec. 709 formula

Sum := Sum + Luma; //sum all luma values in row

end;

AverageLumainRow := Sum / Image_width;
SumOfAverageLumas := SumOfAverageLumas + AverageLumainRow;

end;

Average_color_THREAD1 := SumOfAverageLumas / 1000;

end;

procedure TForm1.JvWorkerThread2Execute(Sender: TObject; Params: Pointer);
var Luma,AverageLumainRow,Sum,SumOfAverageLumas:double;
R,G,B:Byte;
Color:TColor;
x,y:integer;
begin

Average_color_THREAD2 := 0;
SumOfAverageLumas := 0;
AverageLumainRow := 0;

for y := 1000 to 2000 - 1 do
begin

Sum := 0;

for x := 0 to 1000 - 1 do
begin

Color := form1.Image321.Bitmap.Canvas.Pixels[x,y];

R := GetRValue( Color );
G := GetGValue( Color );
B := GetBValue( Color );

Luma := 0.2126 * R + 0.7152 * G + 0.0722 * B; //convert to grey scale using Rec. 709 formula

Sum := Sum + Luma; //sum all luma values in row

end;

AverageLumainRow := Sum / Image_width;
SumOfAverageLumas := SumOfAverageLumas + AverageLumainRow;

end;

Average_color_THREAD2 := SumOfAverageLumas / 1000;

end;

LoRd_MuldeR
16th January 2013, 16:20
I guess the problem lies here:
Color := form1.Image321.Bitmap.Canvas.Pixels[x,y]

You are not allowed to access any GUI controls from a thread, except from the applications's main thread.
Thus the Synchronize function allows you to execute a function in the context of the main thread...

How to solve this? Well, if you put all image data into a separate array first (from Main thread!) and then make sure the threads operate on non-overlapping parts of the array only, you should be fine. But instead of copying all the image data into an array, you may consider not abusing a GUI controls as data storage. Instead keep your image data stored in an array right away. You can still copy the (final!) result into a GUI widget, in case you want to show it to the user...

BTW: Instead of having a separate function for each thread, make the thread function parameterized! For example, add a parameter "index of first line to process" and a parameter "number of lines to process". Then you can use one single function for as many threads as you like. You just have to set the parameters for each thread right.

BTW2: Parallelized sum can be done more efficient using a "pyramid" style, instead of creating the sum by walking through the line value-by-value (given you have enough threads).

BTW3: How do you combine the results of your threads? Be sure, you synchronize them properly!

Atak_Snajpera
16th January 2013, 16:49
You are not allowed to access any GUI controls from a thread, except from the applications's main thread.
Thus the Synchronize function allows you to execute a function in the context of the main thread...
Yes I know but this component from Graphics32 library is thread safe.

BTW: Instead of having a separate function for each thread, make the thread function parameterized! For example, add a parameter "index of first line to process" and a parameter "number of lines to process". Then you can use one single function for as many threads as you like. You just have to set the parameters for each thread right.
ok. But what will happen to variables declared in thread when two or more thread will be using them?

BTW3: How do you combine the results of your threads? Be sure, you synchronize them properly!
There is another Thread (SupervisorThread) waiting for workers and then calculates total average value

AverageLuma := (Average_color_THREAD1 + Average_color_THREAD2) / 2

LoRd_MuldeR
16th January 2013, 17:26
ok. But what will happen to variables declared in thread when two or more thread will be using them?

The same that always happens: If you don't synchronize access between threads (i.e. serialize all access using a Mutex/Semaphore), you can get serious problems!

But if you use a plain array and the threads access the array in a non-overlapping way (i.e. each thread only works on a part of the array that no other thread will access) it will be okay. It will be pretty much like you have a long sequence of variables and each variable is used by only one thread. It's even less problematic with read-only access. On the other hand, accessing the Canvas.Pixels[x,y] property actually invokes the TCanvas.GetPixel.GetPixel() method of the Canvas object. Who knows what that method does internally - and whether it's safe to call it multiple times in parallel from separate threads. Unless the docs explicitly state "this method is thread-safe" you have to assume the opposite...

LoRd_MuldeR
16th January 2013, 22:33
The same that always happens: If you don't synchronize access between threads (i.e. serialize all access using a Mutex/Semaphore), you can get serious problems!

But if you use a plain array and the threads access the array in a non-overlapping way (i.e. each thread only works on a part of the array that no other thread will access) it will be okay. It will be pretty much like you have a long sequence of variables and each variable is used by only one thread. It's even less problematic with read-only access. On the other hand, accessing the Canvas.Pixels[x,y] property actually invokes the TCanvas.GetPixel.GetPixel() method of the Canvas object. Who knows what that method does internally - and whether it's safe to call it multiple times in parallel from separate threads. Unless the docs explicitly state "this method is thread-safe" you have to assume the opposite...

Maybe I misunderstood your question.

Local variables (i.e. variables declared inside a function/procedure), such as "Sum" and "SumOfAverageLumas", are allocated on the stack. Each thread has its own separate stack.

Consequently, if two or more threads call the same function at the same time (in parallel), each thread will have its own separate set of local variables. There is no conflict using local variables.

But be aware: If the function/procedure belongs to a class, then the member (instance) variables of the class are shared between all calls that use the same object/instance !!!

(In your code, both threads access the same TCanvas object, which means that there can be thread conflicts inside the GetPixel() method, when accessing the TCanvas' member variables)

Atak_Snajpera
18th January 2013, 17:19
I've just discovered that instead of

Color := form1.Image321.Bitmap.Canvas.Pixels[x,y];

I should just use

Color := form1.Image321.Bitmap.Pixel[x,y];

which is 100 faster! 16Mpix image is now processed in 0,2s instead of 20s! So using more threads does not make sense now :)