SZKの(仮)

現在(2016/03/31)におけるソフトウェアの状況は以下のとおりです。

2016/04/01:AIS_DECODER1.3.1においてmessageID18の"radiostatus"のデコードに誤りがあります(現在修正済み)。
2016/03/31:AIS_DECODER1.3.1をリリースしました。
2016/03/28:vectorのほうにアップロードしたので、反映され次第また更新します。
2016/03/24:全面的に改修してほぼ完成した状態です。ver1.3.1として来週中にはアップする予定です。
2016/02/10:バグは今月中に修正する予定です。(21:03)追記:改めてコードを見直してみると色々思うことがあるので、やっぱり全面的に改修します。公開は早ければ今年度中を目標にやります。
2015/11/06:AIS_DECODER1.2.1ですが、デコード時において、正しくないAIVDMセンテンスが混ざっていると途中でプログラムが落ちる可能性があります。(現在改修中)また、現在のバージョンからGUI周り等全面的に改修する予定です。

1.AIS_DECODER Ver.1.3.1 (Windows版)
公開状況:公開中
ダウンロード先:(http://www.vector.co.jp/soft/winnt/business/se508058.html)

今回は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); } }

このページのトップヘ