/* [<][>][^][v][top][bottom][index][help] */
DEFINITIONS
This source file includes following definitions.
- AnalyzeFourier
- XY
- Line
- add
- get
- size
- delTail
- clr
- Diag
- add
- get
- size
- delTail
- clr
- makeWB
- init
- calcPFFT
- writeDiag
- writeFFTDiag
- writeSrcDiag
- makeDiag
- chase8
- getNearPointData
- getNearPointData
- getPointData
- getPointData
package jp.ac.nime.computer.grpsimulator.ImgPr;
import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
import java.util.*;
/** 画像解析 フーリエ級数展開
* @author Kikuchi
* @see FFT
* @see Complex
* @see ComplexF
*/
public class AnalyzeFourier {
// クラス変数
private int nWidth_;
private int nHeight_;
protected int nOp_;
protected BufferedImage imgSrc_;
protected BufferedImage imgDst_;
protected int[] nBorder_;
// 画像を線分で表した図形
private Diag diag_;
// FFTで一回圧縮展開した線分で表した図形
private Diag diagFFT_;
// fft用データバッファ
private int[] numFFT_;
private ComplexF[] fft_;
/** 点を表すクラス
*/
class XY {
public XY(int x, int y) {
x_ = x;
y_ = y;
}
public int x_, y_;
}
/** 線分を表すクラス
*/
class Line {
private Vector v_;
public Line() {
v_ = new Vector(0, 100);
}
public void add(int x, int y) {
v_.add(new XY(x, y));
}
public XY get(int n) {
if (n >= v_.size()) return null;
return (XY)v_.get(n);
}
public int size() {
return v_.size();
}
public void delTail() {
v_.remove(v_.size() - 1);
}
public void clr() {
v_.clear();
}
}
/** 線分の塊(図形)を表すクラス
*/
class Diag {
private Vector v_;
public Diag() {
v_ = new Vector();
}
public void add(Line l) {
v_.add(l);
}
public Line get(int n) {
if (n >= v_.size()) return null;
return (Line)v_.get(n);
}
public int size() {
return v_.size();
}
public void delTail() {
v_.remove(v_.size() - 1);
}
public void clr() {
for (int i = 0; i < v_.size(); i ++) {
get(i).clr();
}
v_.clear();
}
}
/** コンストラクタ
*/
public AnalyzeFourier() {
diag_ = new Diag();
diagFFT_ = new Diag();
}
/** 白黒画像作成
* @param flag 1のとき白黒反転する
* @param imgSrc YUV形式画像
* @param imgDst YUV形式画像
*/
public static void makeWB(int flag, BufferedImage imgSrc, BufferedImage imgDst) {
MeasureOp.makeWhiteBlack(flag, imgSrc, imgDst);
}
/** 計算に必要なパラメタ設定 (他のクラスと違って出力にRGB形式画像を渡すこと)
* @param nOp 0:操作対象が黒 1:操作対象が白
* @param imgSrc YUV形式画像(二値化されていることが前提)
*/
public void init(int nOp, BufferedImage imgSrc) {
// 記憶
imgSrc_ = imgSrc;
nWidth_ = imgSrc_.getWidth();
nHeight_ = imgSrc_.getHeight();
// 線分データ作成時に使う境界線データ
nBorder_ = new int [nWidth_ * nHeight_];
Arrays.fill(nBorder_, 0);
// 線分データを生成する
makeDiag();
// FFT用バッファを作る
numFFT_ = new int [diag_.size()];
fft_ = new ComplexF[32768];
for (int i = 0; i < fft_.length; i ++) {
fft_[i] = new ComplexF(0.0, 0.0);
}
//System.out.println("SrcDiag = " + diag_.size() + " lines");
}
/** P形フーリエ変換・逆変換
*/
public void calcPFFT(int nCutOff) {
// 2の累乗のデータ
int[] rui2 = new int [16];
rui2[0] = 1;
for (int i = 1; i < 16; i++) {
rui2[i] = (int)Math.pow(2, i);
}
// 線分ごとにフーリエ変換 -> LowPass -> 逆変換 -> 新しい線分 を繰り替えすループ
diagFFT_.clr();
for (int i = 0; i < diag_.size(); i ++) {
// FFT用データの個数決定
int nPoint = diag_.get(i).size();
int nFFT = 0;
for (int j = 1; j < 16; j ++) { // 32768 まで対応
if (nPoint <= rui2[j]) {
nFFT = rui2[j];
break;
}
}
//System.out.println("SrcDiag Line(" + i + ") = " + nPoint + " points");
// FFT用のデータ作成
double[] dbDiff = new double[nPoint - 1]; // 点と点の間の距離
for (int k = 0; k < nFFT; k ++) {
fft_[k].set(0, 0); // 計算に必要分はクリア
}
for (int k = 0; k < nPoint - 1; k ++) {
XY xy1 = diag_.get(i).get(k);
XY xy2 = diag_.get(i).get(k + 1);
dbDiff[k] = Math.sqrt(((xy1.x_ - xy2.x_) * (xy1.x_ - xy2.x_)) + ((xy1.y_ - xy2.y_) * (xy1.y_ - xy2.y_)));
// 1data作る
fft_[k].set((xy2.x_ - xy1.x_) / dbDiff[k], (xy2.y_ - xy1.y_) / dbDiff[k]);
}
// フーリエ変換
FFT.fft(1, fft_, nFFT);
// LowPass
for (int k = nCutOff + 1; k < nFFT - nCutOff; k ++) {
fft_[k].set(0, 0);
}
// 逆変換
FFT.fft(-1, fft_, nFFT);
// 新しい線分作成
Line line = new Line();
// 始点
XY xy0 = diag_.get(i).get(0);
line.add(xy0.x_, xy0.y_);
for (int k = 0; k < nPoint - 1; k ++) {
// 新しい点作成
int x = (int)((line.get(k).x_ + dbDiff[k] * fft_[k].getReal()) + 0.5);
int y = (int)((line.get(k).y_ + dbDiff[k] * fft_[k].getImag()) + 0.5);
line.add(x, y);
}
// 図形に登録
diagFFT_.add(line);
}
//System.out.println("FFTDiag = " + diagFFT_.size() + " lines");
}
/** 図形を描画する
* @param diag 図形
* @param img 図形を描画する画像バッファ(RGB形式)
*/
private static void writeDiag(Diag diag, BufferedImage img) {
Graphics2D g = img.createGraphics();
// 白でクリア
g.setBackground(Color.white);
g.clearRect(0, 0, 320, 240);
g.setStroke(new BasicStroke(0.0f)); // 一番細く 引数はfloat
g.setPaint(Color.black); // 黒
// 図形
for (int i = 0; i < diag.size(); i ++) {
// 線分のループ
Line line = diag.get(i);
for (int k = 0; k < line.size() - 1; k ++) {
// 一つのライン
g.draw(new Line2D.Double(line.get(k).x_, line.get(k).y_, line.get(k + 1).x_, line.get(k + 1).y_));
}
}
g.dispose();
}
/** FFT展開計算後の新しい図形を描画する
* @param img 画像出力先(RGB形式であること)
*/
public void writeFFTDiag(BufferedImage img) {
writeDiag(diagFFT_, img);
}
/** 元図形(境界線)を描画する
* @param img 画像出力先(RGB形式であること)
*/
public void writeSrcDiag(BufferedImage img) {
writeDiag(diag_, img);
}
/** 図形を切り出して、線分データを生成する
*/
private int makeDiag() {
diag_.clr();
// 始点の検索
for (int y = 0; y < nHeight_; y ++) {
for (int x = 0; x < nWidth_; x ++) {
// 注目点が有効かつ未検索データだったら始点とする
if ((getPointData(x, y) == 1) && (nBorder_[x + (y * nWidth_)] == 0)) {
// 外側境界 or 内側境界
if (getPointData(x - 1, y) == 0) {
Line line = new Line();
// 外側境界探索
chase8(x, y, 7, line);
// 線分追加
diag_.add(line);
} else if (getPointData(x + 1, y) == 0) {
Line line = new Line();
// 内側境界探索
chase8(x, y, 3, line);
// 線分追加
diag_.add(line);
}
}
}
}
return 0;
}
/** 8連結探索しながら線分データ取得
* @param x 始点x
* @param y 始点y
* @param nChase 最初の探索方向
* @param line 抜き出した線分の格納先
*/
private void chase8(int x, int y, int nChase, Line line) {
int x0 = x, y0 = y;
int x1, y1, x2, y2;
boolean chk; // 探索でみつかったか
XY xy;
// 初期化
x1 = x0; y1 = y0;
x2 = -1; y2 = -1;
chk = false;
// 始点を記憶
line.add(x0, y0);
// 探索ループ 初期座標に帰ってくるまでのループ
while ((x2 != x0) || y2 != y0) {
chk = false;
switch (nChase) {
case 0: // 下を探索
x2 = x1;
y2 = y1 + 1;
if (getPointData(x2, y2) == 1) {
// あった
nChase = 6;
chk = true;
} else {
// 無かった
nChase = 1;
}
break;
case 1: // 右下
x2 = x1 + 1;
y2 = y1 + 1;
if (getPointData(x2, y2) == 1) {
// あった
nChase = 7;
chk = true;
} else {
// 無かった
nChase = 2;
}
break;
case 2: // 右
x2 = x1 + 1;
y2 = y1;
if (getPointData(x2, y2) == 1) {
// あった
nChase = 0;
chk = true;
} else {
// 無かった
nChase = 3;
}
break;
case 3: // 右上
x2 = x1 + 1;
y2 = y1 - 1;
if (getPointData(x2, y2) == 1) {
// あった
nChase = 1;
chk = true;
} else {
// 無かった
nChase = 4;
}
break;
case 4: // 上
x2 = x1;
y2 = y1 - 1;
if (getPointData(x2, y2) == 1) {
// あった
nChase = 2;
chk = true;
} else {
// 無かった
nChase = 5;
}
break;
case 5: // 左上
x2 = x1 - 1;
y2 = y1 - 1;
if (getPointData(x2, y2) == 1) {
// あった
nChase = 3;
chk = true;
} else {
// 無かった
nChase = 6;
}
break;
case 6: // 左
x2 = x1 - 1;
y2 = y1;
if (getPointData(x2, y2) == 1) {
// あった
nChase = 4;
chk = true;
} else {
// 無かった
nChase = 7;
}
break;
case 7: // 左下
x2 = x1 - 1;
y2 = y1 + 1;
if (getPointData(x2, y2) == 1) {
// あった
nChase = 5;
chk = true;
} else {
// 無かった
nChase = 0;
}
break;
}
// ラベリング
if (chk) {
// 記憶
nBorder_[x2 + y2 * nWidth_] = 1;
line.add(x2, y2);
// 座標移動
x1 = x2;
y1 = y2;
}
}
// ここの状態で 始点と終点が一致している。
// そしてvectorの最後に終点が入っているので取り除く
line.delTail();
}
/**
* 画像の指定位置の近傍データ(3x3)を出力する
* 出力データは、操作対象によって判断される。
* 有効点が、1になる。非有効点が、0。
* 領域外は、 0 になる。
* imgSrc_が対象画像。
*
* @param ptx データが欲しい中心座標のx
* @param pty データが欲しい中心座標のy
* @return 対象データを1とするデータ それ以外は 0
*/
private int[] getNearPointData(int ptx, int pty) {
return getNearPointData(imgSrc_, 3, ptx, pty);
}
/**
* 画像の指定位置の近傍データを出力する
* 出力データは、操作対象によって判断される。
* 有効点が、1になる。非有効点が、0。
* 領域外は、 0 になる。
* @param img 画像データ
* @param nN 近傍データの矩形サイズ(3x3なら 3を指定)
* @param ptx データが欲しい中心座標のx
* @param pty データが欲しい中心座標のy
* @return 対象データを1とするデータ それ以外は 0
*/
private int[] getNearPointData(BufferedImage img, int nN, int ptx, int pty) {
// 戻り値を獲得する
int[] result = new int[nN * nN];
// 初期化
for (int i = 0; i < result.length; i++) result[i] = 0;
// データ取得
int nHalf = nN / 2;
for (int y = 0; y < nN; y++) {
int iy = pty + y - nHalf;
if (iy < 0 || iy >= img.getHeight()) continue;
for (int x = 0; x < nN; x++) {
int ix = ptx + x - nHalf;
if (ix < 0 || ix >= img.getWidth()) continue;
int yy = (img.getRGB(ix,iy) & 0x00FF0000) >> 16; // 輝度情報
int a = (img.getRGB(ix,iy) & 0xFF000000) >> 24; // alpha
if (a != 0) {
if (nOp_ == 0) { // 操作対象
// 操作対象が 黒
if (yy < 128) {
result[x + y * nN] = 1;
} else {
result[x + y * nN] = 0;
}
} else {
// 操作対象が 白
if (yy < 128) {
result[x + y * nN] = 0;
} else {
result[x + y * nN] = 1;
}
}
} else {
// 領域外
result[x + y * nN] = 0;
}
}
}
return result;
}
/**
* 画像の指定位置の点が操作対象データだったら、1を返す
* 領域外は、 0 になる。
* imgSrc_が対象画像
*
* @param ptx データが欲しい座標のx
* @param pty データが欲しい座標のy
* @return 対象データを1とするデータ それ以外は 0
*/
private int getPointData(int ptx, int pty) {
return getPointData(imgSrc_, ptx, pty);
}
/**
* 画像の指定位置の点が操作対象データだったら、1を返す
* 領域外は、 0 になる。
* @param img 対象画像データ
* @param ptx データが欲しい座標のx
* @param pty データが欲しい座標のy
* @return 対象データを1とするデータ それ以外は 0
*/
private int getPointData(BufferedImage img, int ptx, int pty) {
// 範囲チェック
if (ptx < 0 || ptx >= img.getWidth()) return 0;
if (pty < 0 || pty >= img.getHeight()) return 0;
// 輝度データ
int yy = (img.getRGB(ptx,pty) & 0x00FF0000) >> 16; // 輝度情報
int a = (img.getRGB(ptx,pty) & 0xFF000000) >> 24; // alpha
int result = 0;
if (a != 0) {
if (nOp_ == 0) { // 操作対象
// 操作対象が 黒
if (yy < 128) {
result = 1;
} else {
result = 0;
}
} else {
// 操作対象が 白
if (yy < 128) {
result = 0;
} else {
result = 1;
}
}
} else {
// 領域外
result = 0;
}
return result;
}
}