I first came into contact with digital image processing in high school. At that time, PHOTOSHOP was still 4.0. Maybe because of my preconceptions, I still have no interest in learning 3DMAX and the like. The leap from 2D to 3D is probably nothing to do with me, and I can’t bear to leave that square to Cubic high salary.... Haha.
When I was in college, I wrote some image processing programs with my classmates. At that time, programming was still very casual, and all I thought about was how to implement it. Now it seems that real technology is the ability to grasp the overall situation, rather than the magic of a flash of inspiration. . I came into contact with some foreign image processing programs a few days ago. Here is a summary. I estimate that I will not study image processing specifically in the future.
A former classmate once told me that .net does not have pointers. Now many training courses seem to say the same thing. In fact, this is a fallacy. It’s just that the framework does not recommend the use of pointers, especially in cross-process operations such as webservise and remoting, pointers are unsafe. But everyone who has used TC should be deeply impressed by the execution efficiency of pointers. Under the demand for batch calculation of large-scale data, pointers are the only choice. Therefore, .net cleverly retains the pointer and lists it in the unsafe method set. Reasonable use of pointers will greatly improve execution efficiency. I have done experiments to perform point-by-point operations on a 640*480 image. Non-pointer operations take several minutes, while pointer operations are completed almost instantly. So don't be afraid to use pointers.
The second is mathematics. I advise everyone to understand it before writing a program. Mathematics class is not a joke... If you don’t understand, you have to lie in bed and think about it over and over again. I always feel that mathematics can prevent Alzheimer’s disease.
Closer to home, let’s talk about the program structure:
Imaging projects (filters, textures, image modes)
Math project (algorithm, boundary, customization. and common calculation methods)
Main program project
Let me give you an example. I will also do some interface-oriented programming.
public interface IFilter
{
Bitmap Apply( Bitmap img );
}
To illustrate, I will also do interface-oriented programming. Each filter must implement this interface. The interface definition also includes an excuse definition that does not generate actual images, but only generates binary objects, which will not be considered here for the time being. Take the invert color filter as an example
public Bitmap Apply( Bitmap srcImg )
{
//get source image size
int width = srcImg.Width;
int height = srcImg.Height;
PixelFormat fmt = ( srcImg.PixelFormat == PixelFormat.Format8bppIndexed ) ?
PixelFormat.Format8bppIndexed : PixelFormat.Format24bppRgb;
// lock source bitmap data
BitmapData srcData = srcImg.LockBits(
new Rectangle(0, 0, width, height),
ImageLockMode.ReadOnly, fmt );
// create new image
Bitmap dstImg = ( fmt == PixelFormat.Format8bppIndexed ) ?
AForge.Imaging.Image.CreateGrayscaleImage( width, height ):
new Bitmap( width, height, fmt );
// lock destination bitmap data
BitmapData dstData = dstImg.LockBits(
new Rectangle(0, 0, width, height),
ImageLockMode.ReadWrite, fmt );
// copy image
Win32.memcpy( dstData.Scan0, srcData.Scan0, srcData.Stride * height );
// process the filter
ProcessFilter( dstData, fmt );
// unlock both images
dstImg.UnlockBits(dstData);
srcImg.UnlockBits( srcData );
return dstImg;
}
It is the entrance to the filter method and completes the preparatory work before processing. ProcessFilter also calls the ProcessFilter method common in each filter class, and this ProcessFilter is the key to realizing the function, point-by-point operation or template operation.
// Process the filter
private unsafe void ProcessFilter( BitmapData data, PixelFormat fmt )
{
int width = data.Width;
int height = data.Height;
int lineSize = width * ( ( fmt == PixelFormat.Format8bppIndexed ) ? 1 : 3 );
int offset = data.Stride - lineSize;
// do the job
byte * ptr = (byte *) data.Scan0.ToPointer( );
// invert
for ( int y = 0; y < height; y++ )
{
for ( int x = 0; x < lineSize; x++, ptr ++ )
{
// ivert each pixel
*ptr = (byte)( 255 - *ptr );
}
ptr += offset;
}
}
Among them, Format8bppIndexed does not need to be too concerned. I personally think that it is not necessary to consider the issue of compatibility with it in the early stages of design.
Now let’s talk about textures. I haven’t thought much about this before, but I found that foreigners like to play with it because textures have more room to play in mathematics. I don’t know how they came up with it. It may be true based on imagination. It's difficult. Maybe one of them discovered this method when playing mathematical modeling software, so the high-level mathematics teacher refused to accept anyone and played with the algorithm very hard. Anyway, I think that's what happened. . .
public interface ITextureGenerator
{
/**//// <summary>
/// Generate texture
/// </summary>
float[,] Generate( int width, int height );
/**//// <summary>
/// Reset - regenerate internal random numbers
/// </summary>
void Reset( );
}
This is the implementation interface of the texture generator. In order to ensure that the texture is different each time, random numbers must be updated as calculation parameters.
private Math.PerlinNoise noise = new Math.PerlinNoise( 1.0 / 32, 0.05, 0.5, 8 );
Achieving texture details also requires noise, so many types of noise need to be implemented.
//Constructors
public WoodTexture( ) : this( 12.0 ) { }
public WoodTexture(double rings)
{
this.rings = rings;
Reset( );
}
The constructor provides the setting of the default value, which is the limit of the unit texture size.
//Generate texture
public float[,] Generate( int width, int height )
{
float[,] texture = new float[height, width];
int w2 = width / 2;
int h2 = height / 2;
for ( int y = 0; y < height; y++ )
{
for (int x = 0; x < width; x++)
{
double xv = (double) ( x - w2 ) / width;
double yv = (double) ( y - h2 ) / height;
texture[y, x] =
Math.Max( 0.0f, Math.Min( 1.0f, (float)
Math.Abs( Math.Sin(
( Math.Sqrt( xv * xv + yv * yv ) + noise.Function2D( x + r, y + r ) )
* Math.PI * 2 * rings
))
));
}
}
return texture;
}
That's it. . . The result of my bad math. I don’t even know what she is talking about. I choose the maximum value from the minimum value. Algorithms are not difficult to find, the key is to see how the structure integrates them.
public void Reset( )
{
r = rand.Next( 5000 );
}Don’t forget this random number, the image of the number also needs natural beauty.
The object-oriented concept in Math engineering is not easy to implement. Take a look at PerlinNoise to inspire others.
public PerlinNoise( double initFrequency, double initAmplitude, double persistance, int octaves )
{
this.initFrequency = initFrequency;
this.initAmplitude = initAmplitude;
this.persistance = persistance;
this.octaves = octaves;
}
First, we need to collect data, because image processing involves one-dimensional and two-dimensional situations, so underlying methods like noise need to provide corresponding methods for the two situations.
/**//// <summary>
/// 1-D Perlin noise function
/// </summary>
public double Function(double x)
{
double frequency = initFrequency;
double amplitude = initAmplitude;
double sum = 0;
// octaves
for (int i = 0; i < octaves; i++)
{
sum += SmoothedNoise( x * frequency ) * amplitude;
frequency *= 2;
amplitude *= persistence;
}
return sum;
}
/**//// <summary>
/// 2-D Perlin noise function
/// </summary>
public double Function2D(double x, double y)
{
double frequency = initFrequency;
double amplitude = initAmplitude;
double sum = 0;
// octaves
for (int i = 0; i < octaves; i++)
{
sum += SmoothedNoise( x * frequency, y * frequency ) * amplitude;
frequency *= 2;
amplitude *= persistence;
}
return sum;
}
What is the difference between one-dimensional and two-dimensional? When I was in middle school, I learned that the movement of lines generates surfaces. When I was in college, I learned that cyclic and changing lines can represent surfaces. But after doing edge recognition and sharpening, I also found that I underestimated the line before, but in fact it is just one less parameter than the surface.
/**//// <summary>
/// Ordinary noise function
/// </summary>
protected double Noise(int x)
{
int n = ( x << 13 ) ^ x;
return ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7fffffff ) / 1073741824.0 );
}
protected double Noise(int x, int y)
{
int n = x + y * 57;
n = ( n << 13 ) ^ n ;
return ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7fffffff ) / 1073741824.0 );
} Once again proves the previous paragraph. Personally, I feel that this x+y*57 has a bit of a projection meaning. Get the corresponding noise value. But noise cannot be used directly.
/**//// <summary>
/// Smoothed noise
/// </summary>
protected double SmoothedNoise( double x )
{
int xInt = (int) x;
double xFrac = x - xInt;
return CosineInterpolate( Noise( xInt ) , Noise( xInt + 1 ), xFrac );
}
protected double SmoothedNoise(double x, double y)
{
int xInt = (int) x;
int yInt = (int) y;
double xFrac = x - xInt;
double yFrac = y - yInt;
// get four noise values
double x0y0 = Noise( xInt , yInt );
double x1y0 = Noise( xInt + 1, yInt );
double x0y1 = Noise( xInt , yInt + 1 );
double x1y1 = Noise( xInt + 1, yInt + 1) ;
// x interpolation
double v1 = CosineInterpolate( x0y0, x1y0, xFrac );
double v2 = CosineInterpolate( x0y1, x1y1, xFrac );
//yinterpolation
return CosineInterpolate( v1, v2, yFrac );
} Smooth noise, which seems a bit incongruous to be called, operates by cosine interpolation rather than discrete cosine. What is cosine interpolation? /**//// <summary>
/// Cosine interpolation
/// </summary>
protected double CosineInterpolate( double x1, double x2, double a )
{
double f = ( 1 - Math.Cos( a * Math.PI ) ) * 0.5;
return x1 * ( 1 - f ) + x2 * f;
}That’s it, there are some things that it’s enough for the master to know, and you can just do them accordingly. Why? Because you may not understand it in your lifetime, but someone will naturally figure it out, and knowledge is still being passed on. Just like you don’t have to know your stomach acid ratio to enjoy delicious and spicy foods, you don’t have to worry about indigestion for future generations. There is no need to force some things, it’s a bit negative, haha.
The picture is not difficult, as long as you grasp the calling relationship, and a floating form like photoshop is the best choice, I think, // Invert image
private void invertColorFiltersItem_Click(object sender, System.EventArgs e)
{
ApplyFilter(new Invert());
}
// Apply filter on the image
private void ApplyFilter(IFilter filter)
{
try
{
// set wait cursor
this.Cursor = Cursors.WaitCursor;
// apply filter to the image
Bitmap newImage = filter.Apply(image);
if (host.CreateNewDocumentOnChange)
{
// open new image in new document
host.NewDocument(newImage);
}
else
{
if (host.RememberOnChange)
{
// backup current image
if (backup != null)
backup.Dispose();
backup = image;
}
else
{
// release current image
image.Dispose();
}
image = newImage;
// update
UpdateNewImage();
}
}
catch(ArgumentException)
{
MessageBox.Show("Selected filter can not be applied to the image", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
// restore cursor
this.Cursor = Cursors.Default;
}
}If the call is smooth, no amount of code will feel messy. For beginners, make good use of regions.
There is also the concept of DocumentsHost. It is very convenient to use it to host image files and connect images and forms. /**//// <summary>
/// IDocumentsHost interface
/// Provides connectione between documents and the main widnow
/// </summary>
public interface IDocumentsHost
{
bool CreateNewDocumentOnChange{get;}
bool RememberOnChange{get;}
bool NewDocument(Bitmap image);
bool NewDocument(ComplexImage image);
Bitmap GetImage(object sender, String text, Size size, PixelFormat format);
}Everyone is welcome to discuss with me