午後わてんのブログ

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

WPFだけどRotateTransformを使わずに画像の回転表示をしてみた

今回のアプリのダウンロード先
WPFだけどRotateTransformを使わずに画像の回転表示をしてみた
イメージ 1
処理2 元のピクセル座標を回転行列で変換しただけ
処理3 処理2の位置調整版
処理4 回転後のピクセル座標を回転行列の逆行列で変換
処理5 処理4+ピクセルの値を線形補間でギザギザをなくした
処理6 処理5のカラー版
 
画像の回転は全てのピクセルの座標を変換
使う関数は
変換前座標が(x,y)のとき求める変換後座標を(x',y')とすると
x' = cos * x -sin * y
y' = sin * x +cos * y
らしい
 
回転行列の逆行列を使った関数は
x' = cos * x +sin * y
y' = -sin * x +cos * y
らしい
 
これらを使ってエクセルで見てみる
エクセルには三角関数のCOSとSINも用意されている
引数は角度じゃなくでラジアンってのを使う
これもRADIANSっていう関数で変換できる
イメージ 2
(3,0)を30度回転させるとだいたい(2.6,1.5)になって図形にしてみたらあっているっぽい
 
だいたい(2.6,1.5)になったのを逆行列で30度回転してみたら
イメージ 3
見事にもとに戻った!すごい!
 
 
.NETにもCOSとSINの関数は用意されている、けど角度をラジアンにするのはなさそうなので用意
角度をラジアンに変換するのは
ラジアン = 角度/180*パイ
みたいなので
private double Radians角度をラジアン(float kakudo)
{
return kakudo / 180 * Math.PI;
}
を用意して
 
座標を指定角度で変換する関数
private Point GetRotate回転座標(Point p, float kakudo)
{
double radians = Radians角度をラジアン(kakudo);
return new Point(
Math.Cos(radians) * p.X - Math.Sin(radians) * p.Y,
Math.Sin(radians) * p.X + Math.Cos(radians) * p.Y);
}
 
↑の逆行列版が
private Point GetRotate逆回転座標(Point p, float kakudo)
{
double radians = Radians角度をラジアン(kakudo);
return new Point(
Math.Cos(radians) * p.X + Math.Sin(radians) * p.Y,
-Math.Sin(radians) * p.X + Math.Cos(radians) * p.Y);
}
 
 
 
イメージ 5
これが最初の状態で
処理2で30度回転
イメージ 6
位置調整なしなので角度をつけると元の位置からどんどん離れていく
変な模様ができているのはピクセルに色の値が入っていないからそう見えるだけ
 
イメージ 10
半透明の部分が範囲外になるところ
 
処理2のコード
イメージ 4
元画像座標を回転+位置調整なし
WriteableBitmapのCopyPixelsを使ってピクセルごとの色を指定していくいつもの方法
wbが元画像から作ったWriteableBitmap、nWbが変換後画像用のWriteableBitmap
変数の頭にnが付いているのが変換後画像用
 
332行目、OriginBitmapが元画像でBitmapSourceでPixelFormatはGray8
351行目のRect.Containsは指定したPoint座標がRect内に入っていたらTrueを返すメソッド、これを使って範囲外になったピクセルは無視するようにしている
 
 
 
処理3は処理2の改変
イメージ 7
変換後画像のサイズを変更して大きさを合わせて、さらにピクセルの位置を調整して画像全体が表示されるようにしたもの
穴開き状態はまだそのまま
 
イメージ 11
全部表示されるように赤枠の四角形を取得する必要があるので
 
回転後の画像は90,180,270度を除いて必ずもとより大きくなるので
そのサイズと位置を取得して調整する必要があるので
それを取得するのを用意、JustぴったりサイズRect
イメージ 9
元画像の四角、左上、右上、右下、左下をそれぞれ回転したときの座標を取得して、312行目からの4行
その4点から上下左右を取得が317行目からの4行
上下左右から幅と高さを計算して
Rectを作成して返す
 
処理3のコード
イメージ 8
278行目で回転後画像がぴったり収まるRectを取得、これからWriteableBitmapを作成、これで表示する場所は整った
変換後座標をそのままだとマイナスだったり大きすぎたりするので調整する
調整する値はぴったりRectの左上座標をマイナスにした値
 
 
処理4
イメージ 12
穴が空いていたのを埋めた感じ
処理3までは元画像のピクセルの座標を回転行列を使って回転後の座標を取得していた
元画像のピクセル座標→回転行列で変換→回転後の座標
これを逆にした感じで
回転後画像のピクセル座標→回転行列の逆行列で変換→元画像での座標
こうすると回転後の全てのピクセルを計算するから穴開きがなくなる
 
イメージ 13
処理3とは239行目までは同じ
x,yのループ数が変換後のサイズ分になる
 
イメージ 14
赤枠の中のすべてのピクセル座標を逆回転行列で変換するけど
半透明赤の部分を変換しても元画像にはない座標になるので
それを弾いているのが249行目から252行目
 
この処理4で画像の回転は完成
 
処理5
イメージ 15
処理4で画像の中がギザギザだったのを線形補間でなめらかにした
回転とは関係ない
 
イメージ 16
変換後座標の上下左右4ピクセルの値を距離で線形補間は199行目から
変換後座標が外周1ピクセルだった場合はめんどくさかったので補間なしにした
 
 
処理6
イメージ 17
処理5のカラー版
これも回転とは関係ない
ここまで来て気づいたのが画像の四角が削れていること、これは
変換後座標の小数点以下を切り捨てて処理をしているのと
ぴったりサイズがぴったりじゃないのかなあ
あとは線形補間でギザギザをなくしたけど最外周部のギザギザが目立つ
これは最外周部の補間をしなかったせいかな
なんにしてもきれいにするには丁寧に計算する必要があるみたい
 
処理5のコード
イメージ 19
BitmapSourceのPixelFormatはPbgra32
 
 
 
イメージ 18
画像を回転表示するだけならこんなめんどくさいことしなくても
WPFならRotateTransformを使えばいいんだけど
実際にできると楽しい
 
イメージ 20
この穴が空いて模様に見えるのも面白い
今回の行列もこのまえの分散、中央値に続いて理系の高校でしか教えていないものだった…とはいっても画像の回転だけなら行列はあんまり関係ない感じかなあ、理解はできなかったけど変換の関数があったからそれを使ってできた
今はインターネットがあるから簡単に調べることができるのが素晴らしい
情報を公開してくださっている方々も素晴らしい
 
 
コード全部
 
 
 
 
 
参照したところ
アフィン変換 画像処理ソリューション
http://imagingsolution.blog107.fc2.com/blog-entry-284.html
アフィン変換っていうのと回転行列はこちらから
 
逆行列を理解してみる - デジタル・デザイン・ラボラトリーな日々
http://yaju3d.hatenablog.jp/entry/2013/07/14/133031
回転行列の逆行列はこちらから
逆行列を使わないと穴が空く説明もここがわかりやすかった
 
大学1年生と再履生のための線形代数入門
https://oguemon.com/topic/study/linear-algebra/
行列についてはこちら
 
【すぐわかる線形代数】11.掃き出し法と逆行列 - YouTube
https://www.youtube.com/watch?v=tFKaHfSB5hM
動画
 
 
 
 
関連記事
2018/4/17
線形補間(バイリニア法)
バイリニア法で画像の拡大縮小 ( ソフトウェア ) - 午後わてんのブログ - Yahoo!ブログ
https://blogs.yahoo.co.jp/gogowaten/15464617.html