午後わてんのブログ

ベランダ菜園とWindows用アプリ作成(WPFとC#)

Lanczos法で画像の拡大してみた

Lanczos法で画像の拡大
前回のバイリニア法とは重みの付け方が違う
 
sinc関数ってのを使っているらしい
sinc関数 - Wikipedia
https://ja.wikipedia.org/wiki/Sinc%E9%96%A2%E6%95%B0

内挿 - Wikipedia
https://ja.wikipedia.org/wiki/%E5%86%85%E6%8C%BF
普通科という文系の高校だったせいか記憶にない…この前の「分散」と同様に習っていないのかも
説明を読んでも微塵も理解できないゾ
 
sinc関数にある数値xを渡した時は
sinc(x) = sin(π*x)/(π*x)
こうで、sinは三角関数の、πはパイで、3.14…
これを使って補間するみたい
 
C#で書くと
//窓関数…?
private double Sinc(double x)
{
return Math.Sin(Math.PI * x) / (Math.PI * x);
}
 
 
 
参照するピクセルの範囲がNで距離がdのときの重みは
  • 距離が0なら 1
  • 距離dがNより遠ければ 0
  • それ以外の時は sinc(d)*sinc(d/N)
これで計算すればいいみたい
 
C#で書くと
 //Lanczosの重み取得
private double GetLanczosWeitgt(double d, int n)
{
if (d == 0) { return 1; }
if (d > n) { return 0; }
return Sinc(d) * Sinc(d / n);
}
 
距離0.1ごとの重みをエクセルでグラフにすると

f:id:gogowaten:20191212151444p:plain

Lanczosの後ろについている数字は参照するピクセルの範囲で2なら上下左右2ピクセルの4x4ピクセル、3なら6x6、4だと8x8とかになると思う
範囲が増えるほどきれいに拡大できるけど計算が大変になるので
よく使われるのは2と3
 
バイキュービックと比べてみる
イメージ 2
よく似ている
バイキュービックの-0.5とLanczos2
バイキュービックの-1.0とLanczos3
はほとんど線が重なっている
 
 
 
 
イメージ 3
これを2倍に拡大
 
Lanczos2
イメージ 4
 
Lanczos3
イメージ 6
バイリニア
イメージ 5
Lanczos5
イメージ 7
バイリニアとLanczosの違いはわかるけど、Lanczosの参照範囲の違いではほとんどわかんない
参照範囲が一番広いLanczos5は変な輪郭が出ている、広ければいいってものでもないのかなあ
 
 
イメージ 8
これを2.6倍
 
Lanczos2
イメージ 9
Lanczos5
イメージ 10
Lanczos2よりきれい…かなあ
 
バイリニア
イメージ 11
ぼやけるけどバイリニアも悪くないなあ
 
前回の
バイキュービックは書き方が良くなかったのか
拡大するとブロック状になってしまったけど
イメージ 12
Lanczosは大丈夫みたい
 
これを2倍
 
Lanczos2
イメージ 13
前回の
 
Lanczos2
イメージ 14
 
バイリニア
 
 
 
Lanczos2で4倍
イメージ 15
 
 
PixelFormatがgray8のBitmapSourceをLanczos2で拡大
//Lanczosで重み取得
private double GetLanczosWeitgt(double d)
{
    if (d == 0) { return 1; }
    if (d > 2) { return 0; }
    return Sinc(d) * Sinc(d / 2);
}
//窓関数…?
private double Sinc(double x)
{
    return Math.Sin(Math.PI * x) / (Math.PI * x);
}
/// <summary>
/// 参照する元画像の4x4ピクセルの値取得,8bitグレースケール画像専用
/// </summary>
/// <param name="motoX">元画像のx座標</param>
/// <param name="motoY"></param>
/// <param name="pixels">元画像のpixels</param>
/// <param name="stride"></param>
/// <param name="w">元画像のpixels.Width</param>
/// <param name="h"></param>
/// <returns></returns>
private int[,] GetPixesValue(double motoX, double motoY, byte[] pixels, int stride, int w, int h)
{
    int[,] pv = new int[4, 4];

    //元座標から1+小数点部分を引いたところが起点(左上)になる
    int mx = (int)(motoX - (motoX % 1)) - 1;//4マスの左
    int my = (int)(motoY - (motoY % 1)) - 1;//4マスの上
    int xx, yy;
    int cx = 0, cy = 0;
    //参照する座標がマイナスなら0座標の値を入れる、
    //参照する座標が右端を超えたら右端の値を入れる
    //参照する座標が最下段を超えたら最下段の値を入れる
    for (int y = my; y < my + 4; ++y)
    {
        for (int x = mx; x < mx + 4; ++x)
        {
            if (y < 0) { yy = 0; }
            else if (y > h - 1) { yy = h - 1; }
            else { yy = y; }

            if (x < 0) { xx = 0; }
            else if (x > w - 1) { xx = w - 1; }
            else { xx = x; }

            pv[cx, cy] = pixels[yy * stride + (xx * 1)];
            cx++;
        }
        cy++;
        cx = 0;
    }
    return pv;
}
//グレースケール限定、Lanczos2
private BitmapSource F1Lanczos2A_Gray(BitmapSource source, double scaleX, double scaleY)
{
    if (source == null) { return null; }
    var wb = new WriteableBitmap(source);
    int h = wb.PixelHeight;
    int w = wb.PixelWidth;
    int stride = wb.BackBufferStride;
    var pixels = new byte[h * stride];
    wb.CopyPixels(pixels, stride, 0);

    //変換後のサイズは四捨五入
    int nH = (int)(Math.Round((h * scaleY), MidpointRounding.AwayFromZero));
    int nW = (int)(Math.Round((w * scaleX), MidpointRounding.AwayFromZero));
    var nWb = new WriteableBitmap(nW, nH, 96, 96, source.Format, source.Palette);
    int nStride = nWb.BackBufferStride;
    var nPixels = new byte[nH * nStride];
    long nP = 0;
    double motoX, motoY;
    double dx, dy;

    double yoko倍率 = (w - 1) / (nW - 1.0f);//変形後から見た倍率は長さで求めているから-1.0している
    double tate倍率 = (h - 1) / (nH - 1.0f);
    double[] wX = new double[4];//ウェイト
    double[] wY = new double[4];
    double hokan = 0;//補間した値;
    int[,] pValues4x4 = new int[4, 4];//元画像の参照する4x4ピクセルの値

    for (int y = 0; y < nH; ++y)
    {
        motoY = y * tate倍率;
        dy = motoY % 1;
        //4x4のyのウェイト取得
        wY[0] = GetLanczosWeitgt(1 + dy, 2);
        wY[1] = GetLanczosWeitgt(dy, 2);
        wY[2] = GetLanczosWeitgt(1 - dy, 2);
        wY[3] = GetLanczosWeitgt(2 - dy, 2);
        for (int x = 0; x < nW; ++x)
        {
            motoX = x * yoko倍率;//注目座標の元画像での座標
            dx = motoX % 1;
            //4x4のxのウェイト取得
            wX[0] = GetLanczosWeitgt(1 + dx, 2);
            wX[1] = GetLanczosWeitgt(dx, 2);
            wX[2] = GetLanczosWeitgt(1 - dx, 2);
            wX[3] = GetLanczosWeitgt(2 - dx, 2);

            //元画像の4x4の部分の値取得
            pValues4x4 = GetPixesValue(motoX, motoY, pixels, stride, w, h);
            //ウェイトと値をかけた値の合計
            hokan = 0;
            double wTotal = 0;
            for (int i = 0; i < 4; ++i)
            {
                for (int j = 0; j < 4; ++j)
                {
                    hokan += pValues4x4[j, i] * wX[j] * wY[i];
                    wTotal += wX[j] * wY[i];//縦横のウェイトを掛け算したのを合計していく
                }
            }
            
            //これ重要、ないとまだら模様になる
            hokan /= wTotal;
            //合計が0から255以外なら切り捨てて収める
            hokan = hokan > 255 ? 255 : hokan < 0 ? 0 : hokan;
            //四捨五入
            hokan = Math.Round(hokan, MidpointRounding.AwayFromZero);
            //変形後のpixelsに入れる
            nP = y * nStride + (x * 1);
            nPixels[nP] = (byte)hokan;
        }
    }
    nWb.WritePixels(new Int32Rect(0, 0, nW, nH), nPixels, nStride, 0);
    return nWb;
}
これで大体あっているんだけど
正確に拡大しようとするとピクセルの幅も考える必要があるらしい、さらに
Lanczos関数による画像の拡大縮小
http://www.maroon.dti.ne.jp/twist/4C616E637A6F73B4D8BFF4A4CBA4E8A4EBB2E8C1FCA4CEB3C8C2E7BDCCBEAE.html
縮小の処理は拡大とは違うとか、画像の端の処理は鏡像?とかいう処理がいいとか
画像の拡大縮小処理ってのは想像していたよりかなり難しいのがわかった
前回までのバイリニア法やバイキュービックでは何も考えずに縮小処理をしていたのでほとんど差が出なかったみたい、しかも間違っていたと
縮小処理は難しすぎたので諦めて、今回は拡大だけの結果を載せた
 
 
参照したところ
Lanczos(ランチョス法)【ついでにSpline36】
https://www.rainorshine.asia/2015/10/12/post2602.html

画像処理 - HexeRein
http://www7a.biglobe.ne.jp/~fairytale/article/program/graphics.html#lanczos

画像の拡大「Lanczos法」: koujinz blog
http://koujinz.cocolog-nifty.com/blog/2009/05/lanczos-76aa.html
 
 
 
コード
アプリ
 
 
関連記事
続きは3年後の2021/04/27 
前回、2018/4/20
バイキュービック法で画像の拡大してみようとしたけど、難しすぎた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15469745.html