首頁  >  文章  >  web前端  >  PhotoShop演算法原理解析系列 - 風格化-尋找邊緣。

PhotoShop演算法原理解析系列 - 風格化-尋找邊緣。

高洛峰
高洛峰原創
2017-02-21 09:14:272146瀏覽

      之所以不寫系列文章一、系列文章二這樣的標題,是因為我不知道我能堅持多久。我知道我對事情的表達能力和語言的豐富性方面的天賦不高。而一段程式碼需要我去用心的把他從基本原理--》初步實現--》優化速度 等過程用文字的方式表述清楚,恐怕不是一件很容易的事情。 

      我所掌握的一些Photoshop中的演算法,不能說百分之一百就是正確的,但是從執行的效果中,大的方向肯定是沒有問題的。

      目前,從別人的文章、開源的程式碼以及自己的思考中我掌握的PS的演算法可能有近100個吧。如果時間容許、自身的耐心容許,我會將這些東西慢慢的整理開來,雖然在很多人看來,這些演算法並不具有什麼研究的價值了,畢竟人家都已經商業化了。說的也有道理,我姑且把他當作自我欣賞和自我滿足的一種方式吧。

      今天,我們講講尋找邊緣演算法。可能我說了原理,很多人就不會看下去了,可有幾人層仔細的研究過呢。

  先貼個效果圖吧:

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

  原理:常見的Sobel邊緣算子的結果進行反色即可

      為了能吸引你繼續看下去,我先給出我的程式碼的執行速度: 針對3000*4000*3的數位圖片,處理時間300ms

      何為Sobel,從百度抄幾張圖過來了並修改地址後: 

                   *&   PhotoShop算法原理解析系列 -  风格化---》查找边缘。  對上面兩個式子不做過多解釋,你只需要知道其中A為輸入圖像,把G作為A的輸出圖像就可以了,最後還要做一步: G=255-G ,就是找出邊緣演算法。

      尋找邊緣類別演算法都有個問題,對影像

物理邊緣

處的像素如何處理,在平日的處理程式碼中,很多人就是忽略四個邊緣的像素,作為專業的影像處理軟體,這可是違反最基本的原則的。對邊緣進行的單獨的程式碼處理,又會為編碼帶來冗餘和繁瑣的問題。解決問題的最簡單又有效率的方式就是採用哨兵邊界。       寫多了特效類演算法的都應該知道,除了那種對單一像素進行處理的演算法不需要對原始影像做個備份(不一定去全域備份),那些需要領域資訊的演算法由於演算法的前一步修改了一個像素,而演算法的當前步需要未修改的像素值,因此,一般這種演算法都會在開始前對原始影像做個克隆,在計算時,需要的領域資訊從克隆的資料中讀取。如果這個克隆的過程不是完全完全的克隆,而是擴展適當邊界後再克隆,就有可能解決上述的邊界處理問題。

  例如對下面的一個圖,19×14像素大小,我們的備份圖為上下左右各擴展一個像素的大小,並用邊緣的值填充,變為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#的一維數組實現的,並且計時部分未考慮圖像數據的取得與更新, 因為真正的影像處理過程中影像資料肯定是已經獲得的了。

     針對上述程式碼,編譯為Release模式後,執行編譯後的EXE,對於3000*4000*3的彩色影像,耗時約480ms,如果你是在IDE的模式先運行,記得一定要在選項--》調試--》常規裡不勾選   在模組加載時取消JIT優化(僅限託管)一欄。

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

     上述代碼中的填充克隆圖數據時並沒有新建一副圖,然後再填充其中的圖像數據,而是直接填滿一個數組,圖像其實不就是一片連續內存加一點頭信息嗎,頭信息已經有了,所以只要一片內存就夠了。

     克隆資料的填入採用了系統Buffer.BlockCopy函數,類似我們先前常用CopyMemory,速度非常快。

     為進一步調高執行速度,我們先來看看演算法的關鍵耗時部位的程式碼,也就是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標號,我們追蹤後返現是呼叫了另一個函數:

             0000073c  call        685172A4 

       685172A4 

* 在每個組中看到一個數字組前,都必須執行一個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();
    }</span></p>
<p><span style="font-size: 13px;"></span>     同樣的效果,同樣的圖像,計算用時330ms。 </p>
<p><span style="font-size: 13px;"></span>     我們在來看看相同代碼的匯編碼:</p>
<p class="cnblogs_code"></p>
<p></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>

      生產的匯編碼簡潔,意義明確,對比下少了很多指令。當然速度會快很多。

      注意這段代碼:

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

     如果你把更換為:

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

      程式碼的速度反而比純數組版的還慢,至於為什麼,實踐為王吧,我也沒有去分析,反正我知道有這個結果。你可以參考鐵哥的一篇文章:

                 閒聊.Net類型之public的不public,fixed的不能fixed

  還可以進一步做小動作的的優化,比如movzx eax,byte ptr [esi+edi] 這句話中,esi其實就是數組的基地址,向這樣寫DataCP[SpeedOne] ,每次都會有這個基址+偏移的計算的,如果能實時直接動態控制一個指針變量,使他直接指向索要的位置,則少了一次加法,雖然優化不是很明顯,基本可以達到問中之前所提到的300ms的時間了。具體的代碼可見附件。

      很多人可能對我這些東西不感冒,說這些東西丟給GPU比你現在的.......希望這些朋友也不要過分的打擊吧,每個人都有自己的愛好,我只愛好CPU。

更多PhotoShop演算法原理解析系列 -  風格化-尋找邊緣。相關文章請關注PHP中文網!


 

 

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn