すこし落ち着いてきたので久しぶりにFlashを。
今回はSymmetric Nearest Neighbor (SNN)という手法を画像に適用します。
シンメトリック ニアレスト ネイバー。訳すと対称最近傍法。
画素値の差の絶対値を “距離” として、その距離が近い画素の値を選んでいきます。
対称としているのは注目画素に対して対称方向の位置にある2つの画素を参照するからです。
つまり、
1 2 3
4 5 6
7 8 9
とあって5が注目画素なら、1と9、2と8、3と7、4と6… の位置の画素を参照し、
注目画素との距離が近い方の画素の値を採用します。
メディアンフィルタやKuwaharaフィルタと同様にエッジを残したまま画像をぼかしますが、
両者より高品質な結果が得られることが多い手法で、写真が絵画っぽくなってくれます。
・ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
package { import __AS3__.vec.Vector; import flash.display.*; [SWF(width = "600", height = "250", backgroundColor = "#000000", frameRate = "30")] public class SNN extends Sprite{ [Embed(source = 'assets/picture.jpg')] public var SrcImg:Class; private var src:Bitmap; private var dst:Bitmap; private var srcData:Vector.<uint>; private var w:int; private var h:int; public function SNN() { stage.scaleMode = "noScale"; src = new SrcImg() as Bitmap; w = src.width; h = src.height; dst = new Bitmap(new BitmapData(w, h, false)); srcData = new Vector.<uint>(w*h, true); srcData = src.bitmapData.getVector(src.bitmapData.rect); dst.bitmapData.setVector(dst.bitmapData.rect, filterSNN(srcData, 3)); addChild(src); addChild(dst).x += w; } // Symmetric Nearest Neighbor private function filterSNN(src:Vector.<uint>, radius:int):Vector.<uint> { var dst:Vector.<uint> = new Vector.<uint>(w*h, true); var sumR:uint, sumG:uint, sumB:uint; var rc:int, gc:int, bc:int, r1:int, g1:int, b1:int, r2:int, g2:int, b2:int; var cnt:int = 0; var xyPos:int, uvPos:int; for (var y:int=0;y<h;y++) { for (var x:int=0;x<w;x++) { xyPos = w*y + x; sumR = 0, sumG = 0, sumB = 0; cnt = 0; rc = src[xyPos] >> 16 & 0xff; gc = src[xyPos] >> 8 & 0xff; bc = src[xyPos] & 0xff; for (var v:int=-radius;v<=radius;v++) { for (var u:int=-radius;u<=radius;u++,cnt++) { uvPos = w*v + u; try{ r1 = src[xyPos + uvPos] >> 16 & 0xff; g1 = src[xyPos + uvPos] >> 8 & 0xff; b1 = src[xyPos + uvPos] & 0xff; r2 = src[xyPos - uvPos] >> 16 & 0xff; g2 = src[xyPos - uvPos] >> 8 & 0xff; b2 = src[xyPos - uvPos] & 0xff; }catch(e:RangeError) { break; }catch(e:ReferenceError) { break; } if (delta(rc, gc, bc, r1, g1, b1) < delta(rc, gc, bc, r2, g2, b2)) { sumR += r1; sumG += g1; sumB += b1; }else { sumR += r2; sumG += g2; sumB += b2; } } } dst[xyPos] = sumR/cnt << 16 | sumG/cnt << 8 | sumB/cnt; } } return dst; } private function delta(rc:int, gc:int, bc:int, r1:int, g1:int, b1:int):Number { return Math.sqrt((rc - r1)*(rc - r1) + (gc - g1)*(gc - g1) + (bc - b1)*(bc - b1)); } } } |
少し冗長ですがアルゴリズムがわかりやすいように書きました。
画像境界外を参照したときはbreakしているだけなのでそんなに品質は良くないかも。
hey this is a very interesting article!