>  기사  >  웹 프론트엔드  >  PhotoShop 알고리즘 원리 분석 시리즈 - 스타일화 - 가장자리 찾기.

PhotoShop 알고리즘 원리 분석 시리즈 - 스타일화 - 가장자리 찾기.

高洛峰
高洛峰원래의
2017-02-21 09:14:272149검색

시리즈 1, 시리즈 2 같은 제목을 쓰지 않는 이유는 언제까지 버틸 수 있을지 모르기 때문이다. 나는 사물의 표현이나 언어의 풍부함에 그다지 재능이 없다는 것을 알고 있습니다. 그리고 코드 한 조각에는 기본 원칙인 "예비 구현"부터 최적화 속도 및 기타 프로세스까지 프로세스를 말로 신중하게 설명해야 하는데, 이는 아마도 쉬운 작업이 아닐 것입니다.

제가 마스터한 포토샵 알고리즘 중 일부가 100% 맞다고는 할 수 없지만, 실행 효과로 보면 전체적인 방향에서는 확실히 문제가 없습니다.

현재 다른 사람의 글, 오픈 소스 코드, 내 생각을 통해 PS 알고리즘을 거의 100개 정도 배웠을 것입니다. 시간이 허락하고 인내심이 허락한다면 천천히 정리해볼 것입니다. 비록 많은 사람들의 눈에는 이러한 알고리즘이 연구 가치가 전혀 없지만 결국에는 상용화되었습니다. 말이 되네요. 그냥 자기 감사와 자기 만족의 방법으로 사용하겠습니다.

오늘은 엣지서치 알고리즘에 대해 이야기해보겠습니다. 원리를 설명하고 나면 읽지 않는 사람도 많을지 모르지만, 주의 깊게 공부한 사람은 적습니다.

먼저 렌더링을 게시해 보겠습니다.

PhotoShop算法原理解析系列 -  风格化---》查找边缘。

원칙: 일반적인 Sobel 모서리 연산자의 결과는 반전될 수 있습니다.

계속 읽으시도록 유도하기 위해 먼저 코드의 실행 속도를 알려드리겠습니다. 3000*4000*3의 디지털 이미지의 경우 처리 시간300ms.

소벨이 뭐예요? 바이두에서 사진 몇 장 복사해서 주소 수정했어요:

PhotoShop算法原理解析系列 -  风格化---》查找边缘。 안 할래요 위의 두 공식에 대해 너무 많이 설명하면 A가 입력 이미지이고 G가 A의 출력 이미지라는 것만 알면 됩니다. 마지막 단계는 다음을 수행하는 것입니다: G=255-G, 이는 에지 검색 알고리즘입니다.

이미지의

물리적 가장자리

에서 픽셀을 처리하는 방법에 문제가 있습니다. 전문적인 이미지 처리 소프트웨어로서 4개의 Edge 픽셀을 무시하면 가장 기본적인 원칙을 위반합니다. 가장자리에 대한 별도의 코드 처리로 인해 인코딩에 중복되고 번거로운 문제가 발생합니다. 문제를 해결하는 가장 간단하고 효율적인 방법은 센티널 경계를 사용하는 것입니다. 많은 특수 효과 알고리즘을 작성한 사람들은 단일 픽셀을 처리하는 알고리즘을 제외하고는 원본 이미지를 백업할 필요가 없다는 것을 알아야 합니다(반드시 전역 백업이 필요하지는 않음). 백업), 도메인 정보가 필요한 알고리즘은 알고리즘의 이전 단계에서 픽셀을 수정하며, 알고리즘의 현재 단계에서는 수정되지 않은 픽셀 값이 필요합니다. 따라서 이 알고리즘은 일반적으로 계산을 시작하기 전에 원본 이미지를 복제해야 합니다. 복제된 데이터에서 도메인 정보를 읽습니다. 복제 과정이 완전한 복제가 아닌 적절한 경계를 확장한 후 복제한다면 위의 경계 처리 문제를 해결할 수 있다.

예를 들어 아래 사진의 크기가 19×14픽셀인 경우 백업 사진을 상하좌우 1픽셀씩 확장하고 가장자리 값은 21*16이 됩니다. 크기:

 

 

PhotoShop算法原理解析系列 -  风格化---》查找边缘。

이렇게 하면 원본 이미지의 3*3 영역 픽셀을 계산할 때 확장된 복제 이미지의 해당 지점에서 샘플링해도 이미지 범위에 들어가지 않는 문제가 발생하지 않으며, 인코딩에 대한 판단력도 훨씬 줄어듭니다.

계산 속도 측면에서 보면 위의 계산식 G에 제곱근 연산이 있다는 점에 주목하세요. 이는 이미지 데이터의 특수성으로 인해 시간이 많이 걸리는 과정입니다. 정수여야 합니다. 테이블 설정을 고려해야 하는 조회 테이블을 사용하여 속도를 최적화할 수 있습니다.

이 기사의 특정 문제와 관련하여 두 단계로 논의하겠습니다. 먼저 루트 기호 아래에서 가능한 모든 상황에 대한 조회 테이블을 만듭니다. GX와 GY의 계산식을 살펴보시고, 둘의 제곱합의 최대값이 얼마인지 잠시 생각해 보시면 좋을 것 같습니다. 둘째: 0^2~255^2 범위 내에서 조회 테이블을 만든 다음 루트 기호 아래의 숫자가 255^2보다 크지 않은지 확인하세요. 이렇게 할 수 있는 이유는 이미지 데이터의 최대값이 255이기 때문입니다. 루트 기호 아래의 숫자가 255^2보다 큰 경우에도 제곱근 값을 찾은 후 255로 조정해야 합니다. 따라서 이 알고리즘에서는 후자를 선택해야 합니다.

private void CmdFindEdgesArray_Click(object sender, EventArgs e)
{    int X, Y;    int Width, Height, Stride, StrideC, HeightC;    int Speed, SpeedOne, SpeedTwo, SpeedThree;    int BlueOne, BlueTwo, GreenOne, GreenTwo, RedOne, RedTwo;    int PowerRed, PowerGreen, PowerBlue;
    Bitmap Bmp = (Bitmap)Pic.Image;    if (Bmp.PixelFormat != PixelFormat.Format24bppRgb) throw new Exception("不支持的图像格式.");    byte[] SqrValue = new byte[65026];    for (Y = 0; Y < 65026; Y++) SqrValue[Y] = (byte)(255 - (int)Math.Sqrt(Y));      // 计算查找表,注意已经砸查找表里进行了反色
    Width = Bmp.Width; Height = Bmp.Height; Stride = (int)((Bmp.Width * 3 + 3) & 0XFFFFFFFC);
    StrideC = (Width + 2) * 3; HeightC = Height + 2;                                 // 宽度和高度都扩展2个像素

    byte[] ImageData = new byte[Stride * Height];                                    // 用于保存图像数据,(处理前后的都为他)
    byte[] ImageDataC = new byte[StrideC * HeightC];                                // 用于保存扩展后的图像数据

    fixed (byte* Scan0 = &ImageData[0])
    {
        BitmapData BmpData = new BitmapData();
        BmpData.Scan0 = (IntPtr)Scan0;                                              //  设置为字节数组的的第一个元素在内存中的地址
        BmpData.Stride = Stride;
        Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadWrite | ImageLockMode.UserInputBuffer, PixelFormat.Format24bppRgb, BmpData);

        Stopwatch Sw = new Stopwatch();                                             //  只获取计算用时        Sw.Start();        for (Y = 0; Y < Height; Y++)
        {
            System.Buffer.BlockCopy(ImageData, Stride * Y, ImageDataC, StrideC * (Y + 1), 3);        // 填充扩展图的左侧第一列像素(不包括第一个和最后一个点)
            System.Buffer.BlockCopy(ImageData, Stride * Y + (Width - 1) * 3, ImageDataC, StrideC * (Y + 1) + (Width + 1) * 3, 3);  // 填充最右侧那一列的数据
            System.Buffer.BlockCopy(ImageData, Stride * Y, ImageDataC, StrideC * (Y + 1) + 3, Width * 3);
        }
        System.Buffer.BlockCopy(ImageDataC, StrideC, ImageDataC, 0, StrideC);              //  第一行
        System.Buffer.BlockCopy(ImageDataC, (HeightC - 2) * StrideC, ImageDataC, (HeightC - 1) * StrideC, StrideC);    //  最后一行               

        for (Y = 0; Y < Height; Y++)
        {
            Speed = Y * Stride;
            SpeedOne = StrideC * Y;            for (X = 0; X < Width; X++)
            {
                SpeedTwo = SpeedOne + StrideC;          //  尽量减少计算
                SpeedThree = SpeedTwo + StrideC;        //  下面的就是严格的按照Sobel算字进行计算,代码中的*2一般会优化为移位或者两个Add指令的,如果你不放心,当然可以直接改成移位
                BlueOne = ImageDataC[SpeedOne] + 2 * ImageDataC[SpeedTwo] + ImageDataC[SpeedThree] - ImageDataC[SpeedOne + 6] - 2 * ImageDataC[SpeedTwo + 6] - ImageDataC[SpeedThree + 6];
                GreenOne = ImageDataC[SpeedOne + 1] + 2 * ImageDataC[SpeedTwo + 1] + ImageDataC[SpeedThree + 1] - ImageDataC[SpeedOne + 7] - 2 * ImageDataC[SpeedTwo + 7] - ImageDataC[SpeedThree + 7];
                RedOne = ImageDataC[SpeedOne + 2] + 2 * ImageDataC[SpeedTwo + 2] + ImageDataC[SpeedThree + 2] - ImageDataC[SpeedOne + 8] - 2 * ImageDataC[SpeedTwo + 8] - ImageDataC[SpeedThree + 8];
                BlueTwo = ImageDataC[SpeedOne] + 2 * ImageDataC[SpeedOne + 3] + ImageDataC[SpeedOne + 6] - ImageDataC[SpeedThree] - 2 * ImageDataC[SpeedThree + 3] - ImageDataC[SpeedThree + 6];
                GreenTwo = ImageDataC[SpeedOne + 1] + 2 * ImageDataC[SpeedOne + 4] + ImageDataC[SpeedOne + 7] - ImageDataC[SpeedThree + 1] - 2 * ImageDataC[SpeedThree + 4] - ImageDataC[SpeedThree + 7];
                RedTwo = ImageDataC[SpeedOne + 2] + 2 * ImageDataC[SpeedOne + 5] + ImageDataC[SpeedOne + 8] - ImageDataC[SpeedThree + 2] - 2 * ImageDataC[SpeedThree + 5] - ImageDataC[SpeedThree + 8];

                PowerBlue = BlueOne * BlueOne + BlueTwo * BlueTwo;
                PowerGreen = GreenOne * GreenOne + GreenTwo * GreenTwo;
                PowerRed = RedOne * RedOne + RedTwo * RedTwo;                if (PowerBlue > 65025) PowerBlue = 65025;           //  处理掉溢出值
                if (PowerGreen > 65025) PowerGreen = 65025;                if (PowerRed > 65025) PowerRed = 65025;
                ImageData[Speed] = SqrValue[PowerBlue];             //  查表
                ImageData[Speed + 1] = SqrValue[PowerGreen];
                ImageData[Speed + 2] = SqrValue[PowerRed];

                Speed += 3;                                  // 跳往下一个像素
                SpeedOne += 3;
            }
        }
        Sw.Stop();        this.Text = "计算用时: " + Sw.ElapsedMilliseconds.ToString() + " ms";

        Bmp.UnlockBits(BmpData);                         //  必须先解锁,否则Invalidate失败     }
    Pic.Invalidate();
}

간단함을 위해 먼저 C#에서 1차원 배열을 사용하여 구현했으며, 타이밍 부분은 그렇지 않습니다. 이미지 데이터는 실제 이미지 처리 과정에서 얻은 것이므로 이미지 데이터 획득 및 업데이트를 고려하십시오.

위 코드는 Release 모드로 컴파일한 후 컴파일된 EXE를 실행합니다. 3000*4000*3 컬러 이미지의 경우 IDE 모드에서 실행하면 약 480ms가 걸립니다. 먼저 모듈이 옵션--"디버깅--"일반에 로드될 때 JIT 최적화 취소(호스팅만) 열을 선택 취소해야 합니다.

PhotoShop算法原理解析系列 -  风格化---》查找边缘。

위 코드에 데이터를 채울 때 새로운 그림이 없고, 그 안에 이미지 데이터를 채울 때, 이미지는 실제로는 연속적인 메모리 조각에 약간의 헤더 정보를 더한 것 아닌가요? 헤더 정보가 이미 있으므로 메모리 한 조각이면 충분합니다.

복제 데이터를 채우는 데는 이전에 사용했던 CopyMemory와 유사하고 매우 빠른 시스템 Buffer.BlockCopy 기능이 사용됩니다.

