Performance of the code for Convolution in spatial domain - c#

See: Image convolution in spatial domain
The following code achieves Linear convolution in Spatial domain.
public static double[,] ConvolutionSpatial(double[,] paddedImage, double[,] mask, double offset)
{
double min = 0.0;
double max = 1.0;
double factor = GetFactor(mask);
int paddedImageWidth = paddedImage.GetLength(0);
int paddedImageHeight = paddedImage.GetLength(1);
int maskWidth = mask.GetLength(0);
int maskHeight = mask.GetLength(1);
int imageWidth = paddedImageWidth - maskWidth;
int imageHeight = paddedImageHeight - maskHeight;
double[,] convolve = new double[imageWidth, imageHeight];
for (int y = 0; y < imageHeight; y++)
{
for (int x = 0; x < imageWidth; x++)
{
double sum = Sum(paddedImage, mask, x, y);
convolve[x, y] = Math.Min(Math.Max((sum / factor) + offset, min), max);
string str = string.Empty;
}
}
return convolve;
}
public static double Sum(double[,] paddedImage1, double[,] mask1, int startX, int startY)
{
double sum = 0;
int maskWidth = mask1.GetLength(0);
int maskHeight = mask1.GetLength(1);
for (int y = startY; y < (startY + maskHeight); y++)
{
for (int x = startX; x < (startX + maskWidth); x++)
{
double img = paddedImage1[x, y];
double msk = mask1[maskWidth - x + startX - 1, maskHeight - y + startY - 1];
sum = sum + (img * msk);
}
}
return sum;
}
public static double GetFactor(double[,] kernel)
{
double sum = 0.0;
int width = kernel.GetLength(0);
int height = kernel.GetLength(1);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
sum += kernel[x, y];
}
}
return (sum == 0) ? 1 : sum;
}
Its performance is as follows:
image-size kernel-size time-elapsed
------------------------------------------
100x100 3x3 13ms
512x512 3x3 291ms
1018x1280 3x3 1687ms
100x100 100x100 4983ms
512x512 512x512 35624394ms
1018x1280 1018x1280 [practically unusable]
I have two questions:
Does it look like a descent performance?
If not, how can I increase performance?

