저는 고등학교 때 처음으로 디지털 영상 처리를 접했습니다. 당시 PHOTOSHOP은 아직 4.0이었습니다. 아마도 선입견 때문인지 2D에서 3D로의 도약은 아직 별 관심이 없었을 것입니다. 나랑도 하고, 큐빅 고연봉에 그 광장을 놔둘 수가 없어.... 하하.
대학에 다닐 때 친구들과 함께 이미지 처리 프로그램을 몇 개 작성했는데, 그 당시에는 프로그래밍이 아직 매우 밋밋했고, 그것을 어떻게 구현해야 할지 고민만 했는데요, 이제 진짜 기술은 전체적인 파악 능력인 것 같습니다. 영감이 번쩍이는 마법보다는 상황이 더 중요합니다. 며칠 전 외국의 영상처리 프로그램을 접하게 되었는데, 앞으로는 영상처리에 대해 구체적으로 공부하지 않을 것으로 예상됩니다.
이전 동급생이 .net에는 포인터가 없다고 말한 적이 있습니다. 현재 많은 교육 과정에서 동일한 내용을 말하는 것 같습니다. 프레임워크는 포인터 사용을 권장하지 않으며, 특히 웹 서비스 및 원격 작업과 같은 프로세스 간 작업에서는 포인터가 안전하지 않습니다. 하지만 TC를 사용해 본 사람이라면 누구나 포인터의 실행 효율성에 깊은 인상을 받을 것입니다. 대규모 데이터의 일괄 계산이 요구되는 상황에서는 포인터가 유일한 선택입니다. 따라서 .net은 포인터를 영리하게 유지하고 이를 안전하지 않은 메서드 세트에 나열합니다. 포인터를 합리적으로 사용하면 실행 효율성이 크게 향상됩니다. 640*480 이미지에서 포인트별 작업을 수행하는 실험을 수행했지만 포인터 작업은 거의 즉시 완료됩니다. 그러므로 포인터를 사용하는 것을 두려워하지 마십시오.
두 번째는 수학입니다. 프로그램을 작성하기 전에 모두가 이해하도록 조언합니다. 수학 수업은 농담이 아닙니다. 이해하지 못하면 침대에 누워서 반복해서 생각해야 합니다. 수학이 알츠하이머병을 예방할 수 있다는 것.
집에 더 가까이 다가가서 프로그램 구조에 대해 이야기해 보겠습니다.
이미징 프로젝트(필터, 텍스처, 이미지 모드)
수학 프로젝트(알고리즘, 경계, 사용자 정의 및 일반적인 계산 방법)
주요 프로그램 프로젝트
인터페이스
지향 프로그래밍도 수행해 보겠습니다.
{
비트맵 적용(비트맵 img);
}
설명을 위해 인터페이스 지향 프로그래밍도 수행할 것입니다. 각 필터는 이 인터페이스를 구현해야 합니다. 인터페이스 정의에는 실제 이미지를 생성하지 않고 바이너리 객체만 생성하는 변명 정의도 포함되어 있으므로 여기서는 고려하지 않겠습니다. 존재. 반전 컬러 필터를 예로 들어 보겠습니다.
공개 비트맵 적용( Bitmap srcImg )
{
//소스 이미지 크기 가져오기
int 너비 = srcImg.Width;
int 높이 = srcImg.Height;
PixelFormat fmt = ( srcImg.PixelFormat == PixelFormat.Format8bppIndexed ) ?
PixelFormat.Format8bppIndexed : PixelFormat.Format24bppRgb;
// 소스 비트맵 데이터 잠금
BitmapData srcData = srcImg.LockBits(
새로운 직사각형(0, 0, 너비, 높이),
ImageLockMode.ReadOnly, fmt )
// 새 이미지 생성
비트맵 dstImg = ( fmt == PixelFormat.Format8bppIndexed ) ?
AForge.Imaging.Image.CreateGrayscaleImage(너비, 높이):
new Bitmap( width, height, fmt )
// 대상 비트맵 데이터 잠금
BitmapData dstData = dstImg.LockBits(
새로운 직사각형(0, 0, 너비, 높이),
ImageLockMode.ReadWrite, fmt )
// 이미지 복사
Win32.memcpy( dstData.Scan0, srcData.Scan0, srcData.Stride * height )
// 필터 처리
ProcessFilter( dstData, fmt )
// 두 이미지 모두 잠금 해제
dstImg.UnlockBits(dstData);
srcImg.UnlockBits( srcData )
return dstImg;
}
필터 메소드의 입구로서 처리 전 준비 작업을 완료합니다. ProcessFilter는 각 필터 클래스에서 공통으로 사용하는 ProcessFilter 메소드도 호출하는데, 이 ProcessFilter는 기능, 포인트별 연산 또는 템플릿 연산을 구현하는 핵심입니다.
// 필터 처리
개인 안전하지 않은 무효 ProcessFilter( BitmapData 데이터, PixelFormat fmt )
{
int 너비 = data.Width;
int height = data.Height;
int lineSize = width * ( ( fmt == PixelFormat.Format8bppIndexed ) ? 1 : 3 );
int offset = data.Stride - lineSize
// 작업을 수행합니다.
byte * ptr = (byte *) data.Scan0.ToPointer( )
// 반전
for ( int y = 0; y < 높이; y++ )
{
for ( int x = 0; x < lineSize; x++, ptr ++ )
{
// 각 픽셀을 반전시킵니다.
*ptr = (바이트)( 255 - *ptr );
}
ptr += 오프셋;
}
}
그 중 Format8bppIndexed는 크게 걱정할 필요는 없다고 개인적으로 생각합니다. 디자인 초기 단계에서 호환성 문제를 고려할 필요는 없습니다.
이제 텍스처에 대해 이야기해 보겠습니다. 이전에는 이에 대해 많이 생각하지 않았지만 수학에서 텍스처가 더 많은 역할을 하기 때문에 외국인들이 그것을 가지고 노는 것을 좋아한다는 것을 알았습니다. 아마도 그들 중 한 명이 수학적 모델링 소프트웨어를 플레이할 때 이 방법을 발견했기 때문에 고급 수학 교사는 누구도 받아들이지 않고 알고리즘을 매우 열심히 가지고 놀았을 것입니다. 어쨌든 그런 일이 일어난 것 같아요. . .
공용 인터페이스 ITextureGenerator
{
/**//// <요약>
/// 텍스처 생성
/// </summary>
float[,] 생성( int 너비, int 높이 )
/**//// <summary>
/// 재설정 - 내부 난수를 재생성합니다.
/// </summary>
무효 재설정( );
}
이것은 텍스처 생성기의 구현 인터페이스입니다. 매번 텍스처가 달라지도록 하려면 계산 매개변수로 난수를 업데이트해야 합니다.
private Math.PerlinNoise 노이즈 = new Math.PerlinNoise( 1.0 / 32, 0.05, 0.5, 8 );
텍스처 디테일을 얻으려면 노이즈도 필요하므로 다양한 유형의 노이즈를 구현해야 합니다.
//생성자
공개 WoodTexture( ) : this( 12.0 ) { }
공개 WoodTexture(이중 링)
{
this.rings = 반지;
재설정( );
}
생성자는 단위 텍스처 크기의 제한인 기본값 설정을 제공합니다.
//텍스처 생성
공개 float[,] 생성( int 너비, int 높이 )
{
float[,] 텍스처 = 새로운 float[높이, 너비];
int w2 = 너비 / 2;
int h2 = 높이 / 2;
for ( int y = 0; y < 높이; y++ )
{
for (int x = 0; x < 너비; x++)
{
더블 xv = (더블) ( x - w2 ) / 너비;
double yv = (double) ( y - h2 ) / 높이
[y, x] =
수학.최대( 0.0f, 수학.최소( 1.0f, (부동 소수점))
수학.Abs( 수학.죄(
( Math.Sqrt( xv * xv + yv * yv ) + Noise.Function2D( x + r, y + r ) )
* Math.PI * 2 * 링
))
)));
}
}
텍스처를 반환합니다.
}
그게 다야. . . 내 나쁜 수학의 결과. 나는 그녀가 무슨 말을하는지조차 모릅니다. 나는 최소값에서 최대 값을 선택합니다. 알고리즘을 찾는 것은 어렵지 않습니다. 핵심은 구조가 알고리즘을 어떻게 통합하는지 확인하는 것입니다.
공개 무효 재설정( )
{
r = rand.Next( 5000 );
}이 임의의 숫자를 잊지 마세요. 숫자의 이미지에도 자연스러운 아름다움이 필요합니다.
수학 공학의 객체 지향 개념은 구현하기 쉽지 않습니다. PerlinNoise를 살펴보고 다른 사람들에게 영감을 주세요.
공개 PerlinNoise( double initFrequency, double initAmplitude, double 지속성, int 옥타브 )
{
this.initFrequency = initFrequency;
this.initAmplitude = initAmplitude;
this.persistance = 지속성;
this.octaves = 옥타브;
}
먼저, 이미지 처리에는 1차원 상황과 2차원 상황이 포함되므로 데이터를 수집해야 합니다. 따라서 노이즈와 같은 기본 방법은 두 상황에 상응하는 방법을 제공해야 합니다.
/**//// <요약>
/// 1차원 Perlin 노이즈 함수
/// </summary>
공개 이중 함수(이중 x)
{
이중 주파수 = initFrequency;
이중 진폭 = initAmplitude;
이중 합계 = 0;
// 옥타브
for (int i = 0; i < 옥타브; i++)
{
sum += SmoothedNoise( x * 주파수 ) * 진폭
*= 2;
진폭 *= 지속성;
}
반환 금액;
}
/**//// <요약>
/// 2차원 Perlin 노이즈 함수
/// </summary>
공개 이중 Function2D(이중 x, 이중 y)
{
이중 주파수 = initFrequency;
이중 진폭 = initAmplitude;
이중 합계 = 0;
// 옥타브
for (int i = 0; i < 옥타브; i++)
{
sum += SmoothedNoise( x * 주파수, y * 주파수 ) * 진폭
주파수 *= 2;
진폭 *= 지속성;
}
반환 금액;
}
1차원과 2차원의 차이점은 무엇인가요? 중학교 때 선의 움직임이 표면을 생성한다는 것을 배웠고, 대학에 와서는 순환적이고 변화하는 선이 표면을 나타낼 수 있다는 것을 배웠습니다. 가장자리 인식 및 선명화를 통해 이전에는 선을 과소평가했지만 실제로는 표면보다 매개변수가 하나 적다는 사실도 발견했습니다.
/**//// <요약>
/// 일반 노이즈 함수
/// </summary>
보호된 이중 노이즈(int x)
{
int n = ( x << 13 ) ^ x;
return ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7ffffff ) / 1073741824.0 );
}
보호된 이중 노이즈(int x, int y)
{
int n = x + y * 57;
n = (n << 13 ) ^n ;
반환 ( 1.0 - ( ( n * ( n * n * 15731 + 789221 ) + 1376312589 ) & 0x7fffffff ) / 1073741824.0 );
} 이전 단락을 다시 한 번 증명합니다. 개인적으로 이 x+y*57에는 약간의 투영 의미가 있다고 생각합니다. 해당 노이즈 값을 가져옵니다. 하지만 노이즈를 직접 사용할 수는 없습니다.
/**//// <요약>
/// 노이즈 완화
/// </summary>
보호된 이중 SmoothedNoise( 이중 x )
{
int xInt = (int) x;
double xFrac = x - xInt;
return CosineInterpolate( Noise( xInt ) , Noise( xInt + 1 ), xFrac );
}
보호된 이중 SmoothedNoise(이중 x, 이중 y)
{
int xInt = (int) x;
int yInt = (int) y;
double xFrac = x - xInt;
double yFrac = y - yInt;
// 4개의 노이즈 값을 얻습니다.
double x0y0 = 노이즈( xInt , yInt );
double x1y0 = 노이즈( xInt + 1, yInt );
double x0y1 = 노이즈( xInt , yInt + 1 );
double x1y1 = Noise( xInt + 1, yInt + 1)
// x 보간
double v1 = CosineInterpolate( x0y0, x1y0, xFrac );
double v2 = CosineInterpolate( x0y1, x1y1, xFrac );
//y보간
CosineInterpolate(v1, v2, yFrac)를 반환합니다.
} 호출하기에는 다소 어색해 보이는 Smooth Noise는 이산 코사인이 아닌 코사인 보간법에 의해 동작합니다. 코사인 보간이란 무엇입니까? /**//// <요약>
/// 코사인 보간
/// </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;
}그렇습니다. 주인님이 알면 되는 것도 있고 그에 맞게 하면 됩니다. 왜일까요? 평생 이해하지 못하더라도 누군가는 자연스럽게 알게 될 것이고, 지식은 지금도 전승되고 있기 때문이다. 맛있고 매운 음식을 즐기기 위해 위산 비율을 알 필요가 없듯이, 미래 세대를 위한 소화 불량을 걱정할 필요가 없습니다. 억지로 할 필요는 없고 좀 부정적인 것 같아요. 하하.
그림은 어렵지 않으니 호출관계만 파악하시면 되고 포토샵처럼 떠있는 형태가 최선의 선택인거 같아요 //이미지 반전
개인 무효 invertColorFiltersItem_Click(개체 전송자, System.EventArgs e)
{
ApplyFilter(new Invert());
}
// 이미지에 필터 적용
개인 무효 ApplyFilter(IFilter 필터)
{
노력하다
{
// 대기 커서 설정
this.Cursor = Cursors.WaitCursor;
// 이미지에 필터 적용
비트맵 newImage = filter.Apply(image);
if (host.CreateNewDocumentOnChange)
{
// 새 문서에서 새 이미지 열기
호스트.NewDocument(newImage);
}
또 다른
{
if(호스트.RememberOnChange)
{
// 현재 이미지 백업
if (백업 != null)
백업.Dispose();
백업 = 이미지;
}
또 다른
{
// 현재 이미지 해제
image.Dispose();
}
이미지 = newImage;
// 업데이트
업데이트새이미지();
}
}
catch(ArgumentException)
{
MessageBox.Show("선택한 필터는 이미지에 적용할 수 없습니다.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
마지막으로
{
// 커서 복원
this.Cursor = Cursors.Default;
}
}호출이 원활하다면 어떤 코드도 지저분하게 느껴지지 않을 것입니다. 초보자의 경우 영역을 잘 활용하십시오.
DocumentsHost라는 개념도 있습니다. 이미지 파일을 호스팅하고 이미지와 양식을 연결하는 데 사용하면 매우 편리합니다.
/// IDocumentsHost 인터페이스
/// 문서와 기본 창 간의 연결을 제공합니다.
/// </summary>
공용 인터페이스 IDocumentsHost
{
bool CreateNewDocumentOnChange{get;}
bool RememberOnChange{get;}
bool NewDocument(비트맵 이미지);
bool NewDocument(ComplexImage 이미지);
Bitmap GetImage(객체 전송자, 문자열 텍스트, 크기 크기, PixelFormat 형식);
}누구나 나와 토론할 수 있습니다.