실행 속도를 더욱 높이기 위해 먼저 알고리즘의 핵심 시간이 많이 소요되는 부분의 코드, 즉 for (X = 0; X < Width; X++) , 코드 줄의 디컴파일된 코드를 살펴보겠습니다.

<span style="font-size: 13px;"> BlueOne = ImageDataC[SpeedOne] + <span style="color: #800080;">2</span> * ImageDataC[SpeedTwo] + ImageDataC[SpeedThree] - ImageDataC[SpeedOne + <span style="color: #800080;">6</span>] - <span style="color: #800080;">2</span> * ImageDataC[SpeedTwo + <span style="color: #800080;">6</span>] - ImageDataC[SpeedThree + <span style="color: #800080;">6</span><span style="color: #000000;">];<br/><br/></span><span style="color: #800080;">00000302</span><span style="color: #000000;">  cmp         ebx,edi 
</span><span style="color: #800080;">00000304</span><span style="color: #000000;">  jae         0000073C             //   数组是否越界?
0000030a  movzx       eax,</span><span style="color: #0000ff;">byte</span> ptr [esi+ebx+<span style="color: #800080;">8</span><span style="color: #000000;">]    //  将ImageDataC[SpeedOne]中的数据传送的eax寄存器
0000030f  mov         dword ptr [ebp</span>-<span style="color: #000000;">80h],eax 
</span><span style="color: #800080;">00000312</span>  mov         edx,dword ptr [ebp-<span style="color: #000000;">2Ch] 
</span><span style="color: #800080;">00000315</span><span style="color: #000000;">  cmp         edx,edi 
</span><span style="color: #800080;">00000317</span><span style="color: #000000;">  jae         0000073C            //     数组是否越界?           
0000031d  movzx       edx,</span><span style="color: #0000ff;">byte</span> ptr [esi+edx+<span style="color: #800080;">8</span><span style="color: #000000;">]   //   将ImageDataC[SpeedTwo]中的数据传送到edx寄存器</span><span style="color: #800080;">00000322</span><span style="color: #000000;">  add         edx,edx             //    计算2*ImageDataC[SpeedTwo]    </span><span style="color: #800080;">00000324</span><span style="color: #000000;">  add         eax,edx             //    计算ImageDataC[SpeedOne]+2*ImageDataC[SpeedTwo],并保存在eax寄存器中           </span><span style="color: #800080;">00000326</span><span style="color: #000000;">  cmp         ecx,edi 
</span><span style="color: #800080;">00000328</span><span style="color: #000000;">  jae         0000073C 
0000032e  movzx       edx,</span><span style="color: #0000ff;">byte</span> ptr [esi+ecx+<span style="color: #800080;">8</span><span style="color: #000000;">]   //    将ImageDataC[SpeedThree]中的数据传送到edx寄存器</span><span style="color: #800080;">00000333</span>  mov         dword ptr [ebp+<span style="color: #000000;">FFFFFF78h],edx 
</span><span style="color: #800080;">00000339</span><span style="color: #000000;">  add         eax,edx 
0000033b  lea         edx,[ebx</span>+<span style="color: #800080;">6</span><span style="color: #000000;">] 
0000033e  cmp         edx,edi 
</span><span style="color: #800080;">00000340</span><span style="color: #000000;">  jae         0000073C 
</span><span style="color: #800080;">00000346</span>  movzx       edx,<span style="color: #0000ff;">byte</span> ptr [esi+edx+<span style="color: #800080;">8</span><span style="color: #000000;">] 
0000034b  mov         dword ptr [ebp</span>+<span style="color: #000000;">FFFFFF7Ch],edx 
</span><span style="color: #800080;">00000351</span><span style="color: #000000;">  sub         eax,edx 
</span><span style="color: #800080;">00000353</span>  mov         edx,dword ptr [ebp-<span style="color: #000000;">2Ch] 
</span><span style="color: #800080;">00000356</span>  add         edx,<span style="color: #800080;">6</span> <span style="color: #800080;">00000359</span><span style="color: #000000;">  cmp         edx,edi 
0000035b  jae         0000073C 
</span><span style="color: #800080;">00000361</span>  movzx       edx,<span style="color: #0000ff;">byte</span> ptr [esi+edx+<span style="color: #800080;">8</span><span style="color: #000000;">] 
</span><span style="color: #800080;">00000366</span><span style="color: #000000;">  add         edx,edx 
</span><span style="color: #800080;">00000368</span><span style="color: #000000;">  sub         eax,edx 
0000036a  lea         edx,[ecx</span>+<span style="color: #800080;">6</span><span style="color: #000000;">] 
0000036d  cmp         edx,edi 
0000036f  jae         0000073C 
</span><span style="color: #800080;">00000375</span>  movzx       edx,<span style="color: #0000ff;">byte</span> ptr [esi+edx+<span style="color: #800080;">8</span><span style="color: #000000;">] 
0000037a  mov         dword ptr [ebp</span>+<span style="color: #000000;">FFFFFF74h],edx 
</span><span style="color: #800080;">00000380</span><span style="color: #000000;">  sub         eax,edx 
</span><span style="color: #800080;">00000382</span>  mov         dword ptr [ebp-30h],eax </span>

