シリーズ1、シリーズ2のようなタイトルを書かないのは、いつまで続けられるかわからないからです。私は、物事を明確に表現したり、言語を豊かにしたりする能力にあまり恵まれていないことを知っています。そして、一つのコードに対して、基本原理から「暫定実装」、最適化速度などのプロセスを丁寧に言葉で記述する必要があり、これは容易ではないでしょう。
私が習得したPhotoshopのアルゴリズムの一部は100%正しいとは言えませんが、実行効果を見る限り、全体的な方向性としては間違いなく問題ありません。
現在、私は他の人の記事、オープンソースコード、そして私自身の考えから、100 近くの PS アルゴリズムを知っているかもしれません。時間が許せば、私はこれらのことをゆっくりと整理していきますが、結局のところ、これらのアルゴリズムは多くの人の目には研究価値がありません。当然のことですが、自己評価と自己満足の手段として使用させてください。
今日は、エッジ探索アルゴリズムについて話します。原理を説明しても読まない人も多いかもしれませんが、じっくり勉強した人も数人はいます。
まずレンダリングを投稿しましょう:
原則: 一般的な Sobel エッジ オペレーターの結果を反転するだけです。
読み続けてもらうために、まずコードの実行速度をあげます: 3000*4000*3 のデジタル画像の場合、処理時間は 300ms です。
Baidu からいくつかの写真をコピーしてアドレスを変更した後:
ここで A は入力画像であり、G を A の出力画像として使用するだけです。最後のステップは次のとおりです: G= 255-G、エッジ検索アルゴリズムです。
エッジ検出アルゴリズムに問題があります。プロの画像処理ソフトウェアとして、日常のコード処理では、多くの人が 4 つのエッジのピクセルを無視します。 、これ しかし、それは最も基本的な原則に違反します。エッジの個別のコード処理は、エンコーディングに冗長で面倒な問題をもたらします。問題を解決する最も簡単かつ効率的な方法は、センチネル境界を使用することです。
多くの特殊効果アルゴリズムを作成したことのある人は、単一ピクセルを処理するアルゴリズムを除いて、元の画像のバックアップが必要ないことを知っているはずです (ドメイン情報を必要とするアルゴリズムは必ずしも必要ではありません)。アルゴリズムの性質上、ピクセルは前のステップで変更されており、アルゴリズムの現在のステップでは変更されていないピクセル値が必要であるため、このアルゴリズムは通常、計算中に元のイメージのクローンを作成します。必要なドメイン情報がクローンデータから読み取られます。クローン作成プロセスが完全なクローンではなく、適切な境界を拡張してクローンを作成する場合、上記の境界処理の問題を解決することができます。 たとえば、サイズが 19×14 ピクセルの以下の画像の場合、バックアップ画像は上下左右に 1 ピクセルずつ拡張され、エッジ値が埋め込まれて 21 になります。 ※16号サイズ:
このように、オリジナル画像の3*3領域のピクセルを計算する際に、展開されたクローン画像の対応点からサンプリングすることで、画像範囲内に収まらないという問題が発生せず、判定が大幅に少なくなります。エンコーディングと可読性が強化されます。
計算速度の点で、上記の計算式 G に平方根演算があることに気付きました。これは、画像データの特殊性により、整数でなければなりません。ルックアップ テーブルを使用して速度を最適化できますが、これにはテーブルの作成を考慮する必要があります。
この記事の具体的な問題については、2 つのステップで説明します。まず、ルート記号の下で考えられるすべての状況に対するルックアップ テーブルを確立します。 GXとGYの計算式を見て、両者の二乗和の最大値がいくらになるか少し考えてみるとよいでしょう。 2 番目: 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 次元配列を使用して実装されます。画像データは事前に取得されている必要があるため、タイミング部分では画像データの取得と更新は考慮されません。実際の画像処理プロセスで処理されました。
上記のコードの場合、リリース モードでコンパイルした後、コンパイルされた EXE を実行します。3000*4000*3 カラー イメージの場合、最初に IDE モードで実行する場合は、必ずこのオプションを選択してください。 --》デバッグ--》一般に、モジュールをロードするときに、[JIT 最適化をキャンセル (ホスティングのみ)] 列のチェックを外します。 上記のコードでクローン図のデータを埋めてから、そこに画像データを埋め込むとき、新しい画像はありませんが、ヘッダー情報はすでに存在するので、メモリの一部だけが存在します。十分。
クローン データの充填には、システム Buffer.BlockCopy 関数が使用されます。これは、以前使用していた CopyMemory に似ており、非常に高速です。
実行速度をさらに向上させるために、まずアルゴリズムの重要な時間のかかる部分のコード、つまり (X = 0; の内部のコード) を見てみましょう。コード:
<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 です。追跡した後、別の関数を呼び出します:
685172A4
各配列要素を取得する前に、cmp 命令と jae 命令を実行する必要があることが分析からわかります。これは、配列の添字が範囲外かどうかを判断するために同様のことを行うためだと思います。アルゴリズムが境界を越えないことを保証できれば、コードのこの部分は非常に役立ちますが、業務が遅れてしまうことはありませんか?
そのためにはC#で直接ポインタを使ってアルゴリズムを実装する必要があると思います。C#にはアンセーフモードとポインタがあるので非常に便利で、ポインタの表現は*か[]のどちらでも可能です。 as * (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(); }
同じ効果、同じ画像、計算時間は 330 ミリ秒です。
同じコードのアセンブリ コードを見てみましょう:
<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]) {
実際、コードの速度が純粋な配列バージョンよりも遅い理由については、私はそれを分析したことがありません。 Tie Brother の記事を参照してください: パブリックのチャット .Net タイプは修正できません
もちろん、MOVZX EAX、バイト PTR [esi+edi] などの小さな動きに対してさらに最適化することもできます。この文の esi は実際には配列のベースアドレスです。 このように DataCP[SpeedOne] を書くと、ベースアドレス + オフセットが毎回計算されます。ポインタ変数をリアルタイムで直接制御できる場合は、make します。要求された位置を直接指すことは、追加が 1 つ少ないことを意味しますが、最適化はあまり明らかではありませんが、基本的には質問で前述した 300 ミリ秒の時間に達する可能性があります。特定のコードは添付ファイルにあります。
多くの人は私のこれらのことに興味がなく、GPU にこれらのことを投げた方が今持っているものよりも優れていると言うかもしれません...これらの友人があまり攻撃しないことを願っています。 . CPUが好きなだけです。
その他の PhotoShop アルゴリズム原理分析シリーズ - スタイライゼーション - エッジの検索。関連記事については、PHP 中国語 Web サイトに注目してください。