It depends on your final requirements.
The obvious things to do: replace multidimensional arrays [,] with jagged arrays [][] and reverse the order of nested loops:
for (int x = 0; ...; x++)
{
for (int y = 0; ...; y++)
{
...
}
}
instead of
for (int y = 0; ...; y++)
{
for (int x = 0; ...; x++)
{
...
}
}
In the first case the CPU cache lines are used much more efficiently (because the left index represents continuous rows) while the latter one invalidates a cache line on every iteration.
So benchmarks of code with jagged arrays and reversed loops has shown 2 times better performance for the 1018x1280 3x3 convolution comparing to the original implementation:
BenchmarkDotNet=v0.11.1, OS=Windows 10.0.17134.167 (1803/April2018Update/Redstone4)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
Frequency=3515624 Hz, Resolution=284.4445 ns, Timer=TSC
[Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3131.0
RyuJitX64 : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3131.0
Job=RyuJitX64 Jit=RyuJit Platform=X64
Method | Mean | Error | StdDev |
------------- |---------:|----------:|----------:|
BenchmarkOld | 61.82 ms | 0.3979 ms | 0.3527 ms |
BenchmarkNew | 26.98 ms | 0.1050 ms | 0.0982 ms |
Here is the code:
public static double[][] ConvolutionSpatial(double[][] paddedImage, double[][] mask, double offset)
{
double min = 0.0;
double max = 1.0;
double factor = GetFactor(mask);
int paddedImageWidth = paddedImage.Length;
int paddedImageHeight = paddedImage[0].Length;
int maskWidth = mask.Length;
int maskHeight = mask[0].Length;
int imageWidth = paddedImageWidth - maskWidth;
int imageHeight = paddedImageHeight - maskHeight;
double[][] convolve = new double[imageWidth][];
for (int x = 0; x < imageWidth; x++)
{
convolve[x] = new double[imageHeight];
for (int y = 0; y < imageHeight; y++)
{
double sum = Sum(paddedImage, mask, x, y);
convolve[x][y] = Math.Min(Math.Max((sum / factor) + offset, min), max);
string str = string.Empty;
}
}
return convolve;
}
public static double Sum(double[][] paddedImage1, double[][] mask1, int startX, int startY)
{
double sum = 0;
int maskWidth = mask1.Length;
for (int x = startX; x < (startX + maskWidth); x++)
{
var maskHeight = mask1[maskWidth - x + startX - 1].Length;
for (int y = startY; y < (startY + maskHeight); y++)
{
double img = paddedImage1[x][y];
double msk = mask1[maskWidth - x + startX - 1][maskHeight - y + startY - 1];
sum = sum + (img * msk);
}
}
return sum;
}
public static double GetFactor(double[][] kernel)
{
double sum = 0.0;
int width = kernel.Length;
for (int x = 0; x < width; x++)
{
var height = kernel[x].Length;
for (int y = 0; y < height; y++)
{
sum += kernel[x][y];
}
}
return (sum == 0) ? 1 : sum;
}
And I think it can improved more with application of SIMD operations.

Related

Parallize elementwise matrix (2dArray) calculations

This is my first question and I'm relatively new to C#. (Excuse my bad English)
I'm writing a template matching algorithm in C# using the .NET Framework with a WindowsForms Application in Visual Studio. Sadly the new indices and ranges functions from C#8.0, especially the range operator (..), didn't get implemented in the .NET Framework. I know there is a workaround for this as you can see in this thread but it is not supported by Microsoft. So I'm searching for another way to parallelize my elementwise 2dArray (matrix) calculations to make my program faster.
In my program, I'm calculating the differential square (ds) of an area (with the size of my template) inside a 2dArray (my image) and a 2dArray (my template). These values a written to a new 2dAary (DS) which holds all differential squares in the corresponding positions to the image. I can search the indices of DS where the differential square is minimal which is equal to the matching position (highest correspondence between template and image) of the template inside the image.
In python the calculation of DS is very quick using the index range operator (:) and will look like this:
H,W = I.shape # read out Height H & Width W from Image I
h,w = T.shape # read out Height h & Width w from Template T
for i in range(H-h+1):
for j in range(W-w+1):
DS[i,j] = np.sum((I[i:i+h,j:j+w] - T)**2)
But in C# I have to make the calculation of DS elementwise therefore it looks like this and takes for ever:
int Tw = template.Width;
int Th = template.Height;
int Iw = image.Width;
int Ih = image.Height;
int d = 0;
int[,] ds = new int[Tw, Th];
int[,] DS = new int[Iw - Tw + 1, Ih - Th + 1];
for (int y = 0; y < Ih - Th + 1; y++)
{
for (int x = 0; x < Iw - Tw + 1; x++)
{
for (int yT = 0; yT < Th; yT++)
{
for (int xT = 0; xT < Tw; xT++)
{
d = I[x + xT, y + yT] - T[xT, yT];
ds[xt, yt] = d * d;
}
}
int sum = ds.Cast<int>().Sum();
DS[x, y] = sum;
}
}
I know that I could use threads but that would be a little complex for me.
Or maybe I could use CUDA with my Nvidia GPU to speed things up a little.
But I am asking you and myself is there another way to parallelize (optimize) my elementwise 2dArray calculations?
I look forward to any help.
Many thanks in advance!!!
EDIT:
Here I have a working example of my code for a .NET Framework Console App. As you can see I make a lot of elementwise 2d and 3d Array calculations which I would like to process in parallel (or perform them faster in any other way):
using System;
using System.Drawing;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TemplateMatcher_Console
{
class Program
{
public static int[,,] bitmapToMatrix(Bitmap bmp)
{
int[,,] I = new int[bmp.Width, bmp.Height, 3];
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
Color pix = bmp.GetPixel(x, y);
I[x, y, 0] = Convert.ToInt32(pix.R);
I[x, y, 1] = Convert.ToInt32(pix.G);
I[x, y, 2] = Convert.ToInt32(pix.B);
}
}
return I;
}
public static int[] indexOfMiniumValue(int[,] matrix)
{
int value = 0;
int minValue = 999999999;
int minFirstIndex = 0;
int minSecondIndex = 0;
int[] ij = new int[2];
for (int i = 0; i < matrix.GetLength(0); i++)
{
for (int j = 0; j < matrix.GetLength(1); j++)
{
value = matrix[i, j];
if (value < minValue)
{
minValue = value;
minFirstIndex = i;
minSecondIndex = j;
}
}
}
ij[0] = minFirstIndex;
ij[1] = minSecondIndex;
return ij;
}
public static void Print2DArray<T>(T[,] matrix)
{
for (int i = 0; i < matrix.GetLength(0); i++)
{
for (int j = 0; j < matrix.GetLength(1); j++)
{
Console.Write(matrix[i, j] + "\t");
}
Console.WriteLine();
}
}
static void Main(string[] args)
{
// Deklaration & Eingabe
Console.WriteLine("Type the filepath for your image and then press Enter");
string im = Console.ReadLine();
Console.WriteLine("\nType the filepath for your template and then press Enter");
string temp = Console.ReadLine();
Bitmap template = new Bitmap(#temp);
Bitmap image = new Bitmap(#im);
int Tw = template.Width;
int Th = template.Height;
int Iw = image.Width;
int Ih = image.Height;
int[,] ds = new int[Tw, Th];
int[,] DS = new int[Iw - Tw + 1, Ih - Th + 1];
int[,,] DS_rgb = new int[Iw - Tw + 1, Ih - Th + 1, 3];
int[] xy = new int[2];
// Verarbeitung
// int[,,] I = Array.ConvertAll(image_I, new Converter<byte, int>(Convert.ToInt32));
int[,,] I = bitmapToMatrix(image);
int[,,] T = bitmapToMatrix(template);
for (int rgb = 0; rgb < 3; rgb++)
{
for (int y = 0; y < Ih - Th + 1; y++)
{
for (int x = 0; x < Iw - Tw + 1; x++)
{
//DS_rgb[x, y, rgb] = (I[x .. x + template.Width, y .. y + template.Height, rgb] - T[0 .. template.Width, 0 .. template.Height, rgb]);
for (int yT = 0; yT < Th; yT++)
{
for (int xT = 0; xT < Tw; xT++)
{
ds[xT, yT] = (I[x + xT, y + yT, rgb] - T[xT, yT, rgb]) * (I[x + xT, y + yT, rgb] - T[xT, yT, rgb]);
}
}
int sum = ds.Cast<int>().Sum();
DS_rgb[x, y, rgb] = sum;
}
}
}
//DS[.., ..] = DS_rgb[.., .., 0] + DS_rgb[.., .., 1] + DS_rgb[.., .., 2];
for (int y = 0; y < Ih - Th + 1; y++)
{
for (int x = 0; x < Iw - Tw + 1; x++)
{
DS[x, y] = DS_rgb[x, y, 0] + DS_rgb[x, y, 1] + DS_rgb[x, y, 2];
}
}
//xy = DS.FindIndex(z => z == Math.Min(DS));
xy = indexOfMiniumValue(DS);
// Ausgabe
// Ausgeben der Matrix DS
/*
Console.WriteLine("\nMatrix with all differtial squares:");
Print2DArray(DS);
*/
Console.WriteLine($"\nPosition of your template in your image (upper left corner): ({xy[0]}, {xy[1]})");
Console.Write("\nPress any key to close the TemplateMatcher console app...");
Console.ReadKey();
}
}
}

Processing image stops mid-loop for no reason c#

I have an image, and I want to take each square of 256X256 pixels, find the mean color, and draw that square with said color.
Problem: it seems that after the first square the processing suddenly stops, but after following the program I can see the indexes are just fine. I have no idea if the problem lies with file writing in my computer system, or a wrong use of the "Bitmap" class functions.
original:
result:
code:
public const int big =256;
public const int small = 16;
static void Main(string[] args)
{
Bitmap bt = new Bitmap(#"C:\Users\mishe\Desktop\00_sorted images - training\general shores\agulhas_oli_2016146_lrg.jpg");
Bitmap bt2 = bt;
Color MeanColor;
double r = 0;
double g = 0;
double b = 0;
int i = 0;
int j = 0;
//big loop to go over all image
for (i = 0; i < bt.Height-257; i+=256)
{
for (j = 0; j < bt.Width-257; j+=256)
{
/////////////////////////////
//small loop on 1 square to get the mean color of the area
for (int x = i; x < big; x++)
{
for (int y = j; y < big; y++)
{
r += bt.GetPixel(x, y).R;
g += bt.GetPixel(x, y).G;
b += bt.GetPixel(x, y).B;
}
}
/////////////////////////////
r = r / Math.Pow(big, 2);
g = g / Math.Pow(big, 2);
b = b / Math.Pow(big, 2);
MeanColor = Color.FromArgb((int)r, (int)g, (int)b);
/////////////////////////////
//small loop on the same square to set the color
for (int x = i; x < big; x++)
{
for (int y = j; y < big; y++)
{
bt2.SetPixel(x, y, MeanColor);
}
}
/////////////////////////////
}
}
bt2.Save(#"C:\Users\mishe\Desktop\compressed image.jpg", ImageFormat.Jpeg);
}
This line:
//small loop on 1 square to get the mean color of the area
for (int x = i; x < big; x++)
After the first square, x will be 256, so it won't do the small loop.
I think you want:
for (int x = i; x < i + big; x++)
Or your small loop could be:
for (int x = 1; x < big; x++)
and then add the large and small values inside the loop:
r += bt.GetPixel(i + x, j + y).R;

Sharpen operation giving an output with incorrect contrast

Take a look at this source code. A 3x3 Sharpen filter with the following kernel
0 -1 0
-1 5 -1
0 -1 0
gives the following output:
I tried to replicate the outcome using my own code which is pretty much straight forward:
public partial class FilterForm : Form
{
public FilterForm()
{
InitializeComponent();
Bitmap image = (Bitmap)Bitmap.FromFile("lena.jpg");
InputPictureBox.Image = image;
double[,] dImage = ToDouble2d(image);
double[,] dMask = { { 0,-1, 0, },
{ -1, 5, -1, },
{ 0,-1, 0, }, };
double[,]dConv = LinearConvolutionSpatial(dImage, dMask);
Bitmap conv = ToBitmap2d(dConv, PixelFormat.Format32bppArgb);
OutputPictureBox.Image = conv;
}
public double[,] ToDouble2d(Bitmap input)
{
int width = input.Width;
int height = input.Height;
double[,] array2d = new double[width, height];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
Color cl = input.GetPixel(x, y);
double gray = ((cl.R * 0.3) + (cl.G * 0.59) + (cl.B * 0.11));
array2d[x, y] = gray / 255.0;
}
}
return array2d;
}
public Bitmap ToBitmap2d(double[,] image, PixelFormat pixelFormat)
{
int Width = image.GetLength(0);
int Height = image.GetLength(1);
Bitmap bmp = new Bitmap(Width, Height, pixelFormat);
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
int i = (int)(image[x, y] * 255.0);
if (i > 255) i = 255;
if (i < 0) i = 0;
Color clr = Color.FromArgb(i, i, i);
bmp.SetPixel(x, y, clr);
}
}
return bmp;
}
private double[,] LinearConvolutionSpatial(double[,] paddedImage, double[,] mask)
{
int paddedImageWidth = paddedImage.GetLength(0);
int paddedImageHeight = paddedImage.GetLength(1);
int maskWidth = mask.GetLength(0);
int maskHeight = mask.GetLength(1);
int imageWidth = paddedImageWidth - maskWidth;
int imageHeight = paddedImageHeight - maskHeight;
double[,] convolve = new double[imageWidth, imageHeight];
for (int y = 0; y < imageHeight; y++)
{
for (int x = 0; x < imageWidth; x++)
{
double sum = Sum(paddedImage, mask, x, y);
int xxx = x;
int yyy = y;
convolve[xxx, yyy] = sum;
string str = string.Empty;
}
}
Rescale(convolve);
return convolve;
}
double Sum(double[,] paddedImage1, double[,] mask1, int startX, int startY)
{
double sum = 0;
int maskWidth = mask1.GetLength(0);
int maskHeight = mask1.GetLength(1);
for (int y = startY; y < (startY + maskHeight); y++)
{
for (int x = startX; x < (startX + maskWidth); x++)
{
double img = paddedImage1[x, y];
double msk = mask1[maskWidth - x + startX - 1, maskHeight - y + startY - 1];
sum = sum + (img * msk);
}
}
return sum;
}
void Rescale(double[,] convolve)
{
int imageWidth = convolve.GetLength(0);
int imageHeight = convolve.GetLength(1);
double minAmp = 0.0;
double maxAmp = 0.0;
for (int j = 0; j < imageHeight; j++)
{
for (int i = 0; i < imageWidth; i++)
{
minAmp = Math.Min(minAmp, convolve[i, j]);
maxAmp = Math.Max(maxAmp, convolve[i, j]);
}
}
double scale = 1 / (Math.Abs(minAmp) + maxAmp);
for (int j = 0; j < imageHeight; j++)
{
for (int i = 0; i < imageWidth; i++)
{
double d = (convolve[i, j] + Math.Abs(minAmp)) * scale;
convolve[i, j] = d;
}
}
}
}
But, the problem I am facing is, my output has a different contrast.
What points I should work on?
Some of the above code has a min/max effect and balances the histogram.
Remove the normalizing bit from this line:
array2d[x, y] = gray;// / 255.0;
I've removed the rescale multiplier of 255 in this piece:
public Bitmap ToBitmap2d(double[,] image, PixelFormat pixelFormat)
{
int Width = image.GetLength(0);
int Height = image.GetLength(1);
Bitmap bmp = new Bitmap(Width, Height, pixelFormat);
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
int i = (int)(image[x, y] * 1);
if (i > 255) i = 255;
if (i < 0) i = 0;
Color clr = Color.FromArgb(i, i, i);
bmp.SetPixel(x, y, clr);
}
}
return bmp;
}
And I've also removed the scaling factor from this function:
void Rescale(double[,] convolve)
{
int imageWidth = convolve.GetLength(0);
int imageHeight = convolve.GetLength(1);
double minAmp = 0.0;
double maxAmp = 255.0;
for (int j = 0; j < imageHeight; j++)
{
for (int i = 0; i < imageWidth; i++)
{
minAmp = Math.Min(minAmp, convolve[i, j]);
maxAmp = Math.Max(maxAmp, convolve[i, j]);
}
}
double scale = 1 / (Math.Abs(minAmp) + maxAmp);
for (int j = 0; j < imageHeight; j++)
{
for (int i = 0; i < imageWidth; i++)
{
double d = (convolve[i, j]);// + Math.Abs(minAmp)) * scale;
convolve[i, j] = d;
}
}
}
Appears to work as intended after those changes are made, in this case.
I have found the solution from this link. The main clue was to introduce an offset and a factor.
factor is the sum of all values in the kernel.
offset is an arbitrary value to fix the output further.
The following source code is supplied in the given link:
private void SafeImageConvolution(Bitmap image, ConvMatrix fmat)
{
//Avoid division by 0
if (fmat.Factor == 0)
return;
Bitmap srcImage = (Bitmap)image.Clone();
int x, y, filterx, filtery;
int s = fmat.Size / 2;
int r, g, b;
Color tempPix;
for (y = s; y < srcImage.Height - s; y++)
{
for (x = s; x < srcImage.Width - s; x++)
{
r = g = b = 0;
// Convolution
for (filtery = 0; filtery < fmat.Size; filtery++)
{
for (filterx = 0; filterx < fmat.Size; filterx++)
{
tempPix = srcImage.GetPixel(x + filterx - s, y + filtery - s);
r += fmat.Matrix[filtery, filterx] * tempPix.R;
g += fmat.Matrix[filtery, filterx] * tempPix.G;
b += fmat.Matrix[filtery, filterx] * tempPix.B;
}
}
r = Math.Min(Math.Max((r / fmat.Factor) + fmat.Offset, 0), 255);
g = Math.Min(Math.Max((g / fmat.Factor) + fmat.Offset, 0), 255);
b = Math.Min(Math.Max((b / fmat.Factor) + fmat.Offset, 0), 255);
image.SetPixel(x, y, Color.FromArgb(r, g, b));
}
}
}

