| Home | Index of Samples | PDF Version of this Page |
![]() |
Code Samples:
|
The algorithm computes two orthogonal linear regressions in order to convert any arbitrary image into a flat background image without any pictorial content.
There are two symmetrical possibilities leading to the same background image:
| horizontal + vertical | vertical + horizontal | |
| 1. regression | compute the regression line of any row | compute the regression line of any column |
| intermediary image: | the values of the horizontal regression line | the values of the vertical regression line |
| 2. regression | compute the regression line of any column | compute the regression line of any row |
| background image: | the values of the vertical regression line | the values of the horizontal regression line |
![]() |
Both the upper and the lower ways lead to the same background image. The program below uses the lower way: vertical + horizontal. |
In a second step the program subtracts the flat background image from the original and rearranges the resulting RGB values between 0 to 255.
The algorithm works with any image format that can be read and written with the
Create a new Windows-project "shading". Delete the files Form1.Designer.cs and Program.cs from the project. Clear any prefabricated content from Form1.cs and replace it by the following code:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
public class Form1 : Form
{ [STAThread] static void Main() { Application.Run( new Form1() ); }
Bitmap i0, i1, i2, i3;
Color [,] a0;
Int16 [,,] a1;
float[,,] slope, y0;
public Form1()
{ MenuItem miRead = new MenuItem( "&Read", new EventHandler( MenuFileRead ) );
MenuItem miExit = new MenuItem( "&Exit", new EventHandler( MenuFileExit ) );
MenuItem miFile = new MenuItem( "&File", new MenuItem[] { miRead, miExit } );
Menu = new System.Windows.Forms.MainMenu( new MenuItem[] { miFile } );
Text = "Shading Correction by Linear Regression";
SetStyle( ControlStyles.ResizeRedraw, true );
try { i0 = new Bitmap( typeof( Form1 ), "shading.shaded_butterfly.jpg" );
define_images_and_arrays();
regression1 ( a0 );
fill_image ( i1 );
regression2 ( slope, y0 );
fill_image ( i2 );
shading_correction( slope, y0 );
fill_image ( i3 );
Width = i0.Width*2 + 10;
} catch {}
Height = 600;
}
void MenuFileRead( object obj, EventArgs ea )
{ OpenFileDialog dlg = new OpenFileDialog();
if ( dlg.ShowDialog() != DialogResult.OK ) return;
if ( i0 != null ) i0.Dispose();
i0 = (Bitmap)Image.FromFile( dlg.FileName );
define_images_and_arrays();
regression1 ( a0 );
fill_image ( i1 );
regression2 ( slope, y0 );
fill_image ( i2 );
shading_correction( slope, y0 );
fill_image ( i3 );
Width = i0.Width*2 + 10;
}
void MenuFileExit( object obj, EventArgs ea )
{ Application.Exit(); }
protected override void OnPaint( PaintEventArgs e )
{ Graphics g = e.Graphics;
g.Clear( BackColor );
Pen pen = new Pen( Color.Red, 12 );
pen.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;
try { g.DrawImage( i0 , 0, 0, i0.Width, i0.Height );
g.DrawImage( i1, i0.Width+10, 0, i0.Width, i0.Height );
g.DrawImage( i2, i0.Width+10, i0.Height+10, i0.Width, i0.Height );
g.DrawImage( i3 , 0, i0.Height+10, i0.Width, i0.Height );
g.DrawLine( pen, i0.Width-10, i0.Height/2, i0.Width+30, i0.Height/2 );
g.DrawLine( pen, i0.Width+20, 3*i0.Height/2, i0.Width-20, 3*i0.Height/2 );
g.DrawLine( pen, 3*i0.Width/2, i0.Height-10, 3*i0.Width/2, i0.Height+30 );
} catch{}
}
private void define_images_and_arrays()
{ a0 = new Color[ i0.Height, i0.Width ];
slope = new float[ 2, 3, Math.Max( i0.Height, i0.Width ) ];
y0 = new float[ 2, 3, Math.Max( i0.Height, i0.Width ) ];
a1 = new Int16[ 3, i0.Height, i0.Width ];
if ( i1 != null ) i1.Dispose(); i1 = new Bitmap( i0 );
if ( i2 != null ) i2.Dispose(); i2 = new Bitmap( i0 );
if ( i3 != null ) i3.Dispose(); i3 = new Bitmap( i0 );
for ( int y=0; y < i0.Height; y++ )
for ( int x=0; x < i0.Width; x++ )
{ a0[y,x] = i0.GetPixel( x, y );
i1.SetPixel( x, y, Color.Black );
i2.SetPixel( x, y, Color.Black );
i3.SetPixel( x, y, Color.Black );
}
}
private void regression1( Color[,] a0 )
{ Int32 x, y, z, xSize = a0.GetLength(1), ySize = a0.GetLength(0);
Int32[] sum = new Int32[3];
float[] mean = new float[3];
float[] sxy = new float[3];
float sx2, mid;
mid = ySize/2f;
for ( x=0; x < xSize; x++ )
{ for ( z=0; z < 3; z++ ) sum[z] = 0;
for ( y=0; y < ySize; y++ )
{ sum[0] += a0[y,x].R;
sum[1] += a0[y,x].G;
sum[2] += a0[y,x].B;
}
for ( z=0; z < 3; z++ )
{ mean[z] = sum[z] / ySize;
sxy[z] = 0f;
}
sx2 = 0f;
for ( y=0; y < ySize; y++ )
{ sxy[0] += (y-mid)*(a0[y,x].R - mean[0]);
sxy[1] += (y-mid)*(a0[y,x].G - mean[1]);
sxy[2] += (y-mid)*(a0[y,x].B - mean[2]);
sx2 += (y-mid)*(y-mid);
}
for ( z=0; z < 3; z++ )
{ slope[1,z,x] = sxy[z] / sx2;
y0 [1,z,x] = mean[z] - slope[1,z,x]*mid;
}
}
}
private void regression2( float[,,] slope, float[,,] y0 )
{ Int32 x, y, z, xSize = i0.Width, ySize = i0.Height;
float[] sum = new float[3];
float[] mean = new float[3];
float[] sxy = new float[3];
float sx2, mid;
mid = xSize/2f;
for ( y=0; y < ySize; y++ )
{ for ( z=0; z < 3; z++ ) sum[z] = 0;
for ( x=0; x < xSize; x++ )
for ( z=0; z < 3; z++ ) sum[z] += slope[1,z,x]*y + y0[1,z,x];
for ( z=0; z < 3; z++ )
{ mean[z] = sum[z] / xSize;
sxy[z] = 0f;
}
sx2 = 0f;
for ( x=0; x < xSize; x++ )
{ for ( z=0; z < 3; z++ )
sxy[z] += (x-mid)*(slope[1,z,x]*y + y0[1,z,x] - mean[z]);
sx2 += (x-mid)*(x-mid);
}
for ( z=0; z < 3; z++ )
{ slope[0,z,y] = sxy[z] / sx2;
y0 [0,z,y] = mean[z] - slope[0,z,y]*mid;
}
}
}
private void fill_image( Bitmap i )
{ int x, y, z;
int[] c = new int[3];
if ( i == i1 )
for ( x=0; x < i.Width; x++ )
for ( y=0; y < i.Height; y++ )
{ for ( z=0; z < 3; z++ )
{ c[z] = Convert.ToInt32( slope[1,z,x]*y + y0[1,z,x] );
c[z] = Math.Max( 0, Math.Min( 255, c[z] ) );
}
i.SetPixel( x, y, Color.FromArgb( c[0], c[1], c[2] ) );
}
else if ( i == i2 )
for ( y=0; y < i.Height; y++ )
for ( x=0; x < i.Width; x++ )
{ for ( z=0; z < 3; z++ )
{ c[z] = Convert.ToInt32( slope[0,z,y]*x + y0[0,z,y] );
c[z] = Math.Max( 0, Math.Min( 255, c[z] ) );
}
i.SetPixel( x, y, Color.FromArgb( c[0], c[1], c[2] ) );
}
else if ( i == i3 )
for ( x=0; x < i0.Width; x++ )
for ( y=0; y < i0.Height; y++ )
i.SetPixel( x, y, a0[y,x] );
}
private void shading_correction( float[,,] slope, float[,,] y0 )
{ int x, y, z;
int[] c = new int[3], min = new int[3], max = new int[3];
float[] factor = new float[3];
for ( z=0; z < 3; z++ ) { min[z] = Int32.MaxValue;
max[z] = Int32.MinValue; }
for ( y=0; y < i0.Height; y++ )
for ( x=0; x < i0.Width; x++ )
{ a1[0,y,x] = Convert.ToInt16( a0[y,x].R - ( slope[0,0,y]*x + y0[0,0,y] ) );
a1[1,y,x] = Convert.ToInt16( a0[y,x].G - ( slope[0,1,y]*x + y0[0,1,y] ) );
a1[2,y,x] = Convert.ToInt16( a0[y,x].B - ( slope[0,2,y]*x + y0[0,2,y] ) );
for ( z=0; z < 3; z++ )
{ if ( a1[z,y,x] < min[z] ) min[z] = a1[z,y,x];
if ( a1[z,y,x] > max[z] ) max[z] = a1[z,y,x];
}
}
for ( z=0; z < 3; z++ ) factor[z] = 255f / ( max[z] - min[z] );
for ( x=0; x < i0.Width; x++ )
for ( y=0; y < i0.Height; y++ )
{ for ( z=0; z < 3; z++ ) c[z] = Convert.ToInt32( ( a1[z,y,x]-min[z] ) * factor[z] );
a0[y,x] = Color.FromArgb( c[0], c[1], c[2] );
}
}
}
Recommended experiments:
1) Load different images via the File menu.
3) Store the pictures below on your hard disk and try them out:

FAQ: What sort of shading can this program remove ?
| A: All sorts of uneven illumination with a brightness ramp, i.e. with gradually change of background color raising or falling from any part of a border to the opposite border. The program cannot remove circular spotlight like this on the right: | ![]() |