Releases: routersys/YMM4-DirectionalColorKey
v1.0.2
v1.0.2 - 方向色分離キー for YMM4
静的アイテムに対して動画出力時に毎フレームの再解析が走り、該当エフェクト領域の処理が著しく重くなる不具合を修正した保守リリースです。解析の発火条件をフレーム番号の進行から入力画素の実変化へ変更し、入力内容とパラメータが不変なフレームでは解析を省くよう変更しました。動画入力でのキーイング動作・アルファスケール決定・前景色推定・スピル除去・UI など、その他の挙動に変更はありません。
不具合修正
1. 静的アイテムの毎フレーム再解析抑止(DirectionalColorKeyEffectProcessor)
DirectionalColorKeyEffectProcessor はフレームごとに Update で入力を解析し、方向クラスタリングとアルファスケール決定、前景フィールド生成を行います。従来は再解析要否を判定する analysisDirty の条件に lastFrame != frame を含めていました。動画出力時はフレーム番号が毎フレーム進むため、入力画素もパラメータも不変な静的アイテムであっても、この条件が常に成立して全フレームで analyzer.Analyze と BuildForegroundField が実行されていました。
Analyze は Lloyd 反復ごとに GPU から CPU への同期読み戻しを伴い、前景伝播も多数反復するため、フレームあたりの同期点が多く処理コストが高くなります。この結果、静的アイテムであっても動画出力時に該当エフェクト領域が急に重くなる症状を確認していました。フレーム番号の進行は入力内容が変化したことの根拠にはならないため、これを再解析の契機とする判定が原因でした。
本リリースでは、フレーム番号を入力内容が不変であることの保証にのみ用い、再解析は入力画素が実際に変化したときに限る方式へ変更しました。
| 項目 | 変更前 | 変更後 |
|---|---|---|
| 再解析の契機 | lastFrame != frame | 入力画素の実変化または境界・パラメータ変化 |
| 読み戻しの契機 | analysisDirty 成立時 | フレーム番号または境界の変化時 |
| 内容変化の判定 | なし | 読み戻し時にステージングと既存バッファを行単位で比較 |
入力の読み戻し時に、ステージングのマップ済みメモリと既存バッファを行単位で比較し、差異があるときのみ再解析を行います。同一フレームを保持している間はフレーム番号が変わらないため読み戻し自体を行わず、保持中のシェーダ専用パラメータ操作時の挙動は従来と同一です。内容比較は前フレーム用の別バッファを設けず既存バッファ上で行うため、メモリ使用量は従来と同一です。
静的アイテムでは入力画素が初フレームから一定であり、方向クラスタ中心の収束結果と前景フィールドが各フレームで同一に決定されるため、本変更後の出力は従来実装と一致します。動画入力では画素がフレームごとに変化するため従来どおり毎フレーム再解析され、キーイング動作に変更はありません。アニメーションしたパラメータは値の変化が analysisDirty の判定に含まれるため、値が変わる限り従来どおり毎フレーム再解析されます。
変更は DirectionalColorKeyEffectProcessor.cs の 1 ファイルに限定されます。
v1.0.1
v1.0.1 - 方向色分離キー for YMM4
ノイズ閾値を 0 に設定したときに方向場へ不正値が混入する不具合を修正した保守リリースです。
DisplacementFieldShader の方向正規化へゼロ除算の防止下限を追加し、変位がゼロおよび
正規化不能な微小長の画素を確実に無効方向として扱うよう変更しました。
キーイング処理・アルファスケール決定・前景色推定・UI など、その他の挙動に変更はありません。
不具合修正
1. 変位場生成のゼロ除算防止(DisplacementFieldShader)
DisplacementFieldShader は各ピクセルの背景色との Oklab 差ベクトルを正規化して方向場を生成します。
従来は変位長 len がゼロ近傍の画素を除外する判定が len < noiseThreshold のみであり、
ノイズ閾値が 0 のとき、背景色と完全一致する変位ゼロの画素が 1f / len のゼロ除算へ到達していました。
方向成分が NaN となり、NaN は DirectionFieldConstants.ValidLengthSquaredThreshold による無効判定を
すり抜けて有効方向として扱われるため、バイラテラル平滑化と k-means クラスタリングへ不正値が伝播していました。
この結果、ノイズ閾値が 0 のときにのみ、被写体の周囲へ薄い色成分が固定距離まで広がり、
明確な境界を伴うキーイング不良として現れる症状を確認していました。
ノイズ閾値が正のときは背景領域の微小変位がこの判定で無効化されるため、症状は閾値 0 のときに限られていました。
本リリースでは無効判定の条件を次のとおり変更しました。
| 項目 | 変更前 | 変更後 |
|---|---|---|
| 無効方向の判定条件 | len < noiseThreshold | len < noiseThreshold |
下限 1e-6f は解析器の Normalize が採用する正規化可否の境界と一致します。
これによりノイズ閾値の値に依存せず、変位がゼロまたは正規化不能な微小長の画素を無効方向として扱います。
ノイズ閾値が正のときの従来挙動には影響しません。
変更は DirectionalColorKeyGpuShaders.cs の 1 ファイルに限定されます。
v1.0.0
v1.0.0 - 方向色分離キー for YMM4
YukkuriMovieMaker4 向けの方向色分離キーエフェクトプラグインの初回リリースです。
除去する背景色を基準に、各ピクセルの Oklab 色空間における色変位方向を算出し、
方向場のクラスタリングによって複数の前景層を分離してキーイングするエフェクトです。
ComputeSharp による GPU コンピュートシェーダーを活用した方向場生成・バイラテラル平滑化・
k-means クラスタリング・前景色伝播・HLSL ピクセルシェーダーによる最終合成・
3 種類のアルファスケール決定モード・色差直線モデルによる前景色推定とスピル除去・
フレーム間インクリメンタル再計算・8 言語対応 UI を備えた映像エフェクトプラグインです。
新機能
1. エフェクト定義(DirectionalColorKeyEffect)
DirectionalColorKeyEffect は VideoEffectBase を継承します。
[VideoEffect] 属性は以下のパラメーターで宣言されます。
- 表示名:
Texts.DirectionalColorKeyEffectName(ローカライズキー) - カテゴリー:
VideoEffectCategories.Composition - 検索タグ:
"directional color key"・"dcsk"・"chroma key"・"方向クロマキー"・"色分離キー"・"変位方向キー" IsAviUtlSupported = falseにより AviUtl 向け EXO 出力は非対応です。ResourceType = typeof(Texts)でローカライズリソースを指定します。
LabelプロパティはTexts.DirectionalColorKeyEffectNameの固定値を返却します。
公開プロパティは以下のとおりです。すべて Texts.DirectionalColorKeyGroupName グループに属し、Order の昇順で表示されます。
BackgroundColor(Color、デフォルトColor.FromRgb(0, 255, 0)、Order = 100):
[ColorPicker]で表示されます。
除去する背景色を指定します。Setによる変更通知を伴うフィールドプロパティとして実装されます。ClusterCount(Animation、デフォルト1、内部範囲1〜4、Order = 110):
[AnimationSlider("F0", "", 1, 4)]でスライダー操作範囲 1〜4 として表示されます。
方向クラスタリングで分離する前景層の最大数を制御します。ScaleMode(DirectionalColorKeyScaleMode列挙型、デフォルトPhysical、Order = 120):
[EnumComboBox]でドロップダウンとして表示されます。
アルファのスケール決定戦略を指定します。ForegroundColor(Color、デフォルトColor.FromRgb(255, 255, 255)、Order = 130):
[ColorPicker]で表示されます。
[ShowPropertyEditorWhen(nameof(ScaleMode), DirectionalColorKeyScaleMode.Foreground)]により、
スケール決定が前景色指定のときのみ表示されます。
不透明部分の前景色ヒントを指定します。OpaquePercentile(Animation、デフォルト99、内部範囲0〜100、Order = 140):
[AnimationSlider("F1", "%", 0, 100)]で表示されます。
[ShowPropertyEditorWhen(nameof(ScaleMode), DirectionalColorKeyScaleMode.Opaque)]により、
スケール決定が不透明基準のときのみ表示されます。
不透明とみなす射影値の累積百分位を指定します。NoiseThreshold(Animation、デフォルト0.02、内部範囲0〜1、Order = 150):
[AnimationSlider("F3", "", 0, 0.2)]でスライダー操作範囲 0〜0.2 として表示されます。
背景とみなす変位の大きさを制御します。SigmaColor(Animation、デフォルト0.1、内部範囲0.001〜1、Order = 160):
[AnimationSlider("F3", "", 0.001, 0.5)]でスライダー操作範囲 0.001〜0.5 として表示されます。
方向場平滑化の色類似度の幅を制御します。EdgeSoftness(Animation、デフォルト0、内部範囲0〜100、Order = 170):
[AnimationSlider("F1", "%", 0, 100)]で表示されます。
低アルファ側の足切り量を制御します。SpillStrength(Animation、デフォルト50、内部範囲0〜100、Order = 180):
[AnimationSlider("F1", "%", 0, 100)]で表示されます。
前景色への背景色被りの抑制量を制御します。DespillBias(Animation、デフォルト0、内部範囲0〜1、Order = 190):
[AnimationSlider("F3", "", 0, 0.5)]でスライダー操作範囲 0〜0.5 として表示されます。
スピル除去を開始する彩度の下限を制御します。OutputForeground(bool、デフォルトtrue、Order = 200):
[ToggleSlider]でトグルとして表示されます。
オフにするとマスクを確認できます。Setによる変更通知を伴うフィールドプロパティとして実装されます。
CreateExoVideoFiltersは空のIEnumerable<string>を返却し、EXO 向けフィルター出力は実装されていません。
CreateVideoEffect(IGraphicsDevicesAndContext devices) は
new DirectionalColorKeyEffectProcessor(devices, this) を返却します。
GetAnimatables は ClusterCount・OpaquePercentile・NoiseThreshold・SigmaColor・EdgeSoftness・SpillStrength・DespillBias を列挙します。
2. スケール決定モード定義(DirectionalColorKeyScaleMode)
DirectionalColorKeyScaleMode は public enum として宣言され、3 個の値を持ちます。
| 列挙値 | 値 | 表示名キー |
|---|---|---|
Physical |
1 | DirectionalColorKeyScaleModePhysicalName |
Opaque |
2 | DirectionalColorKeyScaleModeOpaqueName |
Foreground |
4 | DirectionalColorKeyScaleModeForegroundName |
各列挙値は [Display] 属性で表示名・説明のローカライズキーと ResourceType = typeof(Texts) を指定します。
3. カスタムシェーダーエフェクト(DirectionalColorKeyCustomEffect)
DirectionalColorKeyCustomEffect は D2D1CustomShaderEffectBase を継承します。
[CustomEffect(2)] 属性により入力テクスチャ 2 枚のカスタムエフェクトとして宣言されます。
公開プロパティとして以下の 12 個を持ちます。
| プロパティ | 型 | アクセサー |
|---|---|---|
Cluster0 |
Vector4 |
GetVector4Value / SetValue |
Cluster1 |
Vector4 |
GetVector4Value / SetValue |
Cluster2 |
Vector4 |
GetVector4Value / SetValue |
Cluster3 |
Vector4 |
GetVector4Value / SetValue |
BackgroundLab |
Vector3 |
GetVector3Value / SetValue |
NoiseThreshold |
float |
GetFloatValue / SetValue |
SpillStrength |
float |
GetFloatValue / SetValue |
EdgeSoftness |
float |
GetFloatValue / SetValue |
DespillBias |
float |
GetFloatValue / SetValue |
OutputForeground |
float |
GetFloatValue / SetValue |
BackgroundChromaDir |
Vector3 |
GetVector3Value / SetValue |
ClusterCount |
int |
GetIntValue / SetValue |
各クラスタの Vector4 は xyz に方向ベクトル、w にアルファスケール λ を格納します。
EffectImpl 内部クラス
ConstantBuffer 構造体
[StructLayout(LayoutKind.Sequential)] で宣言された ConstantBuffer 構造体は
以下のフィールドを宣言順に保持します。
| フィールド | 型 | 役割 |
|---|---|---|
Cluster0 |
Vector4 |
クラスタ 0 の方向(xyz)とスケール λ(w) |
Cluster1 |
Vector4 |
クラスタ 1 の方向(xyz)とスケール λ(w) |
Cluster2 |
Vector4 |
クラスタ 2 の方向(xyz)とスケール λ(w) |
Cluster3 |
Vector4 |
クラスタ 3 の方向(xyz)とスケール λ(w) |
BackgroundLab |
Vector3 |
背景色の Oklab 座標 |
NoiseThreshold |
float |
背景とみなす変位の大きさ |
SpillStrength |
float |
背景色被りの抑制量 |
EdgeSoftness |
float |
低アルファ側の足切り量 |
DespillBias |
float |
スピル除去を開始する彩度の下限 |
OutputForeground |
float |
前景出力フラグ(0 または 1) |
BackgroundChromaDir |
Vector3 |
背景色の彩度方向 |
ClusterCount |
int |
有効クラスタ数 |
Properties 列挙型
| 列挙値 | 値 |
|---|---|
Cluster0 |
0 |
Cluster1 |
1 |
Cluster2 |
2 |
Cluster3 |
3 |
BackgroundLab |
4 |
NoiseThreshold |
5 |
SpillStrength |
6 |
EdgeSoftness |
7 |
DespillBias |
8 |
OutputForeground |
9 |
BackgroundChromaDir |
10 |
ClusterCount |
11 |
各プロパティは対応する [CustomEffectProperty(PropertyType.XXX, (int)Properties.XXX)] 属性で宣言され、
setter 内で UpdateConstants を呼び出します。
コンストラクター
base(ShaderResourceUri.Get("DirectionalColorKey")) を呼び出します。
これによりコンパイル済みシェーダーオブジェクト(.cso)が WPF リソースとして読み込まれます。
UpdateConstants
drawInformation?.SetPixelShaderConstantBuffer(_cb) を呼び出して
ConstantBuffer 構造体をピクセルシェーダーの定数バッファーレジスター b0 へ転送します。
MapInputRectsToOutputRect
出力矩形を inputRects[0] と同一に設定し、outputOpaqueSubRect を default とします。
MapOutputRectToInputRects
すべての入力矩形を出力矩形と同一に設定します。
4. HLSL ピクセルシェーダー(DirectionalColorKey.hlsl)
シェーダーモデル ps_5_0・エントリーポイント main でコンパイルされます。
リソース宣言
Texture2D InputTexture : register(t0):入力テクスチャ(スロット 0)Texture2D ForegroundTexture : register(t1):前景色フィールドテクスチャ(スロット 1)SamplerState InputSampler : register(s0):サンプラー(スロット 0)
定数バッファー宣言
cbuffer constants : register(b0) に以下のフィールドを packoffset 指定で格納します。
| packoffset | フィールド |
|---|---|
c0 |
clusters[4](float4) |
c4.x |
backgroundLab(float3) |
c4.w |
noiseThreshold(float) |
c5.x |
spillStrength(float) |
c5.y |
edgeSoftness(float) |
c5.z |
despillBias(float) |
c5.w |
outputForeground(float) |
c6.x |
backgroundChromaDir(float3) |
c6.w |
clusterCount(int) |
色空間変換ヘルパー
SrgbToLinear・LinearToSrgb・LinearToOklab・OklabToLinear の各関数を定義します。
Oklab 変換は係数行列によるベクトル演算と立方根(順方向)・三乗(逆方向)で実装されます。
main 関数
入力セマンティクスは SV_POSITION(pos)・SCENE_POSITION(posScene)・
TEXCOORD0(uv0)・TEXCOORD1(uv1)です。
処理は以下の手順で行われます。
InputTexture.Sampleで入力ピクセルを取得し、アルファが 0 以下なら入力をそのまま返却します。- ストレートアルファの sRGB を線形 RGB を経て Oklab へ変換します。
- 背景色との Oklab 差ベクトル
dの長さがnoiseThreshold * 0.5未満なら透明を返却します。 ForegroundTextureのサンプルが有効(アルファ > 0.5)な場合、観測色を背景色と種前景色の
線形補間とみなしてアルファを直接解きます。直線からの残差が許容内であればこの結果を採用します。- 前景フィールドが未解決の場合、各クラスタへの射影
dot(d, clusters[c].xyz)を比較して
最大射影クラスタを選び、射影が 0 以下なら透明を返却します。 - 射影をスケール λ で割った方向アルファと、背景彩度軸に基づく中性アルファの最大値をアルファとします。
edgeSoftnessによる低アルファ側の足切りを適用し、0 以下なら透明を返却します。outputForegroundが 0.5 未満の場合はアルファをグレースケールのマスクとして出力します。- 前景出力時は
backgroundChromaDir.yz方向へのスピル除去を適用し、
前景 sRGB にアルファを乗じたプリマルチプライ済み色を出力します。
5. GPU コンピュートシェーダー群(DirectionalColorKeyGpuShaders.cs)
ComputeSharp を使用した GPU コンピュートシェーダーとして実装されます。
すべてのシェーダーは [GeneratedComputeShaderDescriptor] 属性で宣言されます。
定数定義
DirectionSmoothConstants 静的クラスは平滑化のタイル定数を定義します。
| 定数名 | 値 |
|---|---|
GroupSize |
8 |
Radius |
4 |
TileSize |
GroupSize + Radius * 2(= 16) |
TileCount |
TileSize * TileSize(= 256) |
SpaceTableStride |
Radius * 2 + 1(= 9) |
SpaceTableCount |
SpaceTableStride * SpaceTableStride(= 81) |
DirectionFieldConstants 静的クラスは方向場の有効判定定数を定義します。
| 定数名 | 値 | 用途 |
|---|---|---|
ValidLengthSquaredThreshold |
0.25f |
方向ベクトルの長さ 2 乗がこの値未満なら無効方向とみなす |
DisplacementFieldShader
[ThreadGroupSize(DefaultThreadGroupSizes.XY)] で宣言されます。
各ピクセルの BGRA から sRGB を復元し、線形 RGB を経て Oklab を colorLab へ書き込みます。
背景色との Oklab 差を求め、長さが noiseThreshold 未満なら無効方向、それ以外は正規化方向を
directions へ書き込みます。アルファ 0 のピクセルは色・方向ともにゼロを書き込みます。
DirectionSmoothShader
[ThreadGroupSize(DefaultThreadGroupSizes.XY)] で宣言されます。
半径 4 のタイルと色タイルをグループ共有メモリへ読み込み、空間ガウシアン重みの事前計算テーブルを
構築します。中心方向が有効な場合、近傍方向のうち中心との内積が正かつ有効なものを、
空間重み・色類似度ガウシアン重み・内積を掛けた重みで加重平均し、正規化して targetDirections へ
書き込みます。有効近傍が存在しない場合は中心方向を維持します。
ChangeSeedShader
[ThreadGroupSize(DefaultThreadGroupSizes.XY)] で宣言されます。
現フレームの BGRA と前フレームの BGRA を比較し、異なる場合に seedMask[index] = 1、
同一の場合に 0 を書き込みます。
DilateHorizontalShader / DilateVerticalShader
ともに [ThreadGroupSize(DefaultThreadGroupSizes.XY)] で宣言されます。
それぞれ水平方向・垂直方向に reach の範囲でマスクの膨張(いずれかが非 0 なら 1)を行います。
RegionDirectionSmoothShader
[ThreadGroupSize(DefaultThreadGroupSizes.XY)] で宣言されます。
DirectionSmoothShader と同じバイラテラル平滑化を行いますが、`compu...