How to changes doubles to a greyscale image?

I've created a 2d array of 1024 x 1024 values ranging from -1 to 1, but I do not know how I am supposed to change this to a greyscale image.
What I have been doing is assigning a certain color to certain values, but this is not what I was going for.
What I have:
Specific ranges of values between -1 and 1 are mapped to distinct colors in a noncontinuous way (see the code snippet below)
What I want:
Values between -1 and 1 are mapped to greyscale varying uniformly from black at -1 to white at 1 or vice-versa
Code for the current version
private void button1_Click(object sender, EventArgs e)
{
sw.Start();
LibNoise.Perlin perlinMap = new LibNoise.Perlin();
perlinMap.Lacunarity = lacunarity + 0.01d;
perlinMap.NoiseQuality = LibNoise.NoiseQuality.High;
perlinMap.OctaveCount = octaveCount;
perlinMap.Persistence = persistence;
perlinMap.Frequency = frequency;
perlinMap.Seed = 1024;
if (radioButton1.Checked)
perlinMap.NoiseQuality = LibNoise.NoiseQuality.Low;
else if (radioButton2.Checked)
perlinMap.NoiseQuality = LibNoise.NoiseQuality.Standard;
else if (radioButton3.Checked)
perlinMap.NoiseQuality = LibNoise.NoiseQuality.High;
double sample = trackBar6.Value * 10;
double[,] perlinArray = new double[resolutieX, resolutieY];
for (int x = 0; x < resolutieX; x++)
{
for (int y = 0; y < resolutieY; y++)
{
perlinArray[x, y] = perlinMap.GetValue(x / sample, y / sample, 1d);
}
}
draw(perlinArray);
textBox12.Text = sw.ElapsedMilliseconds.ToString() + "ms";
sw.Reset();
}
public void draw(double[,] array)
{
Color color = Color.DarkBlue;
// Bitmap b = new Bitmap(1, 1);
Color[,] colorArray = new Color[resolutieX, resolutieY];
Bitmap afbeelding = new Bitmap( 1024, 1024);
// int tileSize = 1024 / resolutieY;
for (int y = 1; y < resolutieY; y++)
{
for (int x = 1; x < resolutieX; x++)
{
colorArray[x, y] = array[x, y] <= 0.0 ? Color.DarkBlue :
array[x, y] <= 0.1 ? Color.Blue :
array[x, y] <= 0.2 ? Color.Beige :
array[x, y] <= 0.22 ? Color.LightGreen :
array[x, y] <= 0.40 ? Color.Green :
array[x, y] <= 0.75 ? Color.DarkGreen :
array[x, y] <= 0.8 ? Color.LightSlateGray :
array[x, y] <= 0.9 ? Color.Gray :
array[x, y] <= 1.0 ? Color.DarkSlateGray :
Color.DarkSlateGray;
// colorArray[]
// afbeelding.SetPixel(x, y, color);
}
}
for (int y = 1; y < resolutieY; y++)
{
for (int x = 1; x < resolutieX; x++)
{
afbeelding.SetPixel(x, y, colorArray[x, y]);
}
}
pictureBox1.Image = afbeelding;
}
Ohhh, lovely fractals... :)
As you are using a 2d vector from -1 to 1, you have to recalculate it to 0..255. Your function is
f(x) = 255 * (x+1)/2
Then all you have to do, is to create a 2D Color vector with f(x)
foreach (int value in 2dVector)
{
2dColorVector.add(new Color.fromArgb(255, f(x), f(x), f(x));
}
Is pseudocode, but i think you could understand it clearly :)
You can get a grayscale image by setting the primary colors (RGB) to equal values.
One way of achieving this is to make a function that calculates the average of the RGB components of the colors you have, and then set each of the components to the average value.
Example - Average method:
Color ToGrayscale(Color c)
{
int avg = (c.R + c.G + c.B)/3;
return Color.FromArgb(avg, avg, avg);
}
Then apply that function for each output pixel:
for (int y = 1; y < resolutieY; y++)
{
for (int x = 1; x < resolutieX; x++)
{
afbeelding.SetPixel(x, y, ToGrayscale(colorArray[x, y]));
}
}
Luminosity
A more sophisticated version of grayscaling is the Luminosity method. It also averages the values, but it is a weighted average that takes human perception into account.
The formula is: 0.2126*Red + 0.7152*Green + 0.0722*Blue.
You can see how the formula is weighted towards which colors the human eye is most sensitive to.
To see if this alternate approach looks better for your project you simply use the Luminosity formula for calculating the average instead:
Color ToGrayscaleLuminosity(Color c)
{
var avg = (int)Math.Round(0.2126 * c.R + 0.7152 * c.G + 0.0722 * c.B);
return Color.FromArgb(avg, avg, avg);
}

