このフィルターは私が PS SDK を使って開発したもので、フィルター アルゴリズムの提案者は不明かもしれませんが、このアルゴリズムの主な参照ソースは Filters.cpp です。ジェイソン・ウォルトマン著(2001年4月18日)。さらに、C# 言語で書かれた別の国産ソフトウェアである PhotoSprite (バージョン 3.0、2006、Lian Jun 著) の油絵フィルターのアルゴリズムも前者 (または他の類似コード) から引用する必要があります。このフィルタ アルゴリズムを研究する際、私は主に前者の C++ コードを参照しました。この記事でのアルゴリズムの概念的な説明は私の理解と解釈に属します。しかし、このアルゴリズムの効率はそれほど高くありません。テンプレート サイズに関する時間計算量は O (n^2) から線形計算量 O (n) に改善されました。ピクセル数は一定ですが、同じテストサンプル (特定の 1920 * 1200 ピクセルの RGB 画像) では、同じパラメータの処理速度が約 35 秒から約 3 秒に短縮されます。処理速度が約10~12倍(目安)に向上します。
この記事では主にPhotoshopの油絵効果フィルター(OilPaint)を公開しています。このアルゴリズムは私が提案したものではありません。この記事の参考文献を参照してください。このフィルターはC#で開発された国産ソフトウェアPhotoSpriteで見ることができます。私は 2010 年にこのフィルターの開発を手伝ってほしいと頼まれ、今では数日ほどかけて開発し、無料で利用できるようにしました。
(1) 油絵フィルターのアルゴリズムの概念説明
これが、FilterExplorerのソースコードを読んで得た理解です。このフィルターには 2 つのパラメーターがあります。1 つはテンプレートの半径 (radius)、テンプレートのサイズは (半径 * 2 + 1) * (半径 * 2 + 1) のサイズです。つまり、現在のピクセルを中心として外側に拡張されます。半径によるピクセルの長方形領域を探索範囲として、仮に「テンプレート」と呼びます(実際にはこのアルゴリズムはガウスぼかしやカスタムフィルターなどの標準的なテンプレート手法ではなく、処理プロセスが似ているだけです)ので、後で紹介する最適化を実装できます。
もう 1 つのパラメータは滑らかさで、実際にはグレースケール バケットの数です。ピクセルのグレースケール/明るさ (0 ~ 255) が滑らかさの間隔に均等に分割されていると仮定し、ここでは各間隔をバケットと呼びます。そのため、多くのバケットがあり、一時的にバケット配列 (バケット) と呼ばれます。
このアルゴリズムは、画像上の各ピクセルを走査し、現在位置(x,y)のピクセルについて、テンプレート範囲内のすべてのピクセルをグレースケール化、つまり画像をグレースケール画像に変換し、さらにピクセル値を離散化します。つまり、ピクセルのグレーレベルが該当する間隔に従って、テンプレート内のピクセルが対応するバケットに順番に入れられます。次に、そのバケットに入るピクセル数が最も多いバケットを見つけ、そのバケット内のすべてのピクセルの色を平均して、位置 (x, y) の値として求めます。
上記のアルゴリズムの説明は、以下の模式図で表されます。中央の画像は、元の画像からグレースケール + 離散化 (Photoshop のトーン分離に相当) した結果であり、小さなボックスはテンプレートを表します。以下に示すのはバケット配列 (8 つのバケット、つまり 0 から 255 までのグレー値が 8 つの間隔に離散化されている) です。
2)外国人コードの効率を向上させる外国人の既存のコードの効率を改善するため
既存のコードをPSフィルターに移植することは難しくありません1〜2日ほどかかりました。デバッグは基本的に空いた時間で完了しました。しかし、外国人のソースコードを読んでいると、オリジナルのコードは十分に効率的ではないことが明らかに感じられました。このアルゴリズムは画像を 1 回走査するだけで完了でき、各ピクセルの処理は定数時間であるため、複雑さはピクセル数 (画像の長さ * 画像の幅) に対して O(n) ですが、元のコードの係数は一定です。たとえば、ピクセル結果が計算されるたびに、テンプレート範囲内のピクセルのグレースケールを再計算してバケットに入れる必要があり、実際には多数の反復計算が必要になります。
2.1 この目的のために、私の最初の改善は、PS 内の現在の画像パッチ全体をグレースケールして離散化し、テンプレートを使用してパッチを走査するときにグレースケールを計算する必要がないようにすることです。繰り返し離散化されます。これにより、アルゴリズムの実行速度がおよそ 2 倍になります (特定のサンプルでは、処理速度が 20 秒以上から約 10 秒に増加します)。
🎜 🎜🎜 2.2 しかし、この速度の向上はまだ十分ではありません。そこで、さらに重要な最適化をもう 1 つ行いました。それは、テンプレート サイズの複雑さを正方形から線形の複雑さに減らすことでした。これは、テンプレートが現在の行間で左から右に移動すると、結果内のテンプレートの中央 (2 つの隣接するテンプレートの交差部分) のピクセルの統計が変化しないという事実に基づいています。左端の列のみがテンプレートから移動し、右端の列がテンプレートに入ります。そのため、画像を横断するときにテンプレートの中央のピクセルを気にする必要はありません。処理する必要があるのは、画像の 2 つの端だけです。テンプレート。下の図に示すように (半径は 2、テンプレートのサイズは 5 * 5 ピクセル): 🎜🎜 🎜
パッチの右端に到達したら、復帰や改行のように行頭にリセットするのではなく、テンプレートを 1 行下に移動し、末尾に入力します。次の行を選択してから左に移動すると、テンプレートの軌道が曲がりくねった遠回りなステップ軌道になります。この改善後は、ピクセルをトラバースするときに、テンプレートの 2 つのエッジ ピクセルを処理するだけで済みます。このようにして、テンプレートのサイズ (パラメータの半径) が O(n^2) から O(n) に減少し、それによってアルゴリズムの動作速度が大幅に向上し、最適化 2.1 と組み合わせることで、アルゴリズムの動作速度が最終的に向上しました 11。 (この値は単なる概算であり、多数のサンプルでテストされていません) 大きな画像に対する最適化されたアルゴリズムの処理時間も許容できるようになりました。
【注意】 このような最適化が達成できる理由は、フィルターアルゴリズムが標準のテンプレートアルゴリズムではないためであり、その本質はテンプレート範囲内の統計情報を取得すること、つまり結果はテンプレートとは何の関係もありません。ピクセルのテンプレート座標。これは、ある地域の人口数や男女比などの情報を取得したいのと同じです。したがって、上記の方法に従って最適化します。
)) 蛇行と回り道で蛇行ステップで蛇行ステップで - 回り道です… ← ← ← ← ← ←
↓
→ → ...
2.3 radiusのオリジナルコード 範囲制限は(1~5)になっているので、コードを最適化しました。半径を 100 に設定すると、半径の範囲を大幅に広げることができます。半径が大きすぎると、基本的に元の画像が何であるかを確認することができないため、意味がないことがわかりました。 【概要】 改良されたコードは、多数の低レベルのポインター操作や、異なる四角形 (入力パッチ、出力パッチ、テンプレート) 間の座標位置決めを含む、より技術的で挑戦的であり、コードをより読みやすくしています。多少低くなりますが、上記の原則を理解していれば、コードは依然として読みやすいでしょう。さらに、アルゴリズムを改善して、テンプレートを長方形から「円」に変更し、テンプレートの半径とバケットの数の 2 つのパラメーターを画像を走査するときにランダムに変動させることも考えましたが、これらの改善により、 2.2 での改善 最適化が失敗すると、アルゴリズムの速度が低いレベルに戻ります。 (3)マルチスレッド技術を利用してサムネイルの表示効率を向上させ、UIスレッドのインタラクティブ性に影響を与えないようにします この記事の4番目の記事については、ここでは説明しません。ここで私が話しているのは、サムネイルを更新するときの UI インタラクションの改善と、ズームとパンのテクノロジーです。下の写真は、Photoshopでこのフィルターを呼び出したときに表示されるパラメータ設定ダイアログボックスです。ユーザーはスライダー コントロール (TrackBar、スライダーとも呼ばれます) をドラッグするか、後ろのテキスト ボックスに直接入力してパラメーターを変更できます。サムネイルはリアルタイムで更新され、新しいパラメータが反映されます。独自のフィルター実装では、サムネイルの更新処理をダイアログUIと同じスレッドに配置していました。これを行うと、ユーザーがスライダー コントロールを素早くドラッグすると、パラメーターが急速に変化し、UI スレッドがサムネイル データの処理でビジー状態になり、短時間「ブロック」されて、即時に応答できなくなる可能性があります。つまり、スライダー コントロールのドラッグが十分にスムーズではなく、飛び跳ねたり、イライラしたり、鈍くなったりします。また、マウスのドラッグに対するフィードバックも十分に敏感ではありません。
この問題を改善し、UIスレッドに影響を与えないようにするために、時間のかかるサムネイル処理タスクを新しいスレッドに入れて完了させ、スレッドがサムネイルを完了したら通知されるようにする予定です処理中 ダイアログ ボックスのビューが更新されます。トラックバーをドラッグすると、UI スレッドは「サージ」のような制御通知を非常に頻繁に受信します。そのため、後続の UI イベントの到着によって、実行中のスレッド タスクがすぐに終了して終了する必要があります。
フィルタのアルゴリズムを共有関数として抽出し、実際のフィルタの処理とサムネイルの更新がこの関数を共有できるようにしました。 PS によるフィルターの実際の呼び出しとサムネイルの更新中に、フィルター アルゴリズムは実際に「タスクのキャンセル」イベントを定期的に検出する必要があります。たとえば、PS がフィルターを呼び出すときにユーザーが ESC キーを押すと、時間のかかるフィルター操作は直ちに中止されます。サムネイルを更新するときに UI イベントの急増が発生した場合、処理スレッドも迅速に終了できる必要があります。
フィルタのコアアルゴリズム関数では、PSでのフィルタ呼び出し時のキャンセルテストとサムネイル更新時のキャンセルテストの方法が異なるため、定期的に「タスクキャンセル」イベントを検出しています。 , コールバック関数のパラメータ(TestAbortProc)を追加しました。このように、PSが実際の処理でフィルタを呼び出す際には、PSの組み込みコールバック関数を利用してキャンセルイベントを検出しています。 ダイアログボックスのサムネールを更新する際には、キャンセルイベントを検出するために自作のコールバック関数を使用しています。この関数は、処理を待っている新しい UI イベントがあるかどうかを知るためにブール変数を検出します)。
サムネイル処理にはシングルスレッドを使用しています。つまり、新しい UI イベントが到着するたびに、サムネイル処理スレッドが実行されているかどうかを検出する必要があります。実行されている場合は、新しい UI イベントにマークを設定し、スレッドが終了するのを待ってから開始します。前のスレッドが終了した後に新しいスレッドが作成されるため、UI イベントが継続的に到着したときに多くのスレッドを開くのではなく、サムネイルを処理するスレッドが常に 1 つだけになります。この利点は、ロジックが明確で制御しやすいことです。スレッドが多すぎて保守不能な状況に陥ることもありません。欠点は、スレッドがキャンセル イベントを定期的に検出しているにもかかわらず、スレッドの終了に少し時間がかかることです。これにより、UI スレッドにわずかな「一時停止」が発生しますが、サムネイルの更新に比べれば些細な問題です。 UI スレッドの本質的な改善を実現します。
改善後、パラメーター ダイアログ ボックス上の 2 つのスライダーを非常に高速でドラッグできるようになりました。このフィルターのコア アルゴリズムは大量の計算を必要としますが、パラメーター ダイアログ ボックスは依然として非常にスムーズであることがわかります。応答。
(4) サムネイルのズーム・パン機能
実は、ズームやパンに関わらず、サムネイルデータ自体を更新すること自体は難しいことではありません。難しいのは主にサムネイルのパン操作です。これはマウス操作が必要となるためです。これには、非常に確かな Windows プログラミング スキルと、Windows プログラムの基礎となるメカニズムの理解が必要です。
サムネイルをドラッグするには 2 つの方法があります:
4.1 結果画像を直接ドラッグします。
これは2つの方法に分かれます。 1 つは比較的完璧なドラッグ効果ですが、ある程度のスペースと時間を無駄にするという代償が伴い、コーディングも困難です。つまり、サムネイルの入力データを9倍に拡大し、結果画像をメモリ上に取得します。表示すると結果グラフの中央部分のみが表示されます。ドラッグすると、サムネイルに空白部分は表示されません。
もう一つの方法は、ドラッグ中に現在の結果画像のスナップショット(スクリーンショット)を撮り、ドラッグ中にスクリーンショットの結果を画面上の対応する位置に貼り付けるだけです。これは効率的ですが、ドラッグするときにサムネイルの横に空白が表示されるという欠点があります。この方法は、ベクトル描画など、ビューの更新コストが大きい場合によく使用されます。このフィルターに実装したメソッドは、このカテゴリに分類されます。
4.2 画像を元の入力画像にドラッグします。
つまり、ドラッグするときに使用される画像は結果画像ではなく元データになります。これもデータ更新のコストを削減するための妥協方法です。たとえば、この方法は Photoshop の組み込みフィルターであるガウスぼかしで使用されます。サムネイルをドラッグすると、表示されるサムネイルは元の画像であり、プレビュー効果はマウスを放した後にのみ表示されます。これはより経済的で効果的です。元のデータを要求するコストは高くありませんが、サムネイルをフィルターで処理するコストは高いためです。
マウスがクライアント領域外に移動する(負の数になる)可能性があるため、クライアント領域の座標を取得するために LOWORD (lParam) と HIWORD (lParam) を直接使用することはできません (WORD は符号なし数値であるため)。使用する前に、署名付きの番号 (短い) を確認してください。正しい方法は、windowsx.h ヘッダー ファイルのマクロ (GET_X_LPARAM および GET_Y_LPARAM) を使用することです。
(5)このフィルターのダウンロードリンク(添付ファイルには、ユーザーのインストールを簡素化できるPSプラグインインストールツールが含まれています) /私が開発した PS プラグインの最新コレクション (ICO、OilPaint、DrawTable などを含む)
富 groundum メニュー内:Filter - このフィルタは、hoodlum1980 - OilPaint で呼び出されます。
メニューの「ヘルプ」→「プラグインについて」→「OilPaint...」に「バージョン情報」ダイアログが表示されます(見た目は私が開発したICOファイル形式プラグインの「バージョン情報」ダイアログとほぼ同じです)。
メニュー:ヘルプ - システム情報で、「OilPaint」項目が読み込まれているかどうかとそのバージョン情報を確認できます。
(6) それほど重要ではない補足説明
6.1 使用した出力パッチのサイズは 128 * 128 ピクセルです フィルター処理中、Photoshop ステータス バーに進行状況バーが表示されます。 の各ステップは完了を表します。出力パッチの。通常、入力パッチは出力パッチ以上であり、入力パッチのサイズはフィルター パラメーターの半径 (テンプレートの半径を 4 方向に外側に拡張するピクセル距離) に関係します。
6.2 フィルターコアアルゴリズムでは、キャンセルイベントに対する感度を高めるために、現在の行で処理される 16 ピクセルごとにキャンセルを検出します (行のピクセルインデックス & 0x0F == 0x0F)。キャンセルは、(列ループで) 各行が処理された後に 1 回検出されます。ただし、この検出頻度はやや頻繁すぎます。頻度が高すぎると、関数呼び出しのコストが増加する可能性があります。
6.3 同じ画像と同じパラメータを使用して、フィルター、FilterExplorer、PhotoSprite を個別に処理し、Photoshop で比較しました。私のアルゴリズムは FilterExplorer のソース コードに基づいており、そのアルゴリズムを改良したものであるため、私のアルゴリズムは FilterExplorer と同等ですが、より効率的であるため、結果はまったく同じになります。ただし、私のフィルターと FilterExplorer の全体的な効果は PhotoSprite の効果に非常に似ていますが、結果は若干異なります。 PhotoSpriteのコードを確認したところ、画像のグレースケールアルゴリズムの違いが原因でした(PhotoSpriteのグレースケールアルゴリズムをFilterExplorerと同じになるように調整したところ、処理結果は同じになりました)。
PhotoSprite でピクセルをグレースケールする方法は次のとおりです:
gray = ( byte ) ( ( 19661 * R + 38666 * G + 7209 * B ) >> 16 ) ;
In FilterExplorer / In the filter私が開発した、ピクセルのグレースケールに使用するメソッドは次のとおりです:
gray = ( byte ) ( 0.3 * R + 0.59 * G + 0.11 * B ) ;
PhotoSpriteのグレースケールメソッドはfloatになります ポイント乗算は整数乗算に変換されます、効率はわずかに向上する可能性がありますが、ここでのパフォーマンスは重要ではありません。 7.1 FilterExplorer のソース コード。
7.2 Photoshop 6.0 SDK ドキュメント。
7.3 FillRedとIcoFormatプラグインのソースコード、byhoodlum1980(私)。
(8)修正履歴
8.01 [H] Continue呼び出しのフィルターで、グレースケールバケットメモリを割り当てる際に、渡されたメモリサイズが正しくない(グレースケールビットマップサイズの空間に誤って設定されている)バグを修正しました。このバグは、次の条件で発生しやすくなります。ドキュメント サイズが小さすぎる、または半径パラメータが小さく、滑らかさパラメータが大きい場合、これらの要因によりパッチが小さすぎる可能性があります。このとき、グレーのバケット領域の割り当てが実際に必要なサイズよりも小さいため、後続のコードがメモリ制限を超え、PS プロセスが予期せず終了する可能性があります。 2011年1月19日19:01。
8.02 [M] 新しいサムネイルズームボタンと機能を追加し、ズームインおよびズームアウトボタンのコードを最適化して、ズームインおよびズームアウト時のちらつきを軽減しました。 2011年1月19日19:06。
8.03 [M] 新しいサムネイルマウスのドラッグ機能を追加し、さらにコードを調整してサムネイルの四角形を固定し、ズーム時のちらつきを完全に回避しました。 2011年1月19日23:50。
8.04 [L] 新機能、サムネイル上にマウスを移動してドラッグ&ドロップすると、カーソルが伸ばした・掴んだ手の形に変わります。これは、PS の Suite PEA UI Hooks スイートの関数を呼び出すことによって実現されます。つまり、サムネイルに表示されるのは PS 内のカーソルです。 2011年1月20日1:23。
8.05 [M] パラメータダイアログボックスの拡大・縮小ボタンをクリックした際に半径パラメータが誤変換されてしまう不具合を修正しました。このバグにより、ズームイン ボタンをクリックした後にサムネイルが正しく表示されません。 2011年1月20日1:55。
8.06 [L] [バージョン情報] ダイアログ ボックスの URL リンクを SysLink コントロールに調整します。これにより、[バージョン情報] ダイアログ ボックスのウィンドウ プロシージャ コードが大幅に簡素化されます。 2011年1月20日18時12分。
8.07 [L] プラグインインストールアシスタントツールを更新し、一度に複数のプラグインをインストールできるようにしました。 2011年1月20日21時15分。
8.08 [L] サムネイルをスケーリングする際の計算エラー (原因は不明、おそらく浮動小数点計算エラーによるもの) により、端に近い右下隅の画像がサムネイル ビューに変換されない可能性があるため、新しいエラーが翻訳範囲バッファに追加されます。 2011年1月27日。
8.09 [L] 美化: フィルター パラメーター ダイアログ ボックスのズーム ボタンを DirectUI テクノロジを使用するように変更します (新しいクラス CImgButton を追加)。インターフェイスの効果は、元のボタン コントロールを使用するよりも優れています。 2011年2月14日。
8.10 [M] パフォーマンス: プラグインがパラメーター ダイアログ ボックスにあるときに、半径と滑らかさのパラメーターを調整するためのスライダーのインタラクティブなパフォーマンスを調整しました。 2013年4月1日。
Photoshop 油絵効果フィルター関連記事の詳細については、PHP 中国語 Web サイトに注目してください。