3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

WPFで映像比較ツールを作った

Last updated at Posted at 2022-12-07

2つの映像ファイルを同時に再生して簡単な比較ができるツールを作りました。

narabemi.gif

映像変換やコンピュータビジョン系の処理結果をサクッと見比べるのに便利だと思います。
世の中に似たようなツールはいろいろありますがあまりお手軽ではないため自作しました。

Windows用です。

できること

  • 映像の再生位置を同期させる
  • 画を見比べる
    • 左右に並べて見比べる
    • スライダーを動かして見比べる
  • 映像のブレンド方法をカスタマイズする(HLSLシェーダ)
  • 字幕表示(.srt)

できないこと

ガチ比較用ではないため、

  • フレームアキュレート、ピクセルパーフェクトな比較
  • 正確な色の再現

はできません。
また、下記には対応できていません。

  • タイムコードなどメタデータの比較
  • ループ再生
  • プレイリスト
  • 3つ以上の映像の比較

あと結構不安定です。

技術的なこと

利用ライブラリ

C#とXAMLで書かれたWPFアプリケーションで、以下のようなライブラリを利用しています。

映像のブレンドのしくみ

WPFの機能を使ってピクセルシェーダでブレンドしています。

以下のようなシェーダを用意し、input0input1 の2つのテクスチャを水平スライダーの割合 ratio に基づいてブレンドしています。

// Shaders/blend.hlsl
sampler2D input0 : register(s0);
sampler2D input1 : register(s1);
float widthPx : register(c0);
float heightPx : register(c1);
float ratio : register(c2);
float borderWidth : register(c3);
float4 borderColor : register(c4);

float4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 color0 = tex2D(input0, uv);
    float4 color1 = tex2D(input1, uv);
    float4 color = lerp(color0, color1, step(ratio, uv.x));
    return lerp(color, borderColor,
        step(uv.x - (borderWidth / widthPx / 2.0f), ratio) *
        step(ratio, uv.x + borderWidth / widthPx / 2.0f));
}

シェーダに映像を入力するために ShaderEffect クラスの派生クラスを定義し、DependencyProperty で入力テクスチャを2つ定義しています。下記の Input0Input1 プロパティです。

// BlendEffect.cs
public class BlendEffect : ShaderEffect
{
    // 一部抜粋
    public Brush Input0
    {
        get => (Brush)GetValue(Input0Property);
        set => SetValue(Input0Property, value);
    }
    public static DependencyProperty Input0Property = 
        RegisterPixelShaderSamplerProperty(nameof(Input0), typeof(BlendEffect), 0, DefaultSamplingMode);
    
    public Brush Input1
    {
        get => (Brush)GetValue(Input1Property);
        set => SetValue(Input1Property, value);
    }
    public static DependencyProperty Input1Property = 
        RegisterPixelShaderSamplerProperty(nameof(Input1), typeof(BlendEffect), 1, DefaultSamplingMode);
}

この2つのプロパティに映像プレイヤーの描画要素を割り当てています。下記のようにXAML上でバインドしています。

<!-- UI/Windows/MainWindow.xaml -->
<Grid.Effect>
    <narabemi:BlendEffect ShaderPath="{Binding ShaderFilePath}"
                          Width="{Binding ElementName=VideoViewbox, Path=ActualWidth}"
                          Height="{Binding ElementName=VideoViewbox, Path=ActualHeight}"
                          Ratio="{Binding BlendHorizontal}"
                          BorderWidth="{Binding BlendBorderWidth}"
                          BorderColor="{Binding BlendBorderColor}">
        <narabemi:BlendEffect.Input0>
            <VisualBrush Visual="{Binding ElementName=VideoPlayerA, Path=Grid}" />
        </narabemi:BlendEffect.Input0>
        <narabemi:BlendEffect.Input1>
            <VisualBrush Visual="{Binding ElementName=VideoPlayerB, Path=Grid}" />
        </narabemi:BlendEffect.Input1>
    </narabemi:BlendEffect>
</Grid.Effect>

再生位置の同期

既存の映像プレイヤーをそのまま乗っけて利用しているため、同期は完全にとれないものとして再生位置をアバウトに合わせる方式をとっています。映像プレイヤーをメインとサブに分けて、サブの再生位置がずれていたらそのずれに応じてプレイヤーの再生速度を調整し、大きくずれていたらシークします。単純です。

ほか

最初は.NET MAUIで作ろうとしましたが映像プレイヤーのコンポーネントがなかったため諦めました。

なので、WPFなのにライブラリだけ最近のものを使っています😅

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?