I want to iterate over all pixel of an image and compare with a search pattern. With best performance possible and in C#. I found emgu cv, a wrapper for Intels opencv. But I
I wanted to supplement the already thorough answer of @Luce Del Tongo and @Dent7777 with one further solution for per-value operations on image data using emgucv. Whilst my testing (same method) shows only equivalent performance to that of the final solution above; I believe that it's a valuable alternative.
The Image.Convert() method has a number of powerful overloads that can be used to apply delegate Functions per-value on the calling image to return a resulting image of the same or differing TDepth. See "Generic Operation" at http://www.emgu.com/wiki/index.php/Working_with_Images for more detail.
Most significant is the ability to pass up to three additional images as parameters to the Convert method, the contained data of which will be passed to the delegate.
For example, a complex filter:
// From the input image "original"
Image<Bgr, Byte> original = new Image<Bgr, byte>(1024, 768);
// I wish to produce an image "result"
Image<Gray, double> result = new Image<Gray, double>(1024, 768);
// Whereby each "result" pixel is
Bgr originalPixel;
double resultPixel =
(0.5 + Math.Log(originalPixel.Green) -
(0.5 * Math.Log(originalPixel.Red)) -
(0.5 * Math.Log(originalPixel.Blue)));
To use the solution provided in previous answer:
byte[,,] sourceData = original.Data;
double[,,] resultData = result.Data;
for (int i = original.Rows - 1; i >= 0; i--)
{
for (int j = original.Cols - 1; j >= 0; j--)
{
resultData[i,j,0] =
(0.5 + Math.Log(sourceData[i, j, 1]) -
(0.5 * Math.Log(sourceData[i, j, 2])) -
(0.5 * Math.Log(sourceData[i, j, 0])));
}
}
Average Time on my machine: (676ms)
Alternatively, I can call Convert on the Blue channel of the original, passing the other two channels as parameters, along with a delegate method of corresponding signature to perform my computation:
result =
original[0].Convert<byte, byte, double>(
original[1],
original[2],
Compute);
Where:
private double Compute(byte B, byte G, byte R)
{
return (0.5 + Math.Log(G) - (0.5 * Math.Log(R)) - (0.5 * Math.Log(B)));
}
Average Time on my machine: (674ms)
Perhaps just personal preference, but I hope this may be of value to someone somewhere.
As emgu website state there are primarly two strategies:
The safe (slow) way
Suppose you are working on an Image<Bgr, Byte>
.
You can obtain the pixel on the y-th row and x-th column by calling
Bgr color = img[y, x];
Setting the pixel on the y-th row and x-th column is also simple
img[y,x] = color;
The fast way
The Image pixels values are stored in the Data property, a 3D array. Ok so this is true but does not tell how to do it in a real scenario. So let's see some working code and then discuss performance and optimization:
Image<Bgr, Byte> original = newImage<Bgr, byte>(1024, 768);
Stopwatch evaluator = newStopwatch();
int repetitions = 20;
Bgr color = newBgr(100, 40, 243);
evaluator.Start();
for (int run = 0; run < repetitions; run++)
{
for (int j = 0; j < original.Cols; j++)
{
for (int i = 0; i < original.Rows; i++)
{
original[i, j] = color;
}
}
}
evaluator.Stop();
Console.WriteLine("Average execution time for {0} iteration \n using column per row access: {1}ms\n", repetitions, evaluator.ElapsedMilliseconds / repetitions);
So this is the average running time that you have after 20 runs using the safe slow way of setting image pixel On my machine it takes 1021ms...
So 1021 milliseconds as average time for looping and setting a number of pixels equal to 1024*768. We could have done a little bit better by looping on row by row
So let's refactor a little bit our code and let's use the faster way using directly Image.Data property:
evaluator.Reset();
evaluator.Start();
for (int run = 0; run < repetitions; run++)
{
for (int i = 0; i < original.Rows; i++)
{
for (int j = 0; j < original.Cols; j++)
{
original.Data[i, j, 0] = 100;
original.Data[i, j, 1] = 40;
original.Data[i, j, 2] = 243;
}
}
}
evaluator.Stop();
Console.WriteLine("Average execution time for {0} iterations \n using Data property: {1}ms\n", repetitions, evaluator.ElapsedMilliseconds / repetitions);
On my machine it takes 519ms. So we have gained a performance boost of 50%. The execution time has been decreased by a factor of two.
So looking carefully at the code keeping in mind that we are using C#, we can do a minor change that will drastically boost again our image pixel setting performance... we should not use c# property inside a loop!!!
evaluator.Reset();
evaluator.Start();
byte[,,] data = original.Data;
for (int run = repetitions - 1; run >= 0; run--)
{
for (int i = original.Rows - 1; i >= 0; i--)
{
for (int j = original.Cols - 1; j >= 0; j--)
{
data[i, j, 0] = 100;
data[i, j, 1] = 40;
data[i, j, 2] = 243;
}
}
}
evaluator.Stop();
With this latest piece of code you will have a huge performance boost (73ms) due to correct use of C# language.