Mit der digitalen Bildbearbeitung bin ich zum ersten Mal in Berührung gekommen. Damals war PHOTOSHOP noch 4.0. Vielleicht habe ich aufgrund meiner Vorurteile noch kein Interesse daran, 3DMAX und dergleichen zu lernen Mach mit mir, und ich kann es nicht ertragen, diesen Platz dem hohen kubischen Gehalt zu überlassen ... Haha.
Als ich auf dem College war, habe ich mit meinen Klassenkameraden einige Bildverarbeitungsprogramme geschrieben. Damals war das Programmieren noch sehr beiläufig und ich dachte nur darüber nach, wie ich es umsetzen sollte. Jetzt scheint es, dass echte Technologie die Fähigkeit ist, das Ganze zu erfassen Situation, und nicht die Magie eines Geistesblitzes. Ich bin vor ein paar Tagen mit einigen ausländischen Bildverarbeitungsprogrammen in Kontakt gekommen. Hier ist eine Zusammenfassung. Ich schätze, dass ich mich in Zukunft nicht speziell mit Bildverarbeitung befassen werde.
Ein ehemaliger Klassenkamerad hat mir einmal gesagt, dass .net keine Hinweise hat. Nun scheinen viele Schulungen dasselbe zu sagen. Es ist nur so, dass das Framework die Verwendung von Zeigern nicht empfiehlt, insbesondere bei prozessübergreifenden Vorgängen wie Webservise und Remoting sind Zeiger unsicher. Aber jeder, der TC verwendet hat, sollte von der Ausführungseffizienz von Zeigern tief beeindruckt sein. Angesichts der Nachfrage nach Stapelberechnungen großer Datenmengen sind Zeiger die einzige Wahl. Daher behält .net den Zeiger geschickt bei und listet ihn im unsicheren Methodensatz auf. Eine sinnvolle Verwendung von Zeigern wird die Ausführungseffizienz erheblich verbessern. Ich habe Experimente durchgeführt, um Punkt-für-Punkt-Operationen an einem 640*480-Bild durchzuführen, während Zeigeroperationen fast sofort abgeschlossen sind. Scheuen Sie sich also nicht davor, Zeiger zu verwenden.
Das zweite ist Mathematik. Ich rate jedem, es zu verstehen, bevor man ein Programm schreibt. Wenn man es nicht versteht, muss man immer wieder darüber nachdenken dass Mathematik die Alzheimer-Krankheit verhindern kann.
Lassen Sie uns näher zu Hause über die Programmstruktur sprechen:
Imaging-Projekte (Filter, Texturen, Bildmodi)
Mathematikprojekt (Algorithmus, Grenze, Anpassung und gängige Berechnungsmethoden)
Hauptprogrammprojekt
Lassen Sie mich Ihnen ein Beispiel geben. Ich werde auch einige schnittstellenorientierte
öffentliche Schnittstellen-IFilter
durchführen
{
Bitmap Apply( Bitmap img );
}
Zur Veranschaulichung werde ich auch eine schnittstellenorientierte Programmierung durchführen. Die Schnittstellendefinition muss auch eine Entschuldigungsdefinition enthalten, die keine tatsächlichen Bilder, sondern nur binäre Objekte generiert, die hier zunächst nicht berücksichtigt werden Sein. Nehmen Sie als Beispiel den invertierten Farbfilter
öffentliche Bitmap Apply( Bitmap srcImg )
{
//Quellbildgröße ermitteln
int width = srcImg.Width;
int height = srcImg.Height;
PixelFormat fmt = ( srcImg.PixelFormat == PixelFormat.Format8bppIndexed ) ?
PixelFormat.Format8bppIndexed: PixelFormat.Format24bppRgb;
// Quell-Bitmap-Daten sperren
BitmapData srcData = srcImg.LockBits(
neues Rechteck (0, 0, Breite, Höhe),
ImageLockMode.ReadOnly, fmt );
// neues Bild erstellen
Bitmap dstImg = (fmt == PixelFormat.Format8bppIndexed) ?
AForge.Imaging.Image.CreateGrayscaleImage( width, height ):
new Bitmap( width, height, fmt );
// Ziel-Bitmap-Daten sperren
BitmapData dstData = dstImg.LockBits(
neues Rechteck (0, 0, Breite, Höhe),
ImageLockMode.ReadWrite, fmt );
// Bild kopieren
Win32.memcpy( dstData.Scan0, srcData.Scan0, srcData.Stride * height );
// den Filter verarbeiten
ProcessFilter( dstData, fmt );
// beide Bilder entsperren
dstImg.UnlockBits(dstData);
srcImg.UnlockBits( srcData );
return dstImg;
}
Es ist der Eingang zur Filtermethode und vervollständigt die vorbereitende Arbeit vor der Verarbeitung. ProcessFilter ruft auch die in jeder Filterklasse übliche ProcessFilter-Methode auf. Dieser ProcessFilter ist der Schlüssel zur Realisierung der Funktion, Punkt-für-Punkt-Operation oder Vorlagenoperation.
// Den Filter verarbeiten
privater unsicherer void ProcessFilter( BitmapData-Daten, PixelFormat fmt )
{
int width = data.Width;
int height = data.Height;
int lineSize = width * ( (fmt == PixelFormat.Format8bppIndexed) ? 1 : 3 );
int offset = data.Stride - lineSize;
// den Job erledigen
byte * ptr = (byte *) data.Scan0.ToPointer( );
// invertieren
for ( int y = 0; y < height; y++ )
{
for ( int x = 0; x < lineSize; x++, ptr ++ )
{
// jedes Pixel umkehren
*ptr = (byte)( 255 - *ptr );
}
ptr += Offset;
}
}
Unter anderem muss man sich bei Format8bppIndexed keine allzu großen Sorgen machen. Ich persönlich denke, dass es nicht notwendig ist, die Frage der Kompatibilität damit in den frühen Phasen des Entwurfs zu berücksichtigen.
Lassen Sie uns jetzt über Texturen sprechen. Ich habe vorher nicht viel darüber nachgedacht, aber ich habe festgestellt, dass Ausländer gerne damit spielen, weil Texturen in der Mathematik mehr Spielraum haben Es ist schwierig, auf der Grundlage der Vorstellungskraft wahr zu sein. Vielleicht hat einer von ihnen diese Methode beim Spielen einer mathematischen Modellierungssoftware entdeckt, also weigerte sich der hochrangige Mathematiklehrer, irgendjemanden zu akzeptieren, und spielte sehr hart mit dem Algorithmus. Jedenfalls denke ich, dass das passiert ist. . .
öffentliche Schnittstelle ITextureGenerator
{
/**//// <Zusammenfassung>
/// Textur erzeugen
/// </summary>
float[,] Generate( int width, int height );
/**//// <summary>
/// Zurücksetzen – interne Zufallszahlen neu generieren
/// </summary>
void Reset( );
}
Dies ist die Implementierungsschnittstelle des Texturgenerators. Um sicherzustellen, dass die Textur jedes Mal anders ist, müssen Zufallszahlen als Berechnungsparameter aktualisiert werden.
private Math.PerlinNoise Noise = new Math.PerlinNoise( 1.0 / 32, 0.05, 0.5, 8 );
Um Texturdetails zu erzielen, ist auch Rauschen erforderlich, daher müssen viele Arten von Rauschen implementiert werden.
//Konstrukteure
public WoodTexture( ) : this( 12.0 ) { }
öffentliche WoodTexture (Doppelringe)
{
this.rings = Ringe;
Zurücksetzen( );
}
Der Konstruktor stellt die Einstellung des Standardwerts bereit, der die Grenze der Einheitstexturgröße darstellt.
//Textur generieren
public float[,] Generate( int width, int height )
{
float[,] Textur = new float[height, width];
int w2 = Breite / 2;
int h2 = Höhe / 2;
for ( int y = 0; y < height; y++ )
{
for (int x = 0; x < width; x++)
{
double xv = (double) ( x - w2 ) / Breite;
double yv = (double) ( y - h2 ) / height;
Textur[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 * Ringe
))
));
}
}
Rückkehrtextur;
}
Das ist es. . . Das Ergebnis meiner schlechten Mathematik. Ich weiß nicht einmal, wovon sie spricht. Ich wähle den Maximalwert aus dem Minimalwert. Algorithmen sind nicht schwer zu finden. Der Schlüssel liegt darin, zu sehen, wie die Struktur sie integriert.
öffentliche Leere Zurücksetzen( )
{
r = rand.Next( 5000 );
}Vergessen Sie diese Zufallszahl nicht, das Bild der Zahl braucht auch natürliche Schönheit.
Das objektorientierte Konzept in der Mathematiktechnik ist nicht einfach umzusetzen. Werfen Sie einen Blick auf PerlinNoise, um andere zu inspirieren.
public PerlinNoise( double initFrequency, double initAmplitude, double persistance, int octaves )
{
this.initFrequency = initFrequency;
this.initAmplitude = initAmplitude;
this.persistance = Persistenz;
this.octaves = Oktaven;
}
Zuerst müssen wir Daten sammeln, da die Bildverarbeitung eindimensionale und zweidimensionale Situationen umfasst, sodass zugrunde liegende Methoden wie Rauschen entsprechende Methoden für die beiden Situationen bereitstellen müssen.
/**//// <Zusammenfassung>
/// 1-D-Perlin-Rauschenfunktion
/// </summary>
öffentliche Double-Funktion (double x)
{
doppelte Frequenz = initFrequency;
doppelte Amplitude = initAmplitude;
Doppelsumme = 0;
// Oktaven
for (int i = 0; i < Oktaven; i++)
{
sum += SmoothedNoise( x * Frequenz ) * Amplitude
*= 2;
Amplitude *= Persistenz;
}
Rückgabesumme;
}
/**//// <Zusammenfassung>
/// 2-D-Perlin-Rauschenfunktion
/// </summary>
öffentliche doppelte Funktion2D (doppeltes x, doppeltes y)
{
doppelte Frequenz = initFrequency;
doppelte Amplitude = initAmplitude;
Doppelsumme = 0;
// Oktaven
for (int i = 0; i < Oktaven; i++)
{
Summe += SmoothedNoise( x * Frequenz, y * Frequenz) * Amplitude
*= 2;
Amplitude *= Persistenz;
}
Rückgabesumme;
}
Was ist der Unterschied zwischen eindimensional und zweidimensional? Als ich in der Mittelschule war, habe ich gelernt, dass die Bewegung von Linien Flächen erzeugen kann Bei der Kantenerkennung und -schärfung habe ich auch festgestellt, dass ich die Linie zuvor unterschätzt habe, aber tatsächlich ist sie nur ein Parameter weniger als die Oberfläche.
/**//// <Zusammenfassung>
/// Gewöhnliche Rauschfunktion
/// </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 );
} Dies beweist einmal mehr den vorherigen Absatz. Ich persönlich habe das Gefühl, dass x+y*57 eine gewisse Projektionsbedeutung hat. Ermitteln Sie den entsprechenden Rauschwert. Aber Lärm kann nicht direkt genutzt werden.
/**//// <Zusammenfassung>
/// Geglättetes Rauschen
/// </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;
// vier Rauschwerte erhalten
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 );
} Glattes Rauschen, dessen Bezeichnung etwas unpassend erscheint, funktioniert eher durch Kosinusinterpolation als durch diskrete Kosinusinterpolation. Was ist Kosinusinterpolation? /**//// <Zusammenfassung>
/// Kosinusinterpolation
/// </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;
}Das ist es, es gibt einige Dinge, die der Meister wissen muss, und Sie können sie einfach entsprechend tun. Warum? Weil Sie es vielleicht in Ihrem Leben nicht verstehen, aber irgendjemand wird es auf natürliche Weise herausfinden, und das Wissen wird immer noch weitergegeben. Genauso wie Sie Ihr Magensäureverhältnis nicht kennen müssen, um köstliche und scharfe Speisen zu genießen, müssen Sie sich für zukünftige Generationen keine Sorgen über Verdauungsstörungen machen. Manche Dinge muss man nicht erzwingen, das ist ein bisschen negativ, haha.
Das Bild ist nicht schwierig, solange man die aufrufende Beziehung versteht, und eine schwebende Form wie Photoshop ist meiner Meinung nach die beste Wahl: // Bild umkehren
private void invertColorFiltersItem_Click(object sender, System.EventArgs e)
{
ApplyFilter(new Invert());
}
// Filter auf das Bild anwenden
private void ApplyFilter(IFilter-Filter)
{
versuchen
{
// Wartecursor setzen
this.Cursor = Cursors.WaitCursor;
// Filter auf das Bild anwenden
Bitmap newImage = filter.Apply(image);
if (host.CreateNewDocumentOnChange)
{
// Neues Bild in neuem Dokument öffnen
host.NewDocument(newImage);
}
anders
{
if (host.RememberOnChange)
{
// Aktuelles Image sichern
if (backup != null)
backup.Dispose();
backup = image;
}
anders
{
// Aktuelles Bild freigeben
image.Dispose();
}
image = newImage;
// aktualisieren
UpdateNewImage();
}
}
Catch(ArgumentException)
{
MessageBox.Show("Ausgewählter Filter kann nicht auf das Bild angewendet werden", "Fehler", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
Endlich
{
// Cursor wiederherstellen
this.Cursor = Cursors.Default;
}
}Wenn der Aufruf reibungslos verläuft, fühlt sich keine Menge Code chaotisch an. Für Anfänger sollten Sie Regionen gut nutzen.
Es gibt auch das Konzept von DocumentsHost. Es ist sehr praktisch, es zum Hosten von Bilddateien und zum Verbinden von Bildern und Formularen zu verwenden
/// IDocumentsHost-Schnittstelle
/// Stellt eine Verbindung zwischen Dokumenten und dem Hauptfenster her
/// </summary>
öffentliche Schnittstelle 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);
}Jeder ist herzlich eingeladen, mit mir zu diskutieren