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: |