チーム19D1
課題名
モザイク処理とFFT画像の比較
研究者名
YUSUKE UEDA
KOTARO KANASUGI
概要
チーム15C2、19C4を参考にし、元の画像とそれにモザイク処理をした画像を用意する。
それをFFTで変換し変化を確認する。
結果
元の画像
モザイク処理した画像(block = 3)
元の画像のFFT
モザイク処理した画像のFFT(block = 3)
モザイク処理した画像のFFT(block = 10)
ソースコード
ImageFFT.java
package hardware;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
public class ImageFFT{
public static void main(String args[]){ try{ // 引数チェック if(args.length == 0){ System.err.println("JPGファイルを引数に与えてください."); return; }
// 画像の読み込み BufferedImage src = ImageIO.read(new File(args[0]));
// 画像をグレースケールに変換 BufferedImage gray = toGrayScale(src); ImageIO.write(gray, "jpg", new File("gray.jpg"));
// 画像を2の累乗の解像度にリサイズする BufferedImage expandedSrc = resizePowerOfTwo(gray);
// 画像を複素数配列に変換して,離散フーリエ変換する Complex c[][] = imageToComplexArray(expandedSrc); FFT2D ft = new FFT2D(c.length, c[0].length); ft.fft(c);
// 得られた周波数領域の複素数二次配列を,画像に変換する BufferedImage freq = toFreq(c); ImageIO.write(freq, "jpg", new File("freq.jpg"));
// 逆離散フーリエ変換する ft.ifft(c);
// 複素数配列から画像に変換する BufferedImage origin = complexArrayToImage(c); ImageIO.write(origin, "jpg", new File("origin.jpg"));
//グレースケール画像をモザイク処理 BufferedImage mos = mo(gray,3); ImageIO.write(mos, "jpg", new File("mos.jpg"));
// 画像を2の累乗の解像度にリサイズする BufferedImage expandedSrc2 = resizePowerOfTwo(mos);
// 画像を複素数配列に変換して,離散フーリエ変換する c = imageToComplexArray(expandedSrc2); ft = new FFT2D(c.length, c[0].length); ft.fft(c);
// 得られた周波数領域の複素数二次配列を,画像に変換する freq = toFreq(c); ImageIO.write(freq, "jpg", new File("freq2.jpg"));
// 逆離散フーリエ変換する ft.ifft(c);
// 複素数配列から画像に変換する origin = complexArrayToImage(c); ImageIO.write(origin, "jpg", new File("origin2.jpg")); }catch(Exception e){ e.printStackTrace(); }
}
/** * nより大きい最少の2の累乗の値を返す. */ private static int smallestPowerOfTwolargerThan(final int n){ int i; for(i = 1; i < n; i *= 2); return i; }
/** * 画像の解像度が2の累乗になるようリサイズする. */ private 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; }
/** * 二次元複素数配列を画像に変換する. * 画像はグレースケール画像になる. */ private 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成分のみに着目して変換する. */ private 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; }
/** * 画像をグレイスケールに変換する. */ private 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; }
/** * 二次元複素数配列のうち,ノルムの最大値を返す. */ private 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色空間の色相で表現する. * 大きい値は赤,小さい値は青で示す. */ private 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; }
public static int rgb(int r,int g,int b){ return 0xff000000 | r <<16 | g <<8 | b; }
private static BufferedImage mo(final BufferedImage src,int x){ assert src != null; int block = x; int w = src.getWidth(); int h = src.getHeight(); BufferedImage mosaic = new BufferedImage(w, h,BufferedImage.TYPE_INT_BGR); for (int i = 0; i < w; i += block) { for (int j = 0; j < h; j += block) { int r = 0, g = 0, b = 0; int sum = 0; for (int k = 0; k < block && k + i < w; k++) { for (int l = 0; l < block && l + j < h; l++) { //画素の取得 int color = src.getRGB(i + k, j + l); r += (color >> 16) & 0xff; g += (color >> 8) & 0xff; b += color & 0xff; sum++; } } //平均値の計算 r /= sum; g /= sum; b /= sum; //画素を格納 for (int k = 0; k < block && k + i < w; k++) for (int l = 0; l < block && l + j < h; l++) mosaic.setRGB(i + k, j + l, (r << 16) + (g << 8) + b); } } return mosaic; }
}
FFT2D.java
package hardware;
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]; } } }
}
考察
通常のFFTの画像では青いグラデーションのような画像が表示されるが、モザイク処理をした後の画像のFFTだとマス目状の模様が現れる。このマス目はモザイク画像のブロック数に依存していることがわかる。今回自由課題を行った結果では、ブロック数を3から10に変えることで、FFTの画像のマス目の数が3×3から10×10に変化している。これは、モザイク処理というのは、周りの画素値の平均を取ることで行われているので、周波数成分に変換したときにその様子が画像に現れると考えられる。このことから、モザイク処理をするとFFT上で変化が起こることがわかる。
- 最終更新:2019-10-21 15:08:43