J'ai découvert le traitement d'images numériques pour la première fois au lycée. À cette époque, PHOTOSHOP était encore 4.0. Peut-être à cause de mes idées préconçues, je n'ai toujours aucun intérêt à apprendre le 3DMAX et autres. Le passage de la 2D à la 3D n'est probablement pas une mince affaire. faire avec moi, et je ne peux pas supporter de laisser cette place au salaire élevé de Cubic.... Haha.
Quand j'étais à l'université, j'écrivais des programmes de traitement d'images avec mes camarades de classe. À cette époque, la programmation était encore très informelle, et je ne pensais qu'à comment la mettre en œuvre. Il semble maintenant que la vraie technologie soit la capacité d'appréhender l'ensemble. situation, plutôt que la magie d’un éclair d’inspiration. Je suis entré en contact avec des programmes de traitement d'images étrangers il y a quelques jours. Voici un résumé. J'estime que je n'étudierai pas spécifiquement le traitement d'images à l'avenir.
Un ancien camarade de classe m'a dit un jour que .net n'avait pas de pointeurs. Aujourd'hui, de nombreux cours de formation semblent dire la même chose. En fait, c'est une erreur. C'est juste que le framework ne recommande pas l'utilisation de pointeurs, en particulier dans les opérations inter-processus telles que le service Web et le remoting, les pointeurs ne sont pas sûrs. Mais tous ceux qui ont utilisé TC devraient être profondément impressionnés par l'efficacité d'exécution des pointeurs. Face à la demande de calcul par lots de données à grande échelle, les pointeurs sont le seul choix. Par conséquent, .net conserve intelligemment le pointeur et le répertorie dans l’ensemble de méthodes non sécurisées. Une utilisation raisonnable des pointeurs améliorera considérablement l'efficacité de l'exécution. J'ai fait des expériences pour effectuer des opérations point par point sur une image 640*480. Les opérations sans pointeur prennent plusieurs minutes, tandis que les opérations avec pointeur sont terminées presque instantanément. N'ayez donc pas peur d'utiliser des pointeurs.
La seconde concerne les mathématiques. Je conseille à tout le monde de la comprendre avant d'écrire un programme. Les cours de mathématiques ne sont pas une blague... Si vous ne comprenez pas, vous devez vous allonger au lit et y réfléchir encore et encore. que les mathématiques peuvent prévenir la maladie d'Alzheimer.
Plus près de chez nous, parlons de la structure du programme :
Projets d'imagerie (filtres, textures, modes d'image)
Projet mathématique (algorithme, limite, personnalisation et méthodes de calcul courantes)
Projet de programme principal
Laissez-moi vous donner un exemple. Je ferai également de la programmation orientée
interface IFilter publique
.
{
Bitmap Appliquer( Bitmap img );
}
Pour illustrer, je ferai également de la programmation orientée interface. Chaque filtre doit implémenter cette interface. La définition de l'interface inclut également une définition d'excuse qui ne génère pas d'images réelles, mais génère uniquement des objets binaires, qui ne seront pas considérés ici pour le moment. être. Prenons l'exemple du filtre de couleur inversé
public Bitmap Appliquer ( Bitmap srcImg )
{
//récupère la taille de l'image source
int width = srcImg.Width;
int hauteur = srcImg.Hauteur ;
PixelFormat fmt = ( srcImg.PixelFormat == PixelFormat.Format8bppIndexed ) ?
PixelFormat.Format8bppIndexed : PixelFormat.Format24bppRgb;
// verrouille les données bitmap source
BitmapData srcData = srcImg.LockBits(
nouveau Rectangle(0, 0, largeur, hauteur),
ImageLockMode.ReadOnly, fmt );
// crée une nouvelle image
Bitmap dstImg = ( fmt == PixelFormat.Format8bppIndexed ) ?
AForge.Imaging.Image.CreateGrayscaleImage(largeur, hauteur):
new Bitmap( width, height, fmt );
// verrouille les données bitmap de destination
BitmapData dstData = dstImg.LockBits(
nouveau Rectangle(0, 0, largeur, hauteur),
ImageLockMode.ReadWrite, fmt );
// copier l'image
Win32.memcpy( dstData.Scan0, srcData.Scan0, srcData.Stride * height );
// traite le filtre
ProcessFilter( dstData, fmt );
// débloque les deux images
dstImg.UnlockBits(dstData);
srcImg.UnlockBits( srcData );
retourner dstImg;
}
C'est l'entrée dans la méthode de filtrage et termine le travail préparatoire avant le traitement. ProcessFilter appelle également la méthode ProcessFilter commune à chaque classe de filtre, et ce ProcessFilter est la clé pour réaliser la fonction, l'opération point par point ou l'opération de modèle.
// Traite le filtre
ProcessFilter privé non sécurisé (données BitmapData, PixelFormat fmt)
{
int width = data.Width;
int hauteur = data.Height;
int lineSize = largeur * ( ( fmt == PixelFormat.Format8bppIndexed ) ? 1 : 3 );
int offset = data.Stride - lineSize;
// fait le travail
*
) data.Scan0.ToPointer( );
pour ( int y = 0; y < hauteur; y++ )
{
pour ( int x = 0; x < lineSize; x++, ptr ++ )
{
// inverse chaque pixel
*ptr = (octet)( 255 - *ptr );
}
ptr += décalage ;
}
}
Parmi eux, Format8bppIndexed n'a pas besoin d'être trop préoccupé. Je pense personnellement qu'il n'est pas nécessaire de considérer la question de la compatibilité avec lui dès les premiers stades de la conception.
Parlons maintenant des textures. Je n'y ai pas beaucoup réfléchi auparavant, mais j'ai découvert que les étrangers aiment jouer avec parce que les textures ont plus de place pour jouer en mathématiques. être vrai en se basant sur l'imagination. C'est difficile. Peut-être que l'un d'eux a découvert cette méthode en jouant à un logiciel de modélisation mathématique, alors le professeur de mathématiques de haut niveau a refusé d'accepter qui que ce soit et a joué très fort avec l'algorithme. Quoi qu'il en soit, je pense que c'est ce qui s'est passé. . .
interface publique ITextureGenerator
{
/**//// <résumé>
/// Générer une texture
/// </summary>
float[,] Generate( int width, int height );
/**//// <résumé>
/// Réinitialiser - régénérer les nombres aléatoires internes
/// </summary>
void Réinitialiser( );
}
Il s'agit de l'interface d'implémentation du générateur de texture. Afin de garantir que la texture soit différente à chaque fois, des nombres aléatoires doivent être mis à jour comme paramètres de calcul.
bruit privé Math.PerlinNoise = new Math.PerlinNoise( 1.0 / 32, 0.05, 0.5, 8 );
L'obtention de détails de texture nécessite également du bruit, c'est pourquoi de nombreux types de bruit doivent être implémentés.
//Constructeurs
public WoodTexture( ) : ceci( 12.0 ) { }
WoodTexture publique (anneaux doubles)
{
this.rings = anneaux;
Réinitialiser( );
}
Le constructeur fournit le réglage de la valeur par défaut, qui est la limite de la taille de la texture unitaire.
//Générer une texture
public float[,] Générer( int largeur, int hauteur )
{
float[,] texture = nouveau float[hauteur, largeur];
int w2 = largeur / 2 ;
int h2 = hauteur / 2;
pour ( int y = 0; y < hauteur; y++ )
{
pour (int x = 0; x < largeur; x++)
{
double xv = (double) ( x - w2 ) / largeur;
double yv = (double) ( y - h2 ) / hauteur;
texture[y, x] =
Math.Max( 0.0f, Math.Min( 1.0f, (flottant)
Math.Abs( Math.Sin(
( Math.Sqrt( xv * xv + yv * yv ) + noise.Function2D( x + r, y + r ) )
* Math.PI * 2 * anneaux
))
));
}
}
retourner la texture ;
}
C'est ça. . . Le résultat de mes mauvais calculs. Je ne sais même pas de quoi elle parle. Je choisis la valeur maximale parmi la valeur minimale. Les algorithmes ne sont pas difficiles à trouver, l’essentiel est de voir comment la structure les intègre.
public void Réinitialiser ( )
{
r = rand.Suivant( 5000 );
}N'oubliez pas ce nombre aléatoire, l'image du nombre a aussi besoin de beauté naturelle.
Le concept orienté objet en ingénierie mathématique n'est pas facile à mettre en œuvre. Jetez un œil à PerlinNoise pour inspirer les autres.
public PerlinNoise (double initFrequency, double initAmplitude, double persistance, int octaves)
{
this.initFrequency = initFrequency;
this.initAmplitude = initAmplitude;
this.persistance = persistance ;
this.octaves = octaves ;
}
Premièrement, nous devons collecter des données, car le traitement des images implique des situations unidimensionnelles et bidimensionnelles. Les méthodes sous-jacentes telles que le bruit doivent donc fournir des méthodes correspondantes aux deux situations.
/**//// <résumé>
/// Fonction de bruit Perlin 1-D
/// </summary>
fonction double publique (double x)
{
double fréquence = initFrequency ;
double amplitude = initAmplitude ;
double somme = 0 ;
// octaves
pour (int i = 0; i < octaves; i++)
{
somme += SmoothedNoise( x * fréquence ) *
fréquence *= 2 ;
amplitude *= persistance ;
}
retourner la somme ;
}
/**//// <résumé>
/// Fonction de bruit Perlin 2D
/// </summary>
fonction double publique2D (double x, double y)
{
double fréquence = initFrequency ;
double amplitude = initAmplitude ;
double somme = 0 ;
// octaves
pour (int i = 0; i < octaves; i++)
{
somme + = SmoothedNoise (x * fréquence, y * fréquence) * amplitude
* = 2 ;
amplitude *= persistance ;
}
retourner la somme ;
}
Quelle est la différence entre unidimensionnel et bidimensionnel ? Quand j'étais au collège, j'ai appris que le mouvement des lignes génère des surfaces. Quand j'étais au collège, j'ai appris que les lignes cycliques et changeantes peuvent représenter des surfaces. reconnaissance des bords et netteté, j'ai également constaté que j'avais sous-estimé la ligne auparavant, mais en fait ce n'est qu'un paramètre de moins que la surface.
/**//// <résumé>
/// Fonction de bruit ordinaire
/// </summary>
protégé double bruit (int x)
{
int n = ( x << 13 ) ^ x;
return ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7fffffff ) / 1073741824.0 );
}
double bruit protégé (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 );
} Cela prouve une fois de plus le paragraphe précédent. Personnellement, je pense que ce x+y*57 a un peu une signification de projection. Obtenez la valeur de bruit correspondante. Mais le bruit ne peut pas être utilisé directement.
/**//// <résumé>
/// Bruit lissé
/// </summary>
protégé double SmoothedNoise (double x)
{
int xInt = (int) x;
double xFrac = x - xInt;
return CosineInterpolate( Noise( xInt ) , Noise( xInt + 1 ), xFrac );
}
double SmoothedNoise protégé (double x, double y)
{
int xInt = (int) x;
int yInt = (int) y;
double xFrac = x - xInt ;
double yFrac = y - yInt;
// obtient quatre valeurs de bruit
double x0y0 = Bruit ( xInt , yInt );
double x1y0 = Bruit( xInt + 1, yInt );
double x0y1 = Bruit( xInt , yInt + 1 );
double x1y1 = Bruit( xInt + 1, yInt + 1) ;
// x interpolation
double v1 = CosineInterpolate( x0y0, x1y0, xFrac );
double v2 = CosineInterpolate( x0y1, x1y1, xFrac );
//yinterpolation
return CosineInterpolate( v1, v2, yFrac );
} Le bruit lisse, qui semble un peu incongru à appeler, fonctionne par interpolation cosinus plutôt que par cosinus discret. Qu’est-ce que l’interpolation cosinus ? /**//// <résumé>
/// Interpolation cosinus
/// </summary>
double protégé CosineInterpolate (double x1, double x2, double a)
{
double f = ( 1 - Math.Cos( a * Math.PI ) ) * 0,5;
retourner x1 * ( 1 - f ) + x2 * f;
}Ça y est, il y a certaines choses qu'il suffit au maître de savoir, et vous pouvez simplement les faire en conséquence. Pourquoi ? Parce que vous ne le comprendrez peut-être pas de votre vivant, mais quelqu'un le comprendra naturellement, et les connaissances sont toujours transmises. Tout comme vous n’avez pas besoin de connaître votre taux d’acide gastrique pour savourer des aliments délicieux et épicés, vous n’avez pas à vous soucier des indigestions pour les générations futures. Il ne faut pas forcer certaines choses, c’est un peu négatif, haha.
L'image n'est pas difficile, tant que vous comprenez la relation d'appel, et une forme flottante comme Photoshop est le meilleur choix, je pense, // Inverser l'image
private void invertColorFiltersItem_Click (expéditeur de l'objet, System.EventArgs e)
{
ApplyFilter(new Invert());
}
// Appliquer un filtre sur l'image
vide privé ApplyFilter (filtre IFilter)
{
essayer
{
// définit le curseur d'attente
this.Cursor = Cursors.WaitCursor;
// applique le filtre à l'image
Bitmap newImage = filter.Apply(image);
if (host.CreateNewDocumentOnChange)
{
// ouvre une nouvelle image dans un nouveau document
hôte.NewDocument(newImage);
}
autre
{
si (hôte.RememberOnChange)
{
// sauvegarde l'image actuelle
si (sauvegarde != null)
sauvegarde.Dispose();
sauvegarde = image ;
}
autre
{
// libère l'image actuelle
image.Dispose();
}
image = nouvelleImage;
// mise à jour
Mettre à jourNouvelleImage();
}
}
attraper (ArgumentException)
{
MessageBox.Show("Le filtre sélectionné ne peut pas être appliqué à l'image", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
enfin
{
// restaurer le curseur
this.Cursor = Cursors.Default;
}
}Si l'appel se déroule sans problème, aucune quantité de code ne semblera compliquée. Pour les débutants, faites bon usage des régions.
Il existe également le concept de DocumentsHost. Il est très pratique de l'utiliser pour héberger des fichiers image et connecter des images et des formulaires /**//// <summary>.
/// Interface IDocumentsHost
/// Fournit une connexion entre les documents et la fenêtre principale
/// </summary>
interface publique IDocumentsHost
{
bool CreateNewDocumentOnChange{get;}
bool RememberOnChange{get;}
bool NewDocument(Image bitmap);
bool NewDocument (image ComplexImage);
Bitmap GetImage (expéditeur de l'objet, texte de chaîne, taille, format PixelFormat);
}Tout le monde est invité à discuter avec moi