Tive contato com o processamento digital de imagens pela primeira vez no ensino médio. Naquela época, o PHOTOSHOP ainda era 4.0. Talvez por causa dos meus preconceitos, ainda não tenha interesse em aprender 3DMAX e coisas do gênero. fazer comigo, e não suporto deixar esse quadrado para o salário alto do Cubic.... Haha.
Quando eu estava na faculdade, escrevi alguns programas de processamento de imagem com meus colegas. Naquela época, a programação ainda era muito casual e tudo que eu pensava era como implementá-la. Agora parece que a tecnologia real é a capacidade de compreender o geral. situação, em vez da magia de um lampejo de inspiração. Entrei em contato com alguns programas estrangeiros de processamento de imagens há alguns dias. Aqui está um resumo. Estimo que não estudarei especificamente o processamento de imagens.
Certa vez, um ex-colega me disse que o .net não tem dicas. Agora, muitos cursos de treinamento parecem dizer a mesma coisa. Acontece que a estrutura não recomenda o uso de ponteiros, especialmente em operações entre processos, como webservise e comunicação remota, os ponteiros não são seguros. Mas todos que usaram TC devem ficar profundamente impressionados com a eficiência de execução dos ponteiros. Sob a demanda por cálculo em lote de dados em grande escala, os ponteiros são a única opção. Portanto, .net retém habilmente o ponteiro e o lista no conjunto de métodos inseguros. O uso razoável de ponteiros melhorará muito a eficiência da execução. Fiz experimentos para realizar operações ponto a ponto em uma imagem 640*480. As operações sem ponteiro levam vários minutos, enquanto as operações com ponteiro são concluídas quase instantaneamente. Portanto, não tenha medo de usar ponteiros.
A segunda é a matemática. Aconselho a todos que entendam antes de escrever um programa. Aula de matemática não é brincadeira... Se você não entende, tem que deitar na cama e pensar nisso sempre. que a matemática pode prevenir a doença de Alzheimer.
Mais perto de casa, vamos falar sobre a estrutura do programa:
Projetos de imagem (filtros, texturas, modos de imagem)
Projeto matemático (algoritmo, limite, personalização e métodos de cálculo comuns)
Projeto principal do programa
Deixe-me dar um exemplo. Também farei alguma programação orientada à interface
IFilter
.
{
Aplicar bitmap (imagem de bitmap);
}
Para ilustrar, também farei programação orientada a interface. Cada filtro deve implementar esta interface. A definição de interface também inclui uma definição de desculpa que não gera imagens reais, mas apenas gera objetos binários, que não serão considerados aqui por enquanto. ser. Veja o filtro de cor invertida como exemplo
Aplicar bitmap público (bitmap srcImg)
{
//obtém o tamanho da imagem fonte
largura interna = srcImg.Width;
altura interna = srcImg.Height;
PixelFormat fmt=(srcImg.PixelFormat== PixelFormat.Format8bppIndexed)?
PixelFormat.Format8bppIndexed : PixelFormat.Format24bppRgb;
// bloqueia dados de bitmap de origem
BitmapData srcData = srcImg.LockBits(
novo retângulo (0, 0, largura, altura),
ImageLockMode.ReadOnly, fmt );
//cria nova imagem
Bitmap dstImg = ( fmt == PixelFormat.Format8bppIndexed ) ?
AForge.Imaging.Image.CreateGrayscaleImage (largura, altura):
)
;
BitmapData dstData = dstImg.LockBits(
novo retângulo (0, 0, largura, altura),
ImageLockMode.ReadWrite, fmt);
// copia a imagem
srcData.Stride
* height );
ProcessFilter( dstData, fmt );
// desbloqueia ambas as imagens
dstImg.UnlockBits(dstData);
srcImg.UnlockBits(srcData)
;
}
É a entrada para o método de filtro e completa o trabalho preparatório antes do processamento. ProcessFilter também chama o método ProcessFilter comum em cada classe de filtro, e este ProcessFilter é a chave para realizar a função, operação ponto a ponto ou operação de modelo.
// Processa o filtro
privado inseguro void ProcessFilter (dados BitmapData, PixelFormat fmt)
{
largura interna = dados.Largura;
int altura = data.Height;
int lineSize = largura * (( fmt == PixelFormat.Format8bppIndexed ) ? 1 : 3 );
int offset = data.Stride - lineSize;
// faz o trabalho
byte * ptr = (byte *) data.Scan0.ToPointer()
// inverter
for (int y = 0; y < altura; y++ )
{
for (int x = 0; x <tamanholinha; x++, ptr ++)
{
// inverte cada pixel
*ptr = (byte)( 255 - *ptr );
}
ptr += deslocamento;
}
}
Entre eles, Format8bppIndexed não precisa se preocupar muito. Pessoalmente, acho que não é necessário considerar a questão da compatibilidade com ele nos estágios iniciais do design.
Agora vamos falar sobre texturas. Não pensei muito sobre isso antes, mas descobri que os estrangeiros gostam de brincar com isso porque as texturas têm mais espaço para brincar em matemática. ser verdade com base na imaginação É difícil Talvez um deles tenha descoberto esse método ao jogar um software de modelagem matemática, então o professor de matemática de alto nível se recusou a aceitar qualquer um e brincou muito com o algoritmo. De qualquer forma, acho que foi isso que aconteceu. . .
interface pública ITextureGenerator
{
/**//// <resumo>
/// Gera textura
/// </sumário>
(
int largura, int altura );
/// Redefinir - regenera números aleatórios internos
/// </sumário>
vazio Redefinir();
}
Esta é a interface de implementação do gerador de textura. Para garantir que a textura seja diferente a cada vez, os números aleatórios devem ser atualizados como parâmetros de cálculo.
ruído privado Math.PerlinNoise = novo Math.PerlinNoise (1,0/32, 0,05, 0,5, 8);
Obter detalhes de textura também requer ruído, portanto, muitos tipos de ruído precisam ser implementados.
//Construtores
public WoodTexture() : this( 12.0 ) { }
WoodTexture pública (anéis duplos)
{
this.rings = anéis;
Reiniciar( );
}
O construtor fornece a configuração do valor padrão, que é o limite do tamanho da textura da unidade.
//Gera textura
public float[,] Gerar(int largura, int altura)
{
float[,] textura = new float[altura, largura];
int w2 = largura/2;
int h2 = altura / 2
for (int y = 0; y < altura; y++ )
{
for (int x = 0; x <largura; x++)
{
duplo xv = (duplo) ( x - w2 ) / largura;
duplo yv = (duplo) ( y - h2 ) / altura
;
Math.Max(0,0f, Math.Min(1,0f, (float)
Math.Abs( Math.Sin(
(Math.Sqrt( xv * xv + yv * yv ) + ruído.Function2D( x + r, y + r ) )
* Math.PI * 2 * anéis
))
));
}
}
textura de retorno;
}
É isso. . . O resultado da minha matemática ruim. Nem sei do que ela está falando. Escolho o valor máximo do valor mínimo. Algoritmos não são difíceis de encontrar, o segredo é ver como a estrutura os integra.
público vazio Redefinir ( )
{
r = rand.Próximo(5000);
}Não se esqueça desse número aleatório, a imagem do número também precisa de beleza natural.
O conceito orientado a objetos na engenharia matemática não é fácil de implementar. Dê uma olhada no PerlinNoise para inspirar outros.
public PerlinNoise (duplo initFrequency, duplo initAmplitude, dupla persistência, int oitavas)
{
this.initFrequency = initFrequency;
this.initAmplitude = initAmplitude;
this.persistance = persistência;
this.octaves = oitavas;
}
Primeiro, precisamos coletar dados, porque o processamento de imagens envolve situações unidimensionais e bidimensionais, portanto, métodos subjacentes como o ruído precisam fornecer métodos correspondentes para as duas situações.
/**//// <resumo>
/// Função de ruído Perlin 1-D
/// </sumário>
função dupla pública (duplo x)
{
frequência dupla = initFrequency;
amplitude dupla = initAmplitude;
soma dupla = 0;
// oitavas
for (int i = 0; i < oitavas; i++)
{
soma += SmoothedNoise( x * frequência ) * amplitude
*= 2;
amplitude *= persistência;
}
soma de retorno;
}
/**//// <resumo>
/// Função de ruído Perlin 2-D
/// </sumário>
função dupla pública2D (duplo x, duplo y)
{
frequência dupla = initFrequency;
amplitude dupla = initAmplitude;
soma dupla = 0;
// oitavas
for (int i = 0; i < oitavas; i++)
{
soma += SmoothedNoise( x * frequência, y * frequência ) * amplitude
*= 2;
amplitude *= persistência;
}
soma de retorno;
}
Qual é a diferença entre unidimensional e bidimensional Quando eu estava no ensino médio, aprendi que o movimento das linhas gera superfícies. Quando estava na faculdade, aprendi que linhas cíclicas e mutáveis podem representar superfícies. reconhecimento e nitidez de bordas, também descobri que subestimei a linha antes, mas na verdade é apenas um parâmetro a menos que a superfície.
/**//// <resumo>
/// Função de ruído comum
/// </sumário>
ruído duplo protegido (int x)
{
int n = ( x << 13 ) ^ x;
return ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7fffffff ) / 1073741824.0 );
}
ruído duplo protegido (int x, int y)
{
int n = x + y * 57;
n = ( n << 13 ) ^ n ;
retornar ( 1,0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7fffffff ) / 1073741824.0 );
} Mais uma vez prova o parágrafo anterior. Pessoalmente, sinto que x+y*57 tem um certo significado de projeção. Obtenha o valor de ruído correspondente. Mas o ruído não pode ser usado diretamente.
/**//// <resumo>
/// Ruído suavizado
/// </sumário>
protegido duplo SmoothedNoise (duplo x)
{
int xInt = (int) x;
double xFrac = x - xInt;
return CosineInterpolate( Noise( xInt ) , Noise( xInt + 1 ), xFrac );
}
protegido duplo SmoothedNoise (duplo x, duplo y)
{
int xInt = (int) x;
int yInt = (int) y;
duplo xFrac = x - xInt;
double yFrac = y - yInt;
// obtém quatro valores de ruído
duplo x0y0 = Ruído (xInt, yInt);
duplo x1y0 = Ruído (xInt + 1, yInt);
duplo x0y1 = Ruído (xInt, yInt + 1);
double x1y1 = Noise( xInt + 1, yInt + 1)
// interpolação x
duplo v1 = CosineInterpolate( x0y0, x1y0, xFrac );
duplo v2 = CosineInterpolate( x0y1, x1y1, xFrac );
//yinterpolação
return CosineInterpolate( v1, v2, yFrac );
} O ruído suave, que parece um pouco incongruente de ser chamado, opera por interpolação de cosseno em vez de cosseno discreto. O que é interpolação de cosseno? /**//// <resumo>
/// Interpolação de cosseno
/// </sumário>
protegido duplo CosineInterpolate (duplo x1, duplo x2, duplo a)
{
duplo f = (1 - Math.Cos(a * Math.PI)) * 0,5;
retorno x1 * (1 - f) + x2 * f;
}É isso, há algumas coisas que basta que o mestre saiba, e você pode fazê-las de acordo. Por quê? Porque você pode não entender isso durante a sua vida, mas alguém vai descobrir naturalmente, e o conhecimento ainda está sendo repassado. Assim como você não precisa saber a proporção de ácido estomacal para desfrutar de alimentos deliciosos e picantes, você não precisa se preocupar com indigestão para as gerações futuras. Não precisa forçar algumas coisas, é meio negativo, haha.
A imagem não é difícil, desde que você entenda a relação de chamada, e uma forma flutuante como o Photoshop é a melhor escolha, eu acho, // Inverter imagem
private void invertColorFiltersItem_Click(objeto remetente, System.EventArgs e)
{
ApplyFilter(new Inverter());
}
//Aplica filtro na imagem
private void ApplyFilter (filtro IFilter)
{
tentar
{
// define o cursor de espera
this.Cursor = Cursors.WaitCursor;
// aplica filtro à imagem
Bitmap newImage = filter.Apply(image)
;
{
//abre nova imagem em novo documento
host.NewDocument(novaImagem);
}
outro
{
se (host.RememberOnChange)
{
//faz backup da imagem atual
if (backup! = nulo)
backup.Dispose();
backup = imagem;
}
outro
{
// libera a imagem atual
imagem.Dispose();
}
imagem = novaImagem;
// atualização
UpdateNewImage();
}
}
catch(ArgumentException)
{
MessageBox.Show("O filtro selecionado não pode ser aplicado à imagem", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finalmente
{
//restaura o cursor
this.Cursor = Cursors.Default;
}
}Se a chamada for tranquila, nenhuma quantidade de código parecerá confusa. Para iniciantes, faça bom uso das regiões.
Existe também o conceito de DocumentsHost. É muito conveniente usá-lo para hospedar arquivos de imagem e conectar imagens e formulários.
///Interface IDocumentsHost
/// Fornece conexão entre documentos e a janela principal
/// </sumário>
interface pública IDocumentsHost
{
bool CreateNewDocumentOnChange{get;}
bool RememberOnChange{get;}
bool NewDocument(imagem bitmap);
bool NewDocument (imagem ComplexImage);
Bitmap GetImage (remetente do objeto, texto da string, tamanho do tamanho, formato PixelFormat);
}Todos são bem-vindos para discutir comigo