バイキュービック法で画像の拡大してみようとしたけど、できたようなできていないような感じになった、難しすぎた
前回のバイリニア法は
バイキュービックは
距離に応じた重み付けは曲線になる
1ピクセル先までだから距離の最大は1になる
重みは距離に反比例っていうのか?距離が離れるほど重みは下がる
下がり方は直線だからわかりやすかった
バイキュービックで重みを求める式は
使う式は3つあるけど実際にはA,Bの2つかなあ
距離が0以上1未満のときはAの式
距離が1以上2未満のときはBの式
画質を決める定数を-1にした場合で距離が0.1のときの重みは
距離が0.1だから使う式はA
(-1+2)*(0.1*0.1*0.1)-(-1+3)*(0.1*0.1)+1=0.981
とかになって
定数-1と-0.5のとき距離0から最大の2までを0.1刻みごとの重みを表にすると
距離0から1までの重みははだんだん小さくなって1で0になる、これが式A
1より離れるとマイナスになるのは式Bなんだよねえ
不思議だわ
横方向だけで計算してみる
拡大後のあるピクセルのx座標が元座標だと9.6だった場合に、参照する横範囲は
それぞれの距離は
1.6, 0.6, 0.4, 1.4
使う式は
B, A, A, Bになる
ピクセルの値が100, 200, 150, 50で
画質を決める定数を-1にしたとき補間される値は194
縦横で計算してみる
これであっているかはわかんないけど、値を見ると大きく外してはいないみたいねえ
参照範囲が元画像の外側になる場合、つまり画像の周縁部の重みはどうすればいいのか
-1ピクセルってピクセルはないから値もない
右に見ても、もしもと画像の幅が1ピクセルだった場合は存在しない座標になる
値が200の2x1ピクセルを10x1に拡大、値は全部200のピクセルなので拡大しても200になりそうだけど?
元画像の外側は重みを0にして計算した場合
左2が遠い左、左1が近い左ピクセルになる
横2ピクセルだから左2と右2は存在しないピクセルなので重み(ウェイト)を0にしている
結果の補間をグラフにすると
画像だと
元画像の全部のピクセルの値が200なんだから、拡大後も全部200になりそうだけどバイキュービックの場合はこれで正しい?
なので普通にバイキュービックで拡大すると画像の周縁部は色が明るくなる
これを2倍すると
拡大すると
こうすると全部200になって
周縁部の色が白(明る)くならなくなった
これでいいのかなあ
バイリニア法と比較してみる
バイキュービックの画質のaは-1
上がバイリニア法、下がバイキュービック
バイリニア
バイキュービック
縮小0.5倍
バイリニア
バイキュービック
2x2の
□■
■□
を10倍してから最近傍補間法で8倍くらい
バイリニア
バイキュービック
バイリニア
バイキュービック
おなじに見える…けど
日付のところはバイキュービックのほうがきれい
これを2倍
バイキュービックのほうがくっきりした感じになる、輪郭が強調される感じ
同時に輪郭部分のもやもやしたノイズも強調されている
画質を決める定数aを変えてみる
今回の画像だと0がいいかなあ
-1~0がよく使われるそう
今回のもどこか計算間違っているかも
拡大率を上げていくとブロック状になる
普通なら下のバイリニア法みたいにグラデーションになるはずなんだよなあ
それに計算量も増えたから処理時間もものすごく増えて
256x192をカラーで6倍にするだけでも約十秒もかかった
PixelFormat.gray8のBitmapSourceを指定したスケールと画質のaで拡大縮小したBitmapSourceを返す
//グレースケール
private BitmapSource F1バイキュービックグレースケール2(BitmapSource source, double xScale, double yScale, double factor)
{
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 * yScale), MidpointRounding.AwayFromZero));
int nW = (int)(Math.Round((w * xScale), 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[] dX = new double[4];//xの距離
double[] dY = new double[4];
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の距離
dY[0] = 1 + dy; dY[1] = dy; dY[2] = 1 - dy; dY[3] = 2 - dy;
//4x4のウェイト取得
for (int i = 0; i < 4; ++i)
{
wY[i] = GetWeight(dY[i], factor);
}
for (int x = 0; x < nW; ++x)
{
motoX = x * yoko倍率;//注目座標の元画像での座標
dx = motoX % 1;
//4x4のxの距離
dX[0] = 1 + dx; dX[1] = dx; dX[2] = 1 - dx; dX[3] = 2 - dx;
for (int i = 0; i < 4; ++i)
{
wX[i] = GetWeight(dX[i], factor);
}
//元画像の4x4の部分の値取得
pValues4x4 = GetPixesValue(motoX, motoY, pixels, stride, w, h);
//ウェイトと値をかけた値の合計
hokan = 0;
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 4; ++j)
{
hokan += pValues4x4[j, i] * wX[j] * wY[i];
}
}
//合計が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;
}
どこか足りないのか逆に余計なことをしているのかわからん
こんなこと書くよりも
WPFで画像をきれいに拡大縮小するならRenderTransformのScaleTransformを使って
表示方法を
BitmapScalingModeをFantかHighQualityを指定するほうがいろいろ速い
コード
失敗?アプリ
関連記事
2018/4/17、前回
バイリニア法で画像の拡大縮小 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15464617.html
2018/04/16、前々回
最近傍補間法で画像の拡大縮小試してみた ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15462921.html