SZKの(仮)

カテゴリ : 画像処理

今回はjavaで射影変換をするプログラムを作りました。射影変換の説明自体はまた今度にしたいと思います。
プログラムの機能としては

Enterキー:初期化する
→、←キー:y軸周りに回転する
↑、↓キー:x軸周りに回転する
s、xキー:z軸周りに回転する
z、aキー:ズーム、縮小する
g、hキー:右、左に平行移動する
r、vキー:上、下に平行移動する

一例ですが、実行結果は以下の通りになります。
projective














図1:実行結果
import java.awt.Container; import java.awt.Graphics; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.io.File; import javax.imageio.ImageIO; import javax.swing.JComponent; import javax.swing.JFrame; public class ProjectiveTrMain extends JFrame{ final int window_size_w = 1900; final int window_size_h = 1000; public static void main(String[] args){ ProjectiveTrMain frame = new ProjectiveTrMain(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } public ProjectiveTrMain(){ ProjectiveTr panel = new ProjectiveTr(); Container contentPane = getContentPane(); contentPane.add(panel); setSize(window_size_w, window_size_h); } } class ProjectiveTr extends JComponent{ BufferedImage src = null; //原画像 BufferedImage dst = null; //出力画像 int xangle = 0; //x軸周りの回転角度 int yangle = 0; //y軸周りの回転角度 int zangle = 0; //z軸周りの回転角度 final int focus = 500; //焦点距離(pixel) int[] tr = {100,100, focus}; //平行移動ベクトル int width = 0; //原画像の横幅 int height = 0; //原画像の縦幅 public ProjectiveTr(){ //画像の読み込み src = imgRead("C:/Picture/ExperimentImg/F1010017.jpg"); width = src.getWidth(); height = src.getHeight(); addKeyListener(new KeyAdapter(){ public void keyPressed(KeyEvent e) { int keycode = e.getKeyCode(); switch(keycode){ //画像を初期位置に戻す case KeyEvent.VK_ENTER: xangle = 0; yangle = 0; zangle = 0; tr[2] = focus; System.out.println("RESET"); rendering(xangle, yangle, zangle); break; //画像を拡大する case KeyEvent.VK_Z: tr[2] -= 10; rendering(xangle, yangle, zangle); break; //画像を縮小する case KeyEvent.VK_A: tr[2] += 10; rendering(xangle, yangle, zangle); break; //z軸周りに正の方向に回転 case KeyEvent.VK_S: zangle++; if(zangle >= 180){ zangle = 180; } System.out.println("xangle = " + xangle + " yangle = " + yangle + " zangle = " + zangle); rendering(xangle, yangle, zangle); break; //z軸周りに正の方向に回転 case KeyEvent.VK_X: zangle--; if(zangle <= -180){ zangle = -180; } System.out.println("xangle = " + xangle + " yangle = " + yangle + " zangle = " + zangle); rendering(xangle, yangle, zangle); break; //画像を右に平行移動 case KeyEvent.VK_G: tr[0] += 10; rendering(xangle, yangle, zangle); break; //画像を左に平行移動 case KeyEvent.VK_H: tr[0] -= 10; rendering(xangle, yangle, zangle); break; //画像を上に平行移動 case KeyEvent.VK_V: tr[1] += 10; rendering(xangle, yangle, zangle); break; //画像を下に平行移動 case KeyEvent.VK_R: tr[1] -= 10; rendering(xangle, yangle, zangle); break; //x軸周りに正の方向に回転 case KeyEvent.VK_UP: xangle++; if(xangle >= 180){ xangle = 180; } System.out.println("xangle = " + xangle + " yangle = " + yangle + " zangle = " + zangle); rendering(xangle, yangle, zangle); break; //x軸周りに負の方向に回転 case KeyEvent.VK_DOWN: xangle--; if(xangle <= -180){ xangle = -180; } System.out.println("xangle = " + xangle + " yangle = " + yangle + " zangle = " + zangle); rendering(xangle, yangle, zangle); break; //y軸周りに正の方向に回転 case KeyEvent.VK_RIGHT: yangle++; if(yangle >= 180){ yangle = 180; } System.out.println("xangle = " + xangle + " yangle = " + yangle + " zangle = " + zangle); rendering(xangle, yangle, zangle); //System.out.println("Pressed RIGHT key"); break; //y軸周りに負の方向に回転 case KeyEvent.VK_LEFT: yangle--; if(yangle <= -180){ yangle = -180; } System.out.println("xangle = " + xangle + " yangle = " + yangle + " zangle = " + zangle); rendering(xangle, yangle, zangle); //System.out.println("Pressed LEFT key"); break; } } }); setFocusable(true); rendering(xangle, yangle, zangle); } //画像の読み込み public static BufferedImage imgRead(String filepath){ System.out.println(filepath); File file = new File(filepath); try { BufferedImage img = ImageIO.read(file); return img; }catch(Exception e){ System.err.println("File can't read or broken"); } return null; } //行列の積を計算 double[][] matMul(double[][] mat1, double[][] mat2){ if(mat1[0].length != mat2.length){ return null; } double[][] multiMat = new double[mat1.length][mat2[0].length]; for(int y = 0; y < mat1.length ; y++){ for(int x = 0 ; x < mat2[0].length; x++){ double sum = 0; for(int z = 0; z < mat1[0].length; z++){ sum += mat1[y][z] * mat2[z][x]; } multiMat[y][x] = sum; } } return multiMat; } //回転行列を計算する public double[][] rotate(double[][] mat3d, double thetaX, double thetaY, double thetaZ){ double sin_theta = Math.sin(Math.toRadians(thetaX)); double cos_theta = Math.cos(Math.toRadians(thetaX)); double Rx[][] = { { 1 , 0 , 0 }, { 0 , cos_theta , -sin_theta }, {0 , sin_theta , cos_theta } }; sin_theta = Math.sin(Math.toRadians(thetaY)); cos_theta = Math.cos(Math.toRadians(thetaY)); double Ry[][] = { { cos_theta , 0 , sin_theta }, { 0 ,1 , 0 }, {-sin_theta , 0 , cos_theta } }; sin_theta = Math.sin(Math.toRadians(thetaZ)); cos_theta = Math.cos(Math.toRadians(thetaZ)); double Rz[][] = { { cos_theta , -sin_theta , 0 }, { sin_theta , cos_theta , 0 }, {0 , 0 , 1 } }; double[][] R; R = matMul(Rx , Ry); R = matMul(R , Rz); mat3d = matMul(R , mat3d); return mat3d; } //行列を表示 public void dispMat(double[][] mat){ for(int i = 0; i < mat.length; i++){ for(int j = 0; j < mat[0].length; j++){ System.out.print(mat[i][j] + "\t"); } System.out.println(); } System.out.println(); } //画像を射影変換する public void rendering(double xtheta, double ytheta, double ztheta){ double[][] cmat = { {1,0, 0}, {0,1, 0}, {0, 0, 1}, }; BufferedImage bufimg = src; dst = new BufferedImage(3000, 3000, BufferedImage.TYPE_INT_RGB); int z = 100; cmat = rotate(cmat, xtheta, ytheta, ztheta); dispMat(cmat); int hw = width / 2; int hh = height / 2; for(int y = 0; y < height; y++){ for(int x = 0; x < width; x++){ //射影変換の計算部分 double dstX = focus * (cmat[0][0]*(x - hw) + cmat[0][1] * (y - hh) + cmat[0][2] * z + tr[0]) / (cmat[2][0] * (x - hw) + cmat[2][1]*(y - hh) + cmat[2][2] * z + tr[2]) + hw; double dstY = focus * (cmat[1][0]*(x - hw) + cmat[1][1] * (y - hh) + cmat[1][2] * z + tr[1]) / (cmat[2][0] * (x - hw) + cmat[2][1]*(y - hh) + cmat[2][2] * z + tr[2]) + hh; try{ dst.setRGB((int)dstX, (int)dstY, bufimg.getRGB(x, y)); }catch(Exception e){ } } } repaint(); } public void paintComponent(Graphics g){ g.fillRect(0, 0, width, height); g.drawImage(dst, 0, 0, this); } }

以前書いた記事でトラッキングのための航海画像の連続画像300枚を使ってトラッキングしてみたいと思います。大まかな流れとしては

画像1枚読み込む→テンプレートマッチング→矩形を表示・・・・と1フレームごとにテンプレートマッチングを行うことをするだけでよいのですが、実際やってみると1枚テンプレートマッチングするのに結構時間がかかるので、リアルタイムでの処理はまず無理です。そこで、今回は疎密探索法(ピラミッド画像を使う方法)をベースにしたアルゴリズムによってトラッキングしたいと思います。

疎密探索法とは「イメージピラミッドを使って探索する方法」で、イメージピラミッドとは「さまざまな解像度の集合」で、具体的には図1のようなものをさします。図1の画像は原画像の横、縦の長さを半分にしていき、ガウシアンフィルタをかけたものです。
downscale














                図1:イメージピラミッド

これを使って、まず、解像度の低い画像に対してテンプレートマッチングします、そして検出した位置を次にその位置を2倍して、解像度を1段階上げた画像に対してテンプレートマッチングをし、また2倍して・・・・原画像のサイズになるまでこれを繰り返します。そうすることによって探索する時間が短くなります。(地図である場所を見るときいきなり縮尺を最大にしてから見ないのと一緒だな、と思いましたが。)
疎密探索については以上です。

今回乗せるプログラムのトラッキングアルゴリズムは、

1枚目のみ疎密探索法によりテンプレートマッチングをおこなう。
                ↓
2枚目は追跡する物体が大きく移動しないので、検出した位置の8近傍に対してテンプレートマッチングを行う
                ↓
3枚目以降も8近傍のみテンプレートマッチングを行う。

となっています。
ソースコードは以下のとおりです。
注意:赤字のパスを設定してください。
ちなみに、下記の実行環境では1フレームあたり0.06secでした。

実行環境
CPU:intel core i7-2677M @1.80GHz 1.8GHz
OS:Windows7 64bit
Memory: 4GB


Tracking.java
import java.awt.Color; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JFrame; public class Tracking { DispImg frame; GaussianPyramids tp = new GaussianPyramids(); GaussianPyramids ip = new GaussianPyramids(); int[][] temp; int[][] img; BufferedImage currentimg = null; int posxl1 = 0; int posyl1 = 0; int posxl2 = 0; int posyl2 = 0; int posx3 = 0; int posy3 = 0; int cx = 0; //被探索画像上の物体のx座標 int cy = 0; //被探索画像上の物体のx座標 BufferedImage img1 = null; public static void main(String[] args){ Tracking tm = new Tracking(); tm.trackingStart(); } public Tracking(){ temp = imgToGray(imgRead("./camera/shipTemp.jpg")); //テンプレート画像の読み読み currentimg = imgRead("./camera/trck000000.jpg"); //最初の被探索画像の読み込み img = imgToGray(currentimg); //GaussianPyramids tp = new GaussianPyramids(); tp.makePyramids(temp); //GaussianPyramids ip = new GaussianPyramids(); ip.makePyramids(img); int[] lev3 = tpmatch(ip.level3, tp.level3); int[] lev2 = tpmatch1(ip.level2, tp.level2, lev3[0], lev3[1]); //System.out.println(lev2[0] + " " + lev2[1]); int[] lev1 = tpmatch1(ip.level1, tp.level1, lev2[0], lev2[1]); int[] lev0 = tpmatch1(img, temp, lev1[0], lev1[1]); //System.out.println(lev0[0] + " " + lev0[1]); cx = lev0[0]; cy = lev0[1]; new Thread(){ public void run(){ frame = new DispImg(currentimg, cx, cy, temp[0].length, temp.length); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }.start(); } public void trackingStart(){ int count = 1; int time = 0; for(int i = 1; i < 300; i++){ long t1 = System.currentTimeMillis(); String str = String.format("%06d",i); currentimg = imgRead("./camera/trck"+ str + ".jpg"); img = imgToGray(currentimg); if(count == 1){ ip.makePyramids(img); int[] lev3 = tpmatch(ip.level3, tp.level3); int[] lev2 = tpmatch1(ip.level2, tp.level2, lev3[0], lev3[1]); int[] lev1 = tpmatch1(ip.level1, tp.level1, lev2[0], lev2[1]); int[] lev0 = tpmatch1(img, temp, lev1[0], lev1[1]); cx = lev0[0]; cy = lev0[1]; count++; } tpmatch2(); frame.setImg(currentimg, cx, cy); long t2 = System.currentTimeMillis(); time += (t2 - t1); System.out.println(t2 - t1); if(i % 10 == 0){ updateTemp(cx, cy); } } System.out.println((double)time / 300); } //テンプレートの状態を更新する public void updateTemp(int cx, int cy){ for(int y = 0; y < temp.length; y++){ for(int x = 0; x < temp[0].length; x++){ temp[y][x] = img[cy + y][cx + x]; } } tp.makePyramids(temp); } //すべての画素に対してテンプレートマッチングする public int[] tpmatch(int[][] img, int[][] temp){ int min = 1000000; int[] res = new int[2]; for(int y = 0; y < img.length - temp.length; y++){ for(int x = 0; x < img[0].length - temp[0].length; x++){ int sum = 0; flag : for(int ty = 0; ty < temp.length; ty++){ for(int tx = 0; tx < temp[0].length; tx++){ sum += Math.abs(img[y + ty][x + tx] - temp[ty][tx]); if(sum > min){ break flag; } } } if(min > sum){ res[0] = x; res[1] = y; min = sum; } } } //System.out.println(res[0] + " " + res[1]); return res; } //縮小した画像上でテンプレートマッチングをするメソッド public int[] tpmatch1(int[][] img, int[][] temp, int xp, int yp){ int min = 1000000; int[] pos = new int[2]; //System.out.println(xp + " " + yp); int startX = xp * 2; int startY = yp * 2; if(startX <= 0){ startX = 1; } for(int y = startY - 1; y <= startY + 1; y++){ for(int x = startX - 1; x <= startX + 1; x++){ int sum = 0; for(int ty = 0; ty < temp.length; ty++){ for(int tx = 0; tx < temp[0].length; tx++){ sum += Math.abs(img[y + ty][x + tx] - temp[ty][tx]); } } if(min > sum){ pos[0] = x; pos[1] = y; min = sum; } } } return pos; } //物体の周辺部分のみテンプレートマッチングする public void tpmatch2(){ int min = 1000000; //System.out.println(xp + " " + yp); if(cx <= 0){ cx = 1; } for(int y = cy - 1; y <= cy + 1; y++){ for(int x = cx - 1; x <= cx + 1; x++){ int sum = 0; for(int ty = 0; ty < temp.length; ty++){ for(int tx = 0; tx < temp[0].length; tx++){ sum += Math.abs(img[y + ty][x + tx] - temp[ty][tx]); } } if(min > sum){ cx = x; cy = y; min = sum; } } } } //画像を読み込むメソッド public BufferedImage imgRead(String filepath){ BufferedImage img = null; try{ img = ImageIO.read(new File(filepath)); return img; }catch(IOException e){ return null; } } //画像をグレースケールの配列に変換するメソッド public int[][] imgToGray(BufferedImage img){ int width = img.getWidth(); int height = img.getHeight(); int[][] gray_img = new int[height][width]; for(int y = 0; y < height; y++){ for(int x = 0; x < width; x++){ int rgb = img.getRGB(x, y); rgb -= 0xFF000000; int r = (rgb & 0xFF0000) >> 16; int g = (rgb & 0xFF00) >> 8 ; int b = rgb & 0xFF; int gray = (b + g + r) / 3; gray_img[y][x] = gray; } } return gray_img; } } //ガウシアンピラミッド class GaussianPyramids { int[][] level1; int[][] level2; int[][] level3; int[][] smooth; public void makePyramids(int[][] gray){ smooth = gaussian_filter(gray); level1 = downscale(smooth); smooth = gaussian_filter(level1); level2 = downscale(smooth); smooth = gaussian_filter(level2); level3 = downscale(smooth); } public int[][] downscale(int[][] gray){ int[][] down = new int[gray.length / 2][gray[0].length / 2]; for(int y = 0; y < down.length; y++){ int gy = y * 2; for(int x = 0; x < down[0].length; x++){ int gx = x * 2; down[y][x] = (gray[gy][gx] + gray[gy][gx + 1] + gray[gy + 1][gx] + gray[gy + 1][gx + 1]) / 4; } } return down; } public int[][] gaussian_filter(int[][] gray){ double[] GAUSSIAN_FILTER = {0.0625, 0.125, 0.25}; int[][] buf_img = new int[gray.length][gray[0].length]; for(int y = 1; y < gray.length - 1; y++){ for(int x = 1; x < gray[0].length - 1; x++){ double sum = 0; sum = (gray[y - 1][x - 1] + gray[y - 1][x + 1] + gray[y + 1][x - 1] + gray[y + 1][x + 1]) * GAUSSIAN_FILTER[0] + (gray[y - 1][x] + gray[y][x - 1] + gray[y][x + 1] + gray[y + 1][x]) * GAUSSIAN_FILTER[1] + gray[y][x] * GAUSSIAN_FILTER[2]; buf_img[y][x] = (int)(Math.round(sum)); } } return buf_img; } public int[][] getLevel1(){ return level1; } public int[][] getlevel2(){ return level2; } public int[][] getLevel3(){ return level3; } } //画像表示を行うフレーム class DispImg extends JFrame { BufferedImage img; BufferedImage currentimg = null; int x = 0; int y = 0; int width = 0; int height = 0; public DispImg(BufferedImage img, int x, int y, int width, int height){ this.img = img; this.x = x; this.y = y; this.width = width; this.height = height; setSize(img.getWidth() + 4, img.getHeight() + 30); } public void setImg(BufferedImage img, int x, int y){ this.x = x; this.y = y; currentimg = img; repaint(); } public void paint(Graphics g){ g.drawImage(currentimg, 4, 30, this); g.setColor(Color.blue); g.drawRect(x + 4, y + 30, width, height); } }

前回は航海画像アップロードしました。
今回は300枚ある画像を再生したいと思います。

画像はtrck000000~trck000299までの通し番号がついているので、これを利用してfor文をまわせば簡単に再生できます。また、これらの画像の1枚当たりの間隔は0.1secなので、1枚表示したらtwait()メソッドにより0.1秒待つことにします。僕の場合ではshipdataのファイルをC:\Picture\のところにおいてあります。それぞれファイルを置く場所は違いますので、指定してください。(相対パスでもいけるのですが。)

ImagePlay.java

import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JFrame; public class ImagePlay { public static void main(String[] args) throws IOException{ ImageFrame frame = new ImageFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); for(int i = 0; i < 300; i++){ String num = String.format("%06d",i);      //画像ファイルの読み込み BufferedImage cur_img = ImageIO.read(new File("C:/Picture/shipdata/trck" + num + ".JPG")); twait(); frame.disp(cur_img); //新しい画像をセットする frame.repaint(); } } public static void twait(){ try{ Thread.sleep(100); //0.1sec待つ }catch(InterruptedException e){} } } //画像を表示するためのクラス class ImageFrame extends JFrame{ final int WIDTH = 960; final int HEIGHT = 540; BufferedImage cur_img = null; public ImageFrame(){ setSize(WIDTH, HEIGHT); setLocationRelativeTo(null); } public void disp(BufferedImage img){ cur_img = img; } public void paint(Graphics g){ g.drawImage(cur_img, 0, 0, this); } }

今回はテンプレートマッチングを高速化するための方法を紹介したいと思います。

1、残差逐次検定法
→あるしきい値を超えたら加算を打ち切り、次の位置での計算に移る方法。

テンプレートマッチングその2の二乗誤差を計算する部分を再掲します。

for(int y = 0; y < srchH - tempH; y++){
   for(int x = 0; x < srchW - tempW; x++){
        int ssd = 0;     //二乗誤差の和(SSD)
        for(int yt = 0; yt < tempH; yt++){
             for(int xt = 0; xt < tempW; xt++){
                  dif = srch[y + yt][x + xt] - temp[yt][xt];
                   ssd += Math.pow(dif, 2);
              }
         }
    
         if(min_ssd > ssd){
         xpos = x;
         ypos = y;
         min_ssd = ssd;
    }
}

目的は、もっとも小さい二乗誤差(min_ssd)を計算することです。
よって、加算している途中で変数ssdの値がmin_ssdの値を超えたら、まずそこが一致する場所
とは考えられませんので、加算を打ち切って次の位置に移ることが適切でしょう。
これが残差逐次検定法です。

 for(int y = 0; y < srchH - tempH; y++){
        for(int x = 0; x < srchW - tempW; x++){
                int ssd = 0;                    //二乗誤差の和(SSD)
                flag : for(int yt = 0; yt < tempH; yt++){
                    for(int xt = 0; xt < tempW; xt++){
                        dif = srch[y + yt][x + xt] - temp[yt][xt];
                        ssd += dif * dif;
          //もし、min_ssdの値を超えたら、次の位置に移る。
                        if(ssd > min_ssd){
                            continue flag;
                        }

                    }
                }
               
                if(min_ssd > ssd){
                    xpos = x;
                    ypos = y;
                    min_ssd = ssd;
                }
               
       }
  }

青色のところなのですが、以前は
ssd += Math.pow(dif, 2);
となっていたと思います。
ただ、これだとテーラー展開されてしまうらしいので、青色の式に直しました。
以下にまとめたコードを載せます。

TemplateMatching.java
import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import javax.imageio.ImageIO; import javax.swing.JFrame; public class TemplateMatching { static int xpos = 0; //テンプレートが一致したときのx座標 static int ypos = 0; //テンプレートが一致したときのy座標 public static void main(String[] args){ //テンプレート、被探索画像の読み込み BufferedImage tempImg = imgRead("./temp.jpg"); BufferedImage srchImg = imgRead("./srch.jpg"); //テンプレート、被探索画像を配列に変換 int[][] temp = imgToArray(tempImg); int[][] srch = imgToArray(srchImg); //テンプレート、被探索画像をグレースケールに変換 int[][] g_temp = trans_grayscale(temp); int[][] g_srImg = trans_grayscale(srch); //テンプレートマッチングを行う templateMatcing(g_temp, g_srImg); //テンプレートマッチングした結果を表示する DispFrame frame = new DispFrame(srchImg, tempImg, xpos, ypos); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } //画像ファイルを読み込むメソッド public static BufferedImage imgRead(String file_path){ BufferedImage img = null; try{ img = ImageIO.read(new File(file_path)); return img; }catch(Exception e){ return null; } } //画像ファイルを2次元配列に変換するメソッド public static int[][] imgToArray(BufferedImage img){ int width = img.getWidth(); int height = img.getHeight(); int[][] imgA = new int[height][width]; for(int y = 0; y < height; y++){ for(int x = 0; x < width; x++){ imgA[y][x] = img.getRGB(x, y); //画像上の(x, y)におけるRGB値を取得 } } return imgA; } //RGB値からグレースケールに変換するメソッド public static int[][] trans_grayscale(int[][] img){ int width = img[0].length; int height = img.length; int[][] gray_img = new int[height][width]; for(int y = 0; y < img.length; y++){ for(int x = 0; x < img[0].length; x++){ int rgb = img[y][x] - 0xFF000000; //アルファ値を取り除く int b = (rgb & 0xFF); //青の成分を取得 int g = (rgb & 0xFF00) >> 8; //緑の成分を取得 int r = (rgb & 0xFF0000) >> 16; //赤の成分を取得 int gray = (b + g + r) / 3; //グレーの値に変換 gray_img[y][x] = gray; } } return gray_img; } //テンプレートマッチングするメソッド public static void templateMatcing(int[][] temp, int[][] srch){ int tempW = temp[0].length; int tempH = temp.length; int srchW = srch[0].length; int srchH = srch.length; int min_ssd = Integer.MAX_VALUE; //最小の二乗誤差の和 int dif = 0;       //非探索画像とテンプレートのピクセル単位での差 for(int y = 0; y < srchH - tempH; y++){ for(int x = 0; x < srchW - tempW; x++){ int ssd = 0; //二乗誤差の和(SSD) flag : for(int yt = 0; yt < tempH; yt++){ for(int xt = 0; xt < tempW; xt++){ dif = srch[y + yt][x + xt] - temp[yt][xt]; ssd += dif * dif; //もし、min_ssdの値を超えたら、次の位置に移る。 if(ssd > min_ssd){ continue flag; } } } if(min_ssd > ssd){ xpos = x; ypos = y; min_ssd = ssd; } } } System.out.println("Min_SSD =" + min_ssd); //SSDの最小値を表示 System.out.println("position=" + xpos + "," + ypos); //テンプレートの一致した座標を表示 } } class DispFrame extends JFrame { BufferedImage srch; //フレーム上に表示するための被探索画像 int xpos = 0; //テンプレートが一致したx座標 int ypos = 0; //テンプレートが一致したy座標 int temp_width; int temp_height; DispFrame(BufferedImage srch, BufferedImage temp, int xpos, int ypos){ this.xpos = xpos; this.ypos = ypos; this.srch = srch; temp_width = temp.getWidth(); temp_height = temp.getHeight(); setSize(srch.getWidth(), srch.getHeight()); setTitle("RESULT"); } //非探索画像上のテンプレートが一致したところに四角を囲むメソッド public void paint(Graphics g){ Graphics2D off = srch.createGraphics(); off.setColor(new Color(0,0,255)); //四角の色を青にする off.drawRect(xpos, ypos, temp_width, temp_height); g.drawImage(srch, 0, 0, this); } }

研究で使う必要が出た判別分析法をjavaで実装しました。
とりあえず動かしたい方は、画像のファイルパスを設定してください。
大きい画像(3000×3000くらいの)をいれるとヒープのサイズがオーバーする可能性があるので、
JavaVMのヒープのサイズを512MB,あるいはそれ以上に設定しておくことがよいと思われます。


F1
               図1:原画像

output
                                     図2:処理結果

LinearDiscriminant.java

import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.File; import java.math.BigDecimal; import javax.imageio.ImageIO; import javax.swing.JFrame; public class LinearDiscriminant { private final int allpix; private int[] hist; private int[] ac; private BufferedImage fimg; public static void main(String[] args){ LinearDiscriminant bin = new LinearDiscriminant(); DispImg frame = new DispImg(bin.getImg()); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } public LinearDiscriminant(){ BufferedImage img = imgRead("picture.jpg"); allpix = img.getWidth() * img.getHeight(); double[][] gray = grayscale(img); double[][] smooth = gaussian_filter(gray); hist = histogram(smooth); ac = accumulation(hist); double t = threshold(); System.out.println("threshold = " + t); fimg = binarize(img, gray, t); } private BufferedImage imgRead(String file_path){ BufferedImage img = null; try{ img = ImageIO.read(new File(file_path)); return img; }catch(Exception e){ return null; } } private double[][] grayscale(BufferedImage img){ double[][] gray = new double[img.getHeight()][img.getWidth()]; for(int y = 0; y < img.getHeight(); y++){ for(int x = 0; x < img.getWidth(); x++){ int rgb = img.getRGB(x, y); rgb -= 0xFF000000; int r = (rgb & 0xFF0000) >> 16; int g = (rgb & 0xFF00) >> 8 ; int b = rgb & 0xFF; double l = (double)(b + g + r) / 3; gray[y][x] = l; } } return gray; } private double[][] gaussian_filter(double[][] gray){ double[] GAUSSIAN = {0.0625, 0.125, 0.25}; double[][] buf = new double[gray.length][gray[0].length]; for(int y = 1; y < buf.length - 1; y++){ for(int x = 1; x < buf[0].length - 1; x++){ double sum = 0; sum = (gray[y-1][x-1] + gray[y-1][x+1] + gray[y+1][x-1] + gray[y+1][x+1]) * GAUSSIAN[0] + (gray[y-1][x] + gray[y][x-1] + gray[y][x+1] + gray[y+1][x]) * GAUSSIAN[1] + gray[y][x] * GAUSSIAN[2]; buf[y][x] = sum; } } return buf; } private int[] histogram(double[][] gray){ int[] histogram = new int[256]; for(int y = 0; y < gray.length; y++){ for(int x = 0; x < gray[0].length; x++){ histogram[(int)(Math.round(gray[y][x]))]++; } } return histogram; } private int[] accumulation(int[] hist){ int ac[] = new int[hist.length]; int sum = 0; for(int i = 0; i < hist.length; i++){ sum += hist[i]; ac[i] = sum; } return ac; } private double threshold(){ double threshold = 0; BigDecimal max = BigDecimal.ZERO; BigDecimal separation_metrics; BigDecimal pbB, pwB, mul; for(double t = 0.5; t < 255; t++){ int pb = ac[(int)(t - 0.5)]; int pw = allpix - pb; double mb = classBlackMean(t); double mw = classWhiteMean(t); if(mb == -1 || mw == -1){ continue; } //System.out.println(t + " : " +mb); pbB = BigDecimal.valueOf(pb); pwB = BigDecimal.valueOf(pw); mul = pbB.multiply(pwB); separation_metrics = mul.multiply(BigDecimal.valueOf(Math.pow((mb - mw), 2))); //System.out.println(separation_metrics); if(separation_metrics.compareTo(max) > 0){ max = separation_metrics; //System.out.println("MAX = " + max); threshold = t; } } return threshold; } private double classBlackMean(double t){ double sum = 0; double mean; for(int i = 0; i < t; i++){ sum += hist[i] * i; } if(ac[(int)t] == 0){ return -1; }else{ mean = sum / ac[(int)t]; return mean; } } private double classWhiteMean(double t){ double sum = 0; double mean; for(int i = (int)(t + 0.5); i <= 255; i++){ sum += hist[i] * i; } if(allpix - ac[(int)t] == 0){ return -1; }else{ mean = sum / (allpix - ac[(int)t]); return mean; } } private BufferedImage binarize(BufferedImage img, double[][] gray, double t){ for(int y = 0; y < img.getHeight(); y++){ for(int x = 0; x < img.getWidth(); x++){ if(gray[y][x] > t){ img.setRGB(x, y, 16777215); //white }else{ img.setRGB(x, y, 0); //black } } } return img; } public BufferedImage getImg(){ return fimg; } } class DispImg extends JFrame { BufferedImage img; final int param = 1; DispImg(BufferedImage img){ this.img = img; setSize(img.getWidth() / param + 4, img.getHeight() / param + 38); } public void paint(Graphics g){ g.drawImage(img, 8, 30, img.getWidth() / param, img.getHeight() / param, this); } }

このページのトップヘ