Retinex algorithm implementation

I need to implement Single Scale retinex and multiscale retinex algorithm in C#,
I searched a bit but couldn't find any useful practice projects and artilces with code
As I understood correctly I should:
Convert RGB to YUV
Blur the image using Gaussian blur filter
Use I'(x, y) = 255*log10( I(x, y)/G(x, y) ) + 127.5
I - is illumination, G - Gaussian kernel, I' - the result image
Сonvert back YUV to RGB
This code is not working correctly
public static Image<Bgr, byte> SingleScaleRetinex(this Image<Bgr, byte> img, int gaussianKernelSize, double sigma)
{
var radius = gaussianKernelSize / 2;
var kernelSize = 2 * radius + 1;
var ycc = img.Convert<Ycc, byte>();
var sum = 0f;
var gaussKernel = new float[kernelSize * kernelSize];
for (int i = -radius, k = 0; i <= radius; i++, k++)
{
for (int j = -radius; j <= radius; j++)
{
var val = (float)Math.Exp(-(i * i + j * j) / (sigma * sigma));
gaussKernel[k] = val;
sum += val;
}
}
for (int i = 0; i < gaussKernel.Length; i++)
gaussKernel[i] /= sum;
var gray = new Image<Gray, byte>(ycc.Size);
CvInvoke.cvSetImageCOI(ycc, 1);
CvInvoke.cvCopy(ycc, gray, IntPtr.Zero);
// Размеры изображения
var width = img.Width;
var height = img.Height;
var bmp = gray.Bitmap;
var bitmapData = bmp.LockBits(new Rectangle(Point.Empty, gray.Size), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
unsafe
{
for (var y = 0; y < height; y++)
{
var row = (byte*)bitmapData.Scan0 + y * bitmapData.Stride;
for (var x = 0; x < width; x++)
{
var color = row + x;
float val = 0;
for (int i = -radius, k = 0; i <= radius; i++, k++)
{
var ii = y + i;
if (ii < 0) ii = 0; if (ii >= height) ii = height - 1;
var row2 = (byte*)bitmapData.Scan0 + ii * bitmapData.Stride;
for (int j = -radius; j <= radius; j++)
{
var jj = x + j;
if (jj < 0) jj = 0; if (jj >= width) jj = width - 1;
val += *(row2 + jj) * gaussKernel[k];
}
}
var newColor = 127.5 + 255 * Math.Log(*color / val);
if (newColor > 255)
newColor = 255;
else if (newColor < 0)
newColor = 0;
*color = (byte)newColor;
}
}
}
bmp.UnlockBits(bitmapData);
CvInvoke.cvCopy(gray, ycc, IntPtr.Zero);
CvInvoke.cvSetImageCOI(ycc, 0);
return ycc.Convert<Bgr, byte>();
}
Look at:
http://www.fer.unizg.hr/ipg/resources/color_constancy
These algorithms are modifications of the Retinex algorithm (with speed improvement) although the author gave them funny names :)
There is a full source code (C++, but it is written very nicely).
Sorry for necro-posting, but it seems that there's a mistake in step 3 of your procedure that can mislead someone passing by.
In order to apply the correction, you want to divide source image by Gauss-filtered copy of it, not the Gaussian kernel itself. Approximately, in pseudo-code:
I_filtered(x,y) = G(x,y) * I(x,y)
I'(x,y) = log(I(x,y) / I_filtered(x,y))
And then apply casting of I'(x,y) to required numeric type (uint8, as I can refer from original post).
More on that topic can be found in this paper:
Ri(x, y) = log(Ii(x, y)) − log(Ii(x, y) ∗ F(x, y))
where Ii
is the input image on the i-th color channel, Ri
is the retinex output image on the i-th
channel and F is the normalized surround function.
.

Categories