Entré en contacto con el procesamiento de imágenes digitales por primera vez en la escuela secundaria. En ese momento, PHOTOSHOP todavía era 4.0. Quizás debido a mis ideas preconcebidas, todavía no tengo interés en aprender 3DMAX y cosas similares. El salto de 2D a 3D probablemente no sea nada importante. que ver conmigo, y no puedo soportar dejarle ese cuadrado al salario alto de Cubic.... Jaja.
Cuando estaba en la universidad, escribí algunos programas de procesamiento de imágenes con mis compañeros de clase. En ese momento, la programación todavía era muy informal y todo lo que pensaba era cómo implementarla. Ahora parece que la tecnología real es la capacidad de comprender el conjunto. situación, en lugar de la magia de un destello de inspiración. Entré en contacto con algunos programas de procesamiento de imágenes extranjeros hace unos días. Aquí hay un resumen. Calculo que no estudiaré específicamente el procesamiento de imágenes en el futuro.
Un ex compañero de clase me dijo una vez que .net no tiene punteros. Ahora muchos cursos de formación parecen decir lo mismo. De hecho, esto es una falacia. Es solo que el marco no recomienda el uso de punteros, especialmente en operaciones entre procesos como servicios web y comunicación remota, los punteros no son seguros. Pero todos los que han utilizado TC deberían quedar profundamente impresionados por la eficiencia de ejecución de los punteros. Ante la demanda de cálculo por lotes de datos a gran escala, los punteros son la única opción. Por lo tanto, .net retiene inteligentemente el puntero y lo incluye en el conjunto de métodos inseguros. El uso razonable de punteros mejorará en gran medida la eficiencia de la ejecución. He realizado experimentos para realizar operaciones punto por punto en una imagen de 640 * 480. Las operaciones sin puntero tardan varios minutos, mientras que las operaciones con puntero se completan casi instantáneamente. Así que no tengas miedo de utilizar sugerencias.
El segundo son las matemáticas. Aconsejo a todos que lo entiendan antes de escribir un programa. La clase de matemáticas no es una broma... Si no lo entiendes, tienes que quedarte en la cama y pensar en ello una y otra vez. que las matemáticas pueden prevenir la enfermedad de Alzheimer.
Más cerca de casa, hablemos de la estructura del programa:
Proyectos de imágenes (filtros, texturas, modos de imagen)
Proyecto matemático (algoritmo, límites, personalización y métodos de cálculo comunes)
Proyecto del programa principal
Déjame darte un ejemplo. También haré algo de programación orientada a la
interfaz pública IFilter
.
{
Aplicar mapa de bits (img de mapa de bits);
}
Para ilustrar, también haré programación orientada a interfaz. Cada filtro debe implementar esta interfaz. La definición de interfaz también incluye una definición de excusa que no genera imágenes reales, sino que solo genera objetos binarios, que no se considerarán aquí por el momento. ser. Tome el filtro de color invertido como ejemplo.
Aplicar mapa de bits público (Mapa de bits srcImg)
{
//obtener el tamaño de la imagen de origen
int ancho = srcImg.Width;
int altura = srcImg.Height;
PixelFormat fmt = (srcImg.PixelFormat == PixelFormat.Format8bppIndexed)?
PixelFormat.Format8bppIndexed: PixelFormat.Format24bppRgb
// bloquear datos de mapa de bits de origen
;
BitmapData srcData = srcImg.LockBits(
nuevo Rectángulo(0, 0, ancho, alto),
ImageLockMode.ReadOnly, fmt);
// crear nueva imagen
¿Mapa de bits dstImg = (fmt == PixelFormat.Format8bppIndexed)?
AForge.Imaging.Image.CreateGrayscaleImage (ancho, alto):
nuevo mapa de bits (ancho, alto, fmt);
// bloquear datos del mapa de bits de destino);
BitmapData dstData = dstImg.LockBits(
nuevo Rectángulo(0, 0, ancho, alto),
ImageLockMode.ReadWrite, fmt);
// copiar imagen
srcData.Stride
* height );
ProcessFilter( dstData, fmt );
// desbloquear ambas imágenes
dstImg.UnlockBits(dstData);
srcImg.UnlockBits( srcData );
devuelve dstImg;
}
Es la entrada al método de filtro y completa el trabajo preparatorio antes del procesamiento. ProcessFilter también llama al método ProcessFilter común en cada clase de filtro, y este ProcessFilter es la clave para realizar la función, operación punto por punto o operación de plantilla.
// Procesar el filtro
ProcessFilter privado inseguro y vacío (datos de BitmapData, PixelFormat fmt)
{
int ancho = datos.Ancho;
int altura = datos.Altura;
int lineSize = ancho * ((fmt == PixelFormat.Format8bppIndexed)? 1: 3);
int offset = data.Stride - lineSize;
// haz el trabajo
byte * ptr = (byte *) data.Scan0.ToPointer( )
;
para (int y = 0; y < altura; y++)
{
para ( int x = 0; x < tamaño de línea; x++, ptr ++ )
{
// invierte cada píxel
*ptr = (byte)( 255 - *ptr );
}
ptr += compensación;
}
}
Entre ellos, Format8bppIndexed no necesita preocuparse demasiado. Personalmente, creo que no es necesario considerar la cuestión de la compatibilidad con él en las primeras etapas del diseño.
Ahora hablemos de texturas. No he pensado mucho en esto antes, pero descubrí que a los extranjeros les gusta jugar con ellas porque las texturas tienen más espacio para jugar en matemáticas, no sé cómo se les ocurrió. Es difícil basarse en la imaginación. Tal vez uno de ellos descubrió este método mientras jugaba con un software de modelado matemático, por lo que el profesor de matemáticas de alto nivel se negó a aceptar a nadie y jugó muy duro con el algoritmo. De todos modos, creo que eso es lo que pasó. . .
interfaz pública ITextureGenerator
{
/**//// <resumen>
/// Generar textura
/// </summary>
float[,] Generar( int ancho, int alto
/**//// <resumen>
);
/// Restablecer - regenerar números aleatorios internos
/// </summary>
Restablecer vacío ();
}
Esta es la interfaz de implementación del generador de texturas. Para garantizar que la textura sea diferente cada vez, se deben actualizar números aleatorios como parámetros de cálculo.
ruido privado Math.PerlinNoise = nuevo Math.PerlinNoise (1.0/32, 0.05, 0.5, 8);
Lograr detalles de textura también requiere ruido, por lo que es necesario implementar muchos tipos de ruido.
//Constructores
Textura de madera pública (): esto (12.0) {}
Textura de madera pública (anillos dobles)
{
this.rings = anillos;
Reiniciar( );
}
El constructor proporciona la configuración del valor predeterminado, que es el límite del tamaño de textura de la unidad.
//Generar textura
flotador público [,] Generar (int ancho, int alto)
{
flotador[,] textura = nuevo flotador[alto, ancho];
int w2 = ancho / 2;
int h2 = altura / 2;
para (int y = 0; y < altura; y++)
{
para (int x = 0; x < ancho; x++)
{
doble xv = (doble) ( x - w2 ) / ancho;
doble yv = (doble) ( y - h2 ) / altura
;
Math.Max( 0.0f, Math.Min( 1.0f, (flotante)
Matemáticas.Abs( Matemáticas.Sin(
( Math.Sqrt( xv * xv + yv * yv ) + ruido.Función2D( x + r, y + r ) )
* Math.PI * 2 * anillos
))
));
}
}
textura de retorno;
}
Eso es todo. . . El resultado de mis malas matemáticas. Ni siquiera sé de qué está hablando. Elijo el valor máximo del valor mínimo. Los algoritmos no son difíciles de encontrar, la clave es ver cómo la estructura los integra.
reinicio público vacío ()
{
r = rand.Siguiente(5000);
}No olvides este número aleatorio, la imagen del número también necesita belleza natural.
El concepto orientado a objetos en ingeniería matemática no es fácil de implementar. Eche un vistazo a PerlinNoise para inspirar a otros.
público PerlinNoise (doble frecuencia de inicio, doble amplitud de inicio, doble persistencia, int octavas)
{
this.initFrequency = initFrequency;
this.initAmplitude = initAmplitude;
this.persistance = persistencia;
this.octavas = octavas;
}
Primero, necesitamos recopilar datos, porque el procesamiento de imágenes implica situaciones unidimensionales y bidimensionales, por lo que los métodos subyacentes como el ruido deben proporcionar métodos correspondientes para las dos situaciones.
/**//// <resumen>
/// Función de ruido Perlin 1-D
/// </summary>
Función doble pública (doble x)
{
doble frecuencia = initFrequency;
doble amplitud = initAmplitud;
doble suma = 0;
// octavas
para (int i = 0; i < octavas; i++)
{
suma += SmoothedNoise( x * frecuencia ) * amplitud
frecuencia *= 2;
amplitud *= persistencia;
}
suma de devolución;
}
/**//// <resumen>
/// Función de ruido Perlin 2-D
/// </summary>
Función doble pública 2D (doble x, doble y)
{
doble frecuencia = initFrequency;
doble amplitud = initAmplitud;
doble suma = 0;
// octavas
para (int i = 0; i < octavas; i++)
{
suma += SmoothedNoise( x * frecuencia, y * frecuencia ) * amplitud
frecuencia *= 2;
amplitud *= persistencia;
}
suma de devolución;
}
¿Cuál es la diferencia entre unidimensional y bidimensional? Cuando estaba en la escuela secundaria, aprendí que el movimiento de las líneas genera superficies. Cuando estaba en la universidad, aprendí que las líneas cíclicas y cambiantes pueden representar superficies. reconocimiento y afilado de bordes, también descubrí que subestimé la línea antes, pero en realidad es solo un parámetro menos que la superficie.
/**//// <resumen>
/// Función de ruido ordinario
/// </summary>
Ruido doble protegido (int x)
{
int n = ( x << 13 ) ^ x;
retorno ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7ffffff ) / 1073741824.0 );
}
Ruido doble protegido (int x, int y)
{
int norte = x + y * 57;
n = (n << 13) ^ n;
retorno (1.0 - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7ffffff) / 1073741824.0);
} Una vez más prueba el párrafo anterior. Personalmente, creo que este x+y*57 tiene un significado de proyección. Obtenga el valor de ruido correspondiente. Pero el ruido no se puede utilizar directamente.
/**//// <resumen>
/// Ruido suavizado
/// </summary>
SmoothedNoise doble protegido (doble x)
{
intxInt = (int)x;
double xFrac = x - xInt;
return CosineInterpolate( Ruido( xInt ), Ruido( xInt + 1 ), xFrac );
}
SmoothedNoise doble protegido (doble x, doble y)
{
intxInt = (int)x;
int yInt = (int)y;
doble xFrac = x - xInt;
double yFrac = y - yInt;
// obtiene cuatro valores de ruido
doble x0y0 = Ruido( xInt , yInt );
doble x1y0 = Ruido( xInt + 1, yInt );
doble x0y1 = Ruido( xInt , yInt + 1 );
double x1y1 = Ruido( xInt + 1, yInt + 1)
// x interpolación
;
doble v1 = CosenoInterpolar( x0y0, x1y0, xFrac );
doble v2 = CosenoInterpolar( x0y1, x1y1, xFrac );
//yinterpolación
devuelve CosineInterpolate( v1, v2, yFrac );
} El ruido suave, cuyo nombre parece un poco incongruente, funciona mediante interpolación de coseno en lugar de coseno discreto. ¿Qué es la interpolación coseno? /**//// <resumen>
/// Interpolación de coseno
/// </summary>
doble coseno interpolado protegido (doble x1, doble x2, doble a)
{
doble f = ( 1 - Math.Cos( a * Math.PI ) ) * 0,5;
devolver x1 * ( 1 - f ) + x2 * f;
} Eso es todo, hay algunas cosas que es suficiente que el maestro sepa, y tú puedes hacerlas en consecuencia. ¿Por qué? Porque es posible que no lo entiendas durante tu vida, pero alguien lo descubrirá naturalmente y el conocimiento aún se está transmitiendo. Así como no es necesario conocer la proporción de ácido del estómago para disfrutar de comidas deliciosas y picantes, tampoco es necesario que las generaciones futuras se preocupen por la indigestión. No hace falta forzar algunas cosas, es un poco negativo, jaja.
La imagen no es difícil, siempre y cuando comprendas la relación de llamada, y creo que una forma flotante como Photoshop es la mejor opción, // Invertir imagen
vacío privado invertColorFiltersItem_Click (remitente del objeto, System.EventArgs e)
{
AplicarFilter(nueva Invertir());
}
// Aplicar filtro en la imagen
vacío privado ApplyFilter (filtro IFilter)
{
intentar
{
// establece el cursor de espera
this.Cursor = Cursors.WaitCursor;
// aplica filtro a la imagen
Mapa de bits nuevaImagen = filtro.Aplicar(imagen);
si (host.CreateNewDocumentOnChange)
{
//abre una nueva imagen en un nuevo documento
host.NuevoDocumento(nuevaImagen);
}
demás
{
si (host.RememberOnChange)
{
// copia de seguridad de la imagen actual
si (copia de seguridad! = nulo)
copia de seguridad.Dispose();
copia de seguridad = imagen;
}
demás
{
// liberar imagen actual
imagen.Dispose();
}
imagen = nuevaImagen
// actualizar
ActualizarNuevaImagen();
}
}
captura (ArgumentoException)
{
MessageBox.Show("El filtro seleccionado no se puede aplicar a la imagen", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finalmente
{
// restaurar cursor
this.Cursor = Cursores.Default;
}
}Si la llamada es fluida, ninguna cantidad de código resultará confusa. Para los principiantes, haga un buen uso de las regiones.
También existe el concepto de DocumentsHost. Es muy conveniente usarlo para alojar archivos de imágenes y conectar imágenes y formularios /**//// <summary>.
/// Interfaz IDocumentsHost
/// Proporciona conexión entre los documentos y la ventana principal.
/// </summary>
interfaz pública IDocumentsHost
{
bool CreateNewDocumentOnChange{get;}
bool RememberOnChange{get;}
bool NewDocument(imagen de mapa de bits);
bool NewDocument (imagen de imagen compleja
GetImage (remitente del objeto, texto de cadena, tamaño, formato PixelFormat);
}Todos son bienvenidos a discutir conmigo