チーム20B2
課題名
モザイクとFFT
研究者名
MAHIRO NIHEI, KOHEI TAKANO
概要
引数の画像の指定した範囲にモザイクかけ、周波数成分をFFTを用いて確認。
プログラミング言語
Java
実行結果
Refreshボタンで初期化、Closeボタンで終了。
↓モザイクのブロック幅をセットし、モザイクをかけたい範囲を指定(赤丸:左上、青丸:右下)。
↓Mosaicボタンで指定した範囲にモザイクをかける。またモザイクがかかった画像が出力される。
↓freqボタンで現在表示されている画像からFFTで周波数成分が取り出される。また周波数成分の画像が出力される。
↓モザイクの範囲を広げた場合
↓元画像の周波数成分
ソースコード
FFTはチーム15C2から引用した。
ImageFFT.java
package mosaic_image;
import java.awt.Color;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
/**
* 2次元FFTを用いて、グレースケール画像のパワースペクトルを表示する */
public class ImageFFT {
static MouseMosaic mm;
public static void main(String args[]){ try{ // 引数チェック if(args.length == 0){ System.err.println("JPGファイルを引数に与えてください."); return; }
// 画像の読み込み BufferedImage src = ImageIO.read(new File(args[0]));
mm = new MouseMosaic(src);
}catch(Exception e){ e.printStackTrace(); } }
public static BufferedImage toMosaicScale(BufferedImage src) { BufferedImage dst = src;
Point p1,p2;
p1 = mm.get_left_top_forFFT(); p2 = mm.get_right_bottom_forFFT();
try { if(p1 == null | | p2 == null) { throw new NullPointerException("モザイクの範囲を決めてください"); } else if(p1.x < 0 | | p1.y < 0 | | src.getWidth() <= p2.x | | src.getHeight() <= p2.y) throw new Exception("out of bounds of image"); } catch(NullPointerException nupe) { mm.message.setText("モザイクの範囲を決めてください");
return dst; } catch (Exception e) { mm.message.setText("画像の範囲内でクリックしてください");
return dst; }
try { if(p1.x >= p2.x | | p1.y >= p2.y) throw new Exception("reverse point exception"); } catch(Exception e) { mm.message.setText("1つ目の点は左上、2つ目の点は右下です"); return dst; }
int block = mm.get_block();
final int xblock_num = (p2.x - p1.x)/block; // x軸におけるブロック数 final int yblock_num = (p2.y - p1.y)/block; // y軸におけるブロック数
if(xblock_num == 0 | | yblock_num == 0) { mm.message.setText("範囲を広げて下さい"); return dst; }
int xblock_left = (p2.x - p1.x)%block; // x軸におけるブロックの切れ端 int xblock = block + (xblock_left/xblock_num); xblock_left = xblock_left%xblock_num; int [] allocx = new int[xblock_left]; // 横方向の割り当て場所を格納する配列
// ブロックを(xblock_left + 1)等分して、その境目の1つ左のブロックを割り当てブロックとする。 int tmp1 = xblock_num, tmp2 = xblock_left + 1; for(int i = 0; i < xblock_left; i++) { allocx[i] = tmp1/tmp2 + (i != 0 ? allocx[i-1] : 0); tmp1 = tmp1 - allocx[i]; tmp2--; }
int yblock_left = (p2.y - p1.y)%block; // y軸におけるブロックの切れ端数 int yblock = block + (yblock_left/yblock_num); yblock_left = yblock_left%yblock_num; int [] allocy = new int[yblock_left]; // 縦方向の割り当て場所を格納する配列
// ブロックを(yblock_left + 1)等分して、その境目の1つ左のブロックを割り当てブロックとする。 tmp1 = yblock_num; tmp2 = yblock_left + 1; for(int i = 0; i < yblock_left; i++) { allocy[i] = tmp1/tmp2 + (i != 0 ? allocy[i-1] : 0); tmp1 = tmp1 - allocy[i]; tmp2--; }
int count_x = 0,count_y = 0; // 配列の添え字をカウントする変数 int pixel_x,pixel_y; // 現在のブロックの左上の座標
for(int i = 0; i < yblock_num ; i++) { Boolean addFlagY = count_y < yblock_left; if(addFlagY) addFlagY = i == allocy[count_y]; if(addFlagY) count_y++; pixel_y = i * yblock + count_y + p1.y + (addFlagY ? -1 : 0); count_x = 0; for(int j = 0; j < xblock_num; j++) { Boolean addFlagX = count_x < xblock_left; if(addFlagX) addFlagX = j == allocx[count_x]; if(addFlagX) count_x++; pixel_x = j * xblock + count_x + p1.x + (addFlagX ? -1 : 0); mosaic(src,dst,pixel_x,pixel_y,(addFlagX ? xblock + 1 : xblock),(addFlagY ? yblock + 1 : yblock)); } }
return dst; }
private static void mosaic(BufferedImage src , BufferedImage dst, final int pixel_x, final int pixel_y, final int block_width, final int block_height) { int sum_r = 0, sum_g = 0, sum_b = 0; for(int i = 0; i < block_height; i++) { for(int j = 0; j < block_width; j++) { int rgb = src.getRGB(j + pixel_x, i + pixel_y); sum_r += (rgb & 0x000000FF) >> 0; sum_g += (rgb & 0x0000FF00) >> 8; sum_b += (rgb & 0x00FF0000) >> 16; } }
int ave_r = sum_r/(block_width*block_height); int ave_g = sum_g/(block_width*block_height); int ave_b = sum_b/(block_width*block_height);
int new_rgb = ave_r | (ave_g << 8) | (ave_b << 16) | 0xFF000000;
for(int i = 0; i < block_height; i++) { for(int j = 0; j < block_width; j++) { dst.setRGB(j + pixel_x, i + pixel_y, new_rgb); } }
}
/** * nより大きい最少の2の累乗の値を返す. */ static int smallestPowerOfTwolargerThan(final int n){ int i; for(i = 1; i < n; i *= 2); return i; }
/** * 画像の解像度が2の累乗になるようリサイズする. */ static BufferedImage resizePowerOfTwo(final BufferedImage src){ assert src != null;
final int srcWidth = src.getWidth(); final int srcHeight = src.getHeight(); final int dstWidth = smallestPowerOfTwolargerThan(srcWidth); final int dstHeight = smallestPowerOfTwolargerThan(srcHeight);
final BufferedImage dst = new BufferedImage( dstWidth, dstHeight, BufferedImage.TYPE_INT_BGR);
for(int i = 0; i < srcHeight; i++){ for(int j = 0; j < srcWidth; j++){ dst.setRGB(j, i, src.getRGB(j, i)); } }
return dst; }
/** * 二次元複素数配列を画像に変換する. */ static BufferedImage complexArrayToImage(final Complex c[][]){ assert c != null;
final int width = c[0].length; final int height = c.length;
final BufferedImage img = new BufferedImage( width, height, BufferedImage.TYPE_INT_BGR); for(int i = 0; i < height; i++){ for(int j = 0; j < width; j++){ int sig = (int)c[i][j].getRe(); if(sig > 255){ sig = 255; }else if(sig < 0){ sig = 0; } int rgb = sig | sig << 8 | sig << 16; img.setRGB(j, i, rgb); } } return img; }
/** * 画像を二次元複素数配列に変換する. * RGBのうち,B成分のみに着目して変換する. */ static Complex[][] imageToComplexArray(final BufferedImage img){ assert img != null;
final int width = img.getWidth(); final int height = img.getHeight();
BufferedImage gray = toGrayScale(img);
Complex c[][] = new Complex[height][width]; for(int i = 0; i < height; i++){ for(int j = 0; j < width; j++){ int sig = gray.getRGB(j, i) & 0xFF; c[i][j] = new Complex(sig); } } return c; }
/** * 画像をグレイスケールに変換する. */ static BufferedImage toGrayScale(final BufferedImage src){ assert src != null;
final int width = src.getWidth(); final int height = src.getHeight(); final BufferedImage dst = new BufferedImage( width, height, BufferedImage.TYPE_INT_BGR);
for(int i = 0; i < height; i++){ for(int j = 0; j < width; j++){ int rgb = src.getRGB(j, i); int r = (rgb & 0x000000FF) >> 0; int g = (rgb & 0x0000FF00) >> 8; int b = (rgb & 0x00FF0000) >> 16; int y = (int)(0.299 * r + 0.587 * g + 0.114 * b); int gray = y | (y << 8) | (y << 16); dst.setRGB(j, i, gray); } } return dst; }
/** * 二次元複素数配列のうち,ノルムの最大値を返す. */ static double maxNormOf(Complex x[][]){ assert x != null;
double max = x[0][0].norm(); for(int i = 0; i < x.length; i++){ for(int j = 0; j < x[i].length; j++){ double tmp = x[i][j].norm(); if(max < tmp){ max = tmp; } } } return max; }
/** * 複素数配列を画像に変換する. * 複素数のノルムをもとに画像を生成する. * ノルムの大きさをHSB色空間の色相で表現する. * 大きい値は赤,小さい値は青で示す. */ static BufferedImage toFreq(final Complex[][] x){ assert x != null;
final int width = x[0].length; final int height = x.length;
final BufferedImage dst = new BufferedImage( width, height, BufferedImage.TYPE_INT_BGR);
double max = maxNormOf(x); double k = 1.0 / Math.sqrt(max); for(int i = 0; i < height; i++){ for(int j = 0; j < width; j++){ double rate = k * Math.sqrt(x[i][j].norm() * 128); if(rate > 1){ rate = 1; } int rgb = Color.HSBtoRGB((float)(0.66 * (1 - rate)), 1.0f, 1.0f); dst.setRGB(j, i, rgb); } } return dst; }
}
MouseMosaic.java
package college;
import java.awt.Button;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Label;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class MouseMosaic extends Frame implements MouseListener, ActionListener{
private final int INSET_TOP = 30; private final int INSET_BOTTOM = 20; private final int INSET_LEFT = 20; private final int INSET_RIGHT = 20;
private final int TOP_MARGIN = 90;
private Point left_top, right_bottom; private Point left_top_forFFT,right_bottom_forFFT; private int desig; private int block;
TextField text; Label message;
BufferedImage draw_img; BufferedImage img_mosaic; BufferedImage img_fft;
final BufferedImage src_tmp;
public MouseMosaic(final BufferedImage src){ desig = 0; left_top = null; right_bottom = null;
setLayout(null);
draw_img = copyImage(src); src_tmp = copyImage(src); img_mosaic = copyImage(src);
setSize(draw_img.getWidth()+INSET_LEFT+INSET_RIGHT, draw_img.getHeight()+INSET_TOP+INSET_BOTTOM+TOP_MARGIN);
final int BUTTONWIDTH = 60, BUTTONHEIGHT = 20, BUTTONSPACE = 10; final int pBASEX = (INSET_LEFT+draw_img.getWidth()/2) - (BUTTONWIDTH*2+(int)(BUTTONSPACE*1.5)),pBASEY = 30;
Point pBase = new Point(pBASEX,pBASEY);
Button mosaicButton = new Button("Mosaic"); Rectangle r_mosaicButton = new Rectangle(pBase.x,pBase.y,BUTTONWIDTH,BUTTONHEIGHT); mosaicButton.setBounds(r_mosaicButton); add(mosaicButton); mosaicButton.addActionListener(this);
Button freqButton = new Button("freq"); Rectangle r_freqButton = new Rectangle(r_mosaicButton.x + BUTTONWIDTH + BUTTONSPACE, pBase.y, BUTTONWIDTH,BUTTONHEIGHT); freqButton.setBounds(r_freqButton); add(freqButton); freqButton.addActionListener(this);
Button refreshButton = new Button("Refresh"); Rectangle r_refreshButton = new Rectangle(r_freqButton.x + BUTTONWIDTH + BUTTONSPACE, pBase.y, BUTTONWIDTH,BUTTONHEIGHT); refreshButton.setBounds(r_refreshButton); add(refreshButton); refreshButton.addActionListener(this);
Button closeButton = new Button("Close"); Rectangle r_closeButton = new Rectangle(r_refreshButton.x + BUTTONWIDTH + BUTTONSPACE, pBase.y, BUTTONWIDTH,BUTTONHEIGHT); closeButton.setBounds(r_closeButton); add(closeButton); closeButton.addActionListener(this);
final int LABELWIDTH = 170;
Label label = new Label("モザイクのブロック幅を入力: "); label.setBounds(pBASEX, pBASEY + BUTTONHEIGHT + BUTTONSPACE, LABELWIDTH, BUTTONHEIGHT); add(label);
final int TEXTLENGTH = 30;
text = new TextField("20",10); Rectangle r_text = new Rectangle(pBASEX + LABELWIDTH + BUTTONSPACE, pBASEY + BUTTONHEIGHT + BUTTONSPACE, TEXTLENGTH, BUTTONHEIGHT); text.setBounds(r_text); add(text);
Button setButton = new Button("set"); Rectangle r_setButton = new Rectangle(r_text.x + TEXTLENGTH + BUTTONSPACE, pBASEY + BUTTONHEIGHT + BUTTONSPACE, BUTTONWIDTH-10, BUTTONHEIGHT); setButton.setBounds(r_setButton); add(setButton); setButton.addActionListener(this);
message = new Label("1つ目の点をクリック",Label.CENTER); message.setBounds(pBASEX, r_setButton.y + BUTTONHEIGHT + BUTTONSPACE, 4*BUTTONWIDTH + 3*BUTTONSPACE, BUTTONHEIGHT); add(message);
block = 20;
setVisible(true); addMouseListener(this); }
public BufferedImage copyImage(final BufferedImage img) { BufferedImage copy_img; copy_img = new BufferedImage(img.getWidth(), img.getHeight(), img.getType()); copy_img.setData(img.getData());
return copy_img; }
public void refresh() { desig = 0; left_top = null; right_bottom = null; left_top_forFFT = null; right_bottom_forFFT = null; draw_img = copyImage(src_tmp); img_mosaic = copyImage(src_tmp);
repaint(); }
public void paint(Graphics g) { g.drawImage(draw_img, INSET_LEFT, INSET_TOP+TOP_MARGIN, this);
if(left_top != null) { g.setColor(Color.red); g.fillOval(left_top.x-2, left_top.y-2, 4, 4); } if(right_bottom != null) { g.setColor(Color.blue); g.fillOval(right_bottom.x-2, right_bottom.y-2, 4, 4); }
}
public void mouseClicked(MouseEvent e){ if(desig % 2 == 0) { left_top = e.getPoint(); left_top_forFFT = new Point(left_top.x-INSET_LEFT,left_top.y-INSET_TOP-TOP_MARGIN); desig++; message.setText("2つ目の点をクリック"); repaint(); } else if(desig % 2 == 1) { right_bottom = e.getPoint(); right_bottom_forFFT = new Point(right_bottom.x-INSET_LEFT,right_bottom.y-INSET_TOP-TOP_MARGIN); desig++; message.setText("1つ目の点をクリック"); repaint(); } }
public void actionPerformed(ActionEvent ae) { String commandName = ae.getActionCommand(); if (commandName.equals("Mosaic")) { try { img_mosaic = ImageFFT.toMosaicScale(img_mosaic); draw_img = copyImage(img_mosaic); ImageIO.write(img_mosaic,"jpg", new File("mosaic.jpg")); repaint(); } catch ( Exception e ) { e.printStackTrace(); return; } } else if (commandName.equals("freq")) { // 画像を2の累乗の解像度にリサイズする
BufferedImage expandedSrc = ImageFFT.resizePowerOfTwo(img_mosaic);
// 画像を複素数配列に変換して,離散フーリエ変換する Complex c[][] = ImageFFT.imageToComplexArray(expandedSrc); FFT2D ft = new FFT2D(c.length, c[0].length); ft.fft(c);
// 得られた周波数領域の複素数二次配列を,画像に変換する BufferedImage freq = ImageFFT.toFreq(c); try { ImageIO.write(freq, "jpg", new File("freq.jpg")); draw_img = freq; left_top = null; right_bottom = null; repaint(); } catch (IOException e) { e.printStackTrace(); }
// 逆離散フーリエ変換する ft.ifft(c);
// 複素数配列から画像に変換する BufferedImage origin = ImageFFT.complexArrayToImage(c); try { ImageIO.write(origin, "jpg", new File("origin.jpg")); } catch (IOException e) { e.printStackTrace(); } } else if (commandName.equals("Refresh")) { refresh(); } else if (commandName.equals("Close")) { dispose(); } else if (commandName.equals("set")) { try { block = Integer.parseInt(text.getText()); message.setText("ブロック幅を"+block+"としました"); } catch(Exception e){ message.setText("ブロック幅は数字を入力して下さい"); } } }
public Point get_left_top_forFFT() { return left_top_forFFT; }
public Point get_right_bottom_forFFT() { return right_bottom_forFFT; }
public int get_block() { return block; }
public void mouseEntered(MouseEvent e){ } public void mouseExited(MouseEvent e){ } public void mousePressed(MouseEvent e){ } public void mouseReleased(MouseEvent e){ }
}
Complex.java
package mosaic_image;
public final class Complex{
/** 実部 */ private final double re; /** 虚部 */ private final double im;
/** 実部虚部を初期化するコンストラクタ */ public Complex(final double re, final double im){ this.re = re; this.im = im; }
/** 実部のみを初期化するコンストラクタ */ public Complex(final double re){ this(re, 0); }
/** 実部reのゲッター*/ public double getRe(){ return re; }
/** 虚部imのゲッター*/ public double getIm(){ return im; }
/** 和 */ public static Complex add(final Complex c1, final Complex c2){ return new Complex(c1.re + c2.re, c1.im + c2.im); }
/** 差 */ public static Complex sub(final Complex c1, final Complex c2){ return new Complex(c1.re - c2.re, c1.im - c2.im); }
/** 積 */ public static Complex mult(final Complex c1, final Complex c2){ return new Complex(c1.re * c2.re - c1.im * c2.im , c1.re * c2.im + c1.im * c2.re); }
/** スカラー積 */ public static Complex mult(final Complex c, final double k){ return new Complex(k * c.re, k * c.im); }
/** スカラー商 */ public static Complex div(final Complex c, final double k){ return new Complex(c.re / k, c.im / k); }
/** 共役 */ public Complex conjugate(){ return new Complex(re, -im); }
/** 長さ */ public double norm(){ return Math.sqrt(re * re + im * im); }
/** 比較 */ @Override public boolean equals(final Object obj){ if(obj instanceof Complex){ Complex c = (Complex)obj; return c.re == re && c.im == im; } return false; }
/** ハッシュ */ @Override public int hashCode(){ return Double.valueOf(re).hashCode() ^ Double.valueOf(im).hashCode(); }
/** 文字列表現 */ @Override public String toString(){ return String.format("(% 3.2f,% 3.2f)", re, im); }
}
FFT.java
package mosaic_image;
public final class FFT{
/** FFTの点数を表すn */ private final int n; /** 2を底とするNの対数 */ private final int logN; /** 回転因子 */ private final Complex w[];
/** N点FFT用オブジェクトのコンストラクタ */ public FFT(final int n){ if(n < 0 | | (n & (n - 1)) != 0){ throw new IllegalArgumentException("n must be power of 2"); } this.n = n;
// calc log_2 n int count = 0; for(int i = n; i > 1; i /= 2){ count++; } logN = count;
// prepare twiddle factors w = new Complex[n / 2]; for(int i = 0; i < n / 2; i++){ double theta = 2 * i * Math.PI / n; w[i] = new Complex(Math.cos(theta), -Math.sin(theta)); } }
/** * 複素数配列xを離散フーリエ変換する */ public void fft(final Complex x[]){ if(x == null){ throw new NullPointerException(); } if(x.length != n){ throw new IllegalArgumentException("x length must be n"); }
// N point DFT for(int m = n; m >= 2; m /= 2){ int hm = m / 2; // half of m // do the DFT begins at i for(int i = 0; i < n; i += m){ // butterfly tx[i + j] and tx[i + hn + j]; for(int j = 0; j < hm; j++){ final int k = i + j; final int l = i + hm + j; final Complex xk = x[k]; final Complex xl = x[l]; x[k] = Complex.add(xk, xl); x[l] = Complex.mult(Complex.sub(xk, xl), w[j*n/m]); } } }
for(int i = 0; i < n; i++){ x[i] = Complex.div(x[i], n); }
bitReverseSort(x); }
/** * 複素数配列xを離散フーリエ逆変換する */ public void ifft(final Complex x[]){ if(x == null){ throw new NullPointerException(); } if(x.length != n){ throw new IllegalArgumentException("x length must be n"); }
for(int i = 0; i < n; i++){ x[i] = x[i].conjugate(); }
fft(x);
for(int i = 0; i < n; i++){ x[i] = Complex.mult(x[i].conjugate(), n); } }
/** * 配列をビットリバース順に並べ替えるメソッド */ private void bitReverseSort(final Complex x[]){ for(int i = 0; i < n; i++){ int j = bitReverse(i, logN); if(i < j){ Complex tmp = x[i]; x[i] = x[j]; x[j] = tmp; } } }
/** * 変数nのビットリバースを計算するメソッド */ private static int bitReverse(final int n){ int w = n; w = (w & 0xAAAAAAAA) >>> 1 | (w & 0x55555555) << 1; w = (w & 0xCCCCCCCC) >>> 2 | (w & 0x33333333) << 2; w = (w & 0xF0F0F0F0) >>> 4 | (w & 0x0F0F0F0F) << 4; w = (w & 0xFF00FF00) >>> 8 | (w & 0x00FF00FF) << 8; w = (w & 0xFFFF0000) >>> 16 | (w & 0x0000FFFF) << 16; return w; }
/** * 下位sizeビットを変数nのビットリバースを計算するメソッド */ private static int bitReverse(final int n, final int size){ final int SIZE_OF_INT = 32; assert 0 <= size && size < SIZE_OF_INT;
final int rev = bitReverse(n); return rev >>> (SIZE_OF_INT - size); }
/** * テスト */ public static void main(String args[]){ int n = 32; FFT f = new FFT(32); // 矩形波 Complex[] x = new Complex[n]; for(int i = 0; i < n; i += 16){ for(int j = 0; j < 8; j++){ x[i+j] = new Complex(0); x[i+j+8] = new Complex(1); } } System.out.println("Original"); for(Complex c : x){ System.out.println(c); } f.fft(x); System.out.println("FFT"); for(Complex c : x){ System.out.println(c); } f.ifft(x); System.out.println("IFFT"); for(Complex c : x){ System.out.println(c); } }
}
FFT2D.java
package mosaic_image;
public final class FFT2D{
/** FFTの点数を表すn */ private final int n; /** FFTの点数を表すm */ private final int m; /** n点FFT */ private final FFT nPointFFT; /** m点FFT */ private final FFT mPointFFT;
/** nxm点FFT用オブジェクトのコンストラクタ */ public FFT2D(final int n, final int m){ nPointFFT = new FFT(n); mPointFFT = new FFT(m); this.n = n; this.m = m; }
/** * 2次元複素数配列xを離散フーリエ変換する */ public void fft(final Complex x[][]){ if(x == null){ throw new NullPointerException(); } if(x.length != n){ throw new IllegalArgumentException("x length must be n"); } if(x[0].length != m){ throw new IllegalArgumentException("x length must be m"); }
// 行ごとにFFT Complex tmp[]; tmp = new Complex[m]; for(int i = 0; i < n; i++){ for(int j = 0; j < m; j++){ tmp[j] = x[i][j]; }
mPointFFT.fft(tmp);
for(int j = 0; j < m; j++){ x[i][j] = tmp[j]; } }
// 列ごとにFFT tmp = new Complex[n]; for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ tmp[j] = x[j][i]; }
nPointFFT.fft(tmp);
for(int j = 0; j < n; j++){ x[j][i] = tmp[j]; } } }
/** * 2次元複素数配列xを離散フーリエ逆変換する */ public void ifft(final Complex x[][]){ if(x == null){ throw new NullPointerException(); } if(x.length != n){ throw new IllegalArgumentException("x length must be n"); } if(x[0].length != m){ throw new IllegalArgumentException("x length must be m"); }
// 列ごとにIFFT Complex tmp[]; tmp = new Complex[m]; for(int i = 0; i < n; i++){ for(int j = 0; j < m; j++){ tmp[j] = x[i][j]; }
mPointFFT.ifft(tmp);
for(int j = 0; j < m; j++){ x[i][j] = tmp[j]; } }
// 列ごとにIFFT tmp = new Complex[n]; for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ tmp[j] = x[j][i]; }
nPointFFT.ifft(tmp);
for(int j = 0; j < n; j++){ x[j][i] = tmp[j]; } } }
}
考察
・元画像の大部分が低周波成分で構成されていることが分かった。このことからローパスフィルタを通すことで、元画像から情報を大きく損なわずに圧縮することができると考えられる。
・モザイクをかけることで、周波数成分が均されていることが分かる。つまり、低周波が大部分占めている画像の平均をとっているので、全体的に低周波に画像の周波数がよっていくと考えられる。
改善点
・右下の点(青い点)を指定する際に、若干マウスとの位置の誤差があるので、改良の余地があると思う。
- 最終更新:2020-06-29 17:17:18