위의 어셈블리 코드(가장 중요한 것은 0000073c 레이블)에 대해 약간만 주석을 달았으며 반환을 추적하고 다른 함수를 호출했습니다.

                                                         >                                                    (((올가미,          . 이전 cmp와 jae 명령을 실행해야 한다고 분석해 보면 배열의 첨자가 범위를 벗어났는지 확인하기 위해 비슷한 작업을 수행하는 것이라고 생각됩니다. 우리 알고리즘이 경계를 넘지 않는다는 것을 보장할 수 있다면 코드의 이 부분은 매우 유용할 것입니다. 이로 인해 제가 업무를 수행하는 데 지장이 생기지 않을까요?

이를 위해서는 C#에서 알고리즘을 구현하기 위해 포인터를 직접 사용해야 한다고 생각합니다. C#에는 unsafe 모드와 포인터가 있어서 매우 편리하고 포인터를 다음과 같이 표현할 수 있습니다. *, []를 사용할 수도 있습니다. 예를 들어 *(P+4)와 P[4]는 같은 의미를 갖습니다. 그런 다음 위의 코드를 거의 수정하지 않고 포인터 버전으로 수정할 수 있습니다.

private void CmdFindEdgesPointer_Click(object sender, EventArgs e)
    {        int X, Y;        int Width, Height, Stride, StrideC, HeightC;        int Speed, SpeedOne, SpeedTwo, SpeedThree;        int BlueOne, BlueTwo, GreenOne, GreenTwo, RedOne, RedTwo;        int PowerRed, PowerGreen, PowerBlue;
        Bitmap Bmp = (Bitmap)Pic.Image;        if (Bmp.PixelFormat != PixelFormat.Format24bppRgb) throw new Exception("不支持的图像格式.");        byte[] SqrValue = new byte[65026];        for (Y = 0; Y < 65026; Y++) SqrValue[Y] = (byte)(255 - (int)Math.Sqrt(Y));      // 计算查找表,注意已经砸查找表里进行了反色
        Width = Bmp.Width; Height = Bmp.Height; Stride = (int)((Bmp.Width * 3 + 3) & 0XFFFFFFFC);
        StrideC = (Width + 2) * 3; HeightC = Height + 2;                                 // 宽度和高度都扩展2个像素

        byte[] ImageData = new byte[Stride * Height];                                    // 用于保存图像数据,(处理前后的都为他)
        byte[] ImageDataC = new byte[StrideC * HeightC];                                 // 用于保存扩展后的图像数据

        fixed (byte* P = &ImageData[0], CP = &ImageDataC[0], LP = &SqrValue[0])
        {            byte* DataP = P, DataCP = CP, LutP = LP;
            BitmapData BmpData = new BitmapData();
            BmpData.Scan0 = (IntPtr)DataP;                                              //  设置为字节数组的的第一个元素在内存中的地址
            BmpData.Stride = Stride;
            Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadWrite | ImageLockMode.UserInputBuffer, PixelFormat.Format24bppRgb, BmpData);

            Stopwatch Sw = new Stopwatch();                                             //  只获取计算用时            Sw.Start();            for (Y = 0; Y < Height; Y++)
            {
                System.Buffer.BlockCopy(ImageData, Stride * Y, ImageDataC, StrideC * (Y + 1), 3);        // 填充扩展图的左侧第一列像素(不包括第一个和最后一个点)
                System.Buffer.BlockCopy(ImageData, Stride * Y + (Width - 1) * 3, ImageDataC, StrideC * (Y + 1) + (Width + 1) * 3, 3);  // 填充最右侧那一列的数据
                System.Buffer.BlockCopy(ImageData, Stride * Y, ImageDataC, StrideC * (Y + 1) + 3, Width * 3);
            }
            System.Buffer.BlockCopy(ImageDataC, StrideC, ImageDataC, 0, StrideC);              //  第一行
            System.Buffer.BlockCopy(ImageDataC, (HeightC - 2) * StrideC, ImageDataC, (HeightC - 1) * StrideC, StrideC);    //  最后一行               

            for (Y = 0; Y < Height; Y++)
            {
                Speed = Y * Stride;
                SpeedOne = StrideC * Y;                for (X = 0; X < Width; X++)
                {
                    SpeedTwo = SpeedOne + StrideC;          //  尽量减少计算
                    SpeedThree = SpeedTwo + StrideC;        //  下面的就是严格的按照Sobel算字进行计算,代码中的*2一般会优化为移位或者两个Add指令的,如果你不放心,当然可以直接改成移位
                    BlueOne = DataCP[SpeedOne] + 2 * DataCP[SpeedTwo] + DataCP[SpeedThree] - DataCP[SpeedOne + 6] - 2 * DataCP[SpeedTwo + 6] - DataCP[SpeedThree + 6];
                    GreenOne = DataCP[SpeedOne + 1] + 2 * DataCP[SpeedTwo + 1] + DataCP[SpeedThree + 1] - DataCP[SpeedOne + 7] - 2 * DataCP[SpeedTwo + 7] - DataCP[SpeedThree + 7];
                    RedOne = DataCP[SpeedOne + 2] + 2 * DataCP[SpeedTwo + 2] + DataCP[SpeedThree + 2] - DataCP[SpeedOne + 8] - 2 * DataCP[SpeedTwo + 8] - DataCP[SpeedThree + 8];
                    BlueTwo = DataCP[SpeedOne] + 2 * DataCP[SpeedOne + 3] + DataCP[SpeedOne + 6] - DataCP[SpeedThree] - 2 * DataCP[SpeedThree + 3] - DataCP[SpeedThree + 6];
                    GreenTwo = DataCP[SpeedOne + 1] + 2 * DataCP[SpeedOne + 4] + DataCP[SpeedOne + 7] - DataCP[SpeedThree + 1] - 2 * DataCP[SpeedThree + 4] - DataCP[SpeedThree + 7];
                    RedTwo = DataCP[SpeedOne + 2] + 2 * DataCP[SpeedOne + 5] + DataCP[SpeedOne + 8] - DataCP[SpeedThree + 2] - 2 * DataCP[SpeedThree + 5] - DataCP[SpeedThree + 8];

                    PowerBlue = BlueOne * BlueOne + BlueTwo * BlueTwo;
                    PowerGreen = GreenOne * GreenOne + GreenTwo * GreenTwo;
                    PowerRed = RedOne * RedOne + RedTwo * RedTwo;                    if (PowerBlue > 65025) PowerBlue = 65025;           //  处理掉溢出值
                    if (PowerGreen > 65025) PowerGreen = 65025;                    if (PowerRed > 65025) PowerRed = 65025;

                    DataP[Speed] = LutP[PowerBlue];                     //  查表
                    DataP[Speed + 1] = LutP[PowerGreen];
                    DataP[Speed + 2] = LutP[PowerRed];

                    Speed += 3;                                         //  跳往下一个像素
                    SpeedOne += 3;
                }
            }
            Sw.Stop();            this.Text = "计算用时: " + Sw.ElapsedMilliseconds.ToString() + " ms";

            Bmp.UnlockBits(BmpData);                         //  必须先解锁,否则Invalidate失败         }
        Pic.Invalidate();
    }</p>
<p><span style="font-size: 13px;"></span></p> 동일한 효과, 동일한 이미지, 계산에 330ms가 걸립니다. <p class="cnblogs_code"></p>
<p></p> 동일한 코드의 어셈블리 코드를 살펴보겠습니다. <p><span style="font-size: 13px;"></span></p>
<pre class="brush:php;toolbar:false"><span style="font-size: 13px;">BlueOne = DataCP[SpeedOne] + <span style="color: #800080;">2</span> * DataCP[SpeedTwo] + DataCP[SpeedThree] - DataCP[SpeedOne + <span style="color: #800080;">6</span>] - <span style="color: #800080;">2</span> * DataCP[SpeedTwo + <span style="color: #800080;">6</span>] - DataCP[SpeedThree + <span style="color: #800080;">6</span><span style="color: #000000;">];<br><br></span><span style="color: #800080;">00000318</span>  movzx       eax,<span style="color: #0000ff;">byte</span> ptr [esi+<span style="color: #000000;">edi] 
0000031c  mov         dword ptr [ebp</span>-<span style="color: #000000;">74h],eax 
0000031f  movzx       edx,</span><span style="color: #0000ff;">byte</span> ptr [esi+<span style="color: #000000;">ebx] 
</span><span style="color: #800080;">00000323</span><span style="color: #000000;">  add         edx,edx 
</span><span style="color: #800080;">00000325</span><span style="color: #000000;">  add         eax,edx 
</span><span style="color: #800080;">00000327</span>  movzx       edx,<span style="color: #0000ff;">byte</span> ptr [esi+<span style="color: #000000;">ecx] 
0000032b  mov         dword ptr [ebp</span>-<span style="color: #000000;">7Ch],edx 
0000032e  add         eax,edx 
</span><span style="color: #800080;">00000330</span>  movzx       edx,<span style="color: #0000ff;">byte</span> ptr [esi+edi+<span style="color: #800080;">6</span><span style="color: #000000;">] 
</span><span style="color: #800080;">00000335</span>  mov         dword ptr [ebp-<span style="color: #000000;">78h],edx 
</span><span style="color: #800080;">00000338</span><span style="color: #000000;">  sub         eax,edx 
0000033a  movzx       edx,</span><span style="color: #0000ff;">byte</span> ptr [esi+ebx+<span style="color: #800080;">6</span><span style="color: #000000;">] 
0000033f  add         edx,edx 
</span><span style="color: #800080;">00000341</span><span style="color: #000000;">  sub         eax,edx 
</span><span style="color: #800080;">00000343</span>  movzx       edx,<span style="color: #0000ff;">byte</span> ptr [esi+ecx+<span style="color: #800080;">6</span><span style="color: #000000;">] 
</span><span style="color: #800080;">00000348</span>  mov         dword ptr [ebp-<span style="color: #000000;">80h],edx 
0000034b  sub         eax,edx 
0000034d  mov         dword ptr [ebp</span>-30h],eax </span>

Produced 어셈블리 코드는 간결하고, 의미가 명확하며, 이에 비해 명령어가 많이 부족합니다. 물론 훨씬 더 빨라질 것이다.

이 코드에 주의하세요:

  fixed (byte* P = &ImageData[0], CP = &ImageDataC[0], LP = &SqrValue[0])
        {            byte* DataP = P, DataCP = CP, LutP = LP;

다음으로 바꾸는 경우:

아아앙

실제로는 순수 배열 버전에 비해 코드 속도가 느립니다. 이유는 연습이 왕인데, 어쨌든 이 결과는 알고 있습니다. Tie Brother의 기사를 참조할 수 있습니다:

Talking .NET Type Public's Public, Fix's Fix

물론 작은 작업을 더욱 최적화할 수도 있습니다. 예를 들어 movzx eax,byte ptr [esi+edi] 이 문장에서 esi는 실제로 배열의 기본 주소입니다. DataCP[SpeedOne]을 이렇게 쓰면 매번 계산을 위해 이 기본 주소 + 오프셋을 갖게 됩니다. 포인터 변수가 요청된 위치를 직접 가리키도록 실시간으로 직접 동적으로 제어할 수 있는 경우 최적화가 명확하지는 않지만 기본적으로 질문에서 앞서 언급한 300ms 시간에 도달할 수 있습니다. 구체적인 코드는 첨부파일에서 확인하실 수 있습니다.

이런거 GPU에 던지는게 지금보다 낫다고 하는 분들이 많을텐데... 이 친구들 너무 공격하지 않았으면 좋겠습니다. .사람마다 취미가 있는데 저는 CPU만 좋아해요.

더 많은 PhotoShop 알고리즘 원리 분석 시리즈 - 스타일화 - 가장자리 찾기. 관련 기사는 PHP 중국어 홈페이지를 주목해주세요!


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.