/*
* 作成日: 2008/06/14
*/
package jp.ac.fit.asura.nao.vision.perception;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import jp.ac.fit.asura.nao.vision.GCD;
/**
* Blob関係のクラス
*
* @author $Author: sey $
*
* @version $Id: BlobVision.java 717 2008-12-31 18:16:20Z sey $
*
*/
public class BlobVision extends AbstractVision {
public static final int MAX_BLOBS = 200;
protected int[] nBlobs;
protected Blob[][] blobInfo;
/**
* 横線の情報を保持するクラス.
*
* ここでの線とは,画像の水平ライン(一次元)のなかで連続した色のまとまりのことである.
*
* 例えば,***BBB**bbb***という行の場合はカラーBlueのBBBとbbbの二つのSegmentが作成される.
*
* VisualCortexでの線(2D)とは違うので注意.
*
*/
private static class Segment1D {
int xmin, xmax;
int length;
// このSegmentが属するblobId
int blobId;
byte color;
public Segment1D(int xmin, int xmax, byte color) {
set(xmin, xmax, color);
}
void set(int xmin, int xmax, byte color) {
this.xmin = xmin;
this.xmax = xmax;
length = xmax - xmin + 1;
this.color = color;
blobId = -1;
if (color < 0) {
// System.out.println(this);
this.color = 0;
}
}
public String toString() {
return String.format("Start: %d End: %d Color: %d Number: %d",
xmin, xmax, (int) color, (int) blobId);
}
}
public static class Blob {
public int xmin, ymin, xmax, ymax;
public int mass;
public boolean bigEnough;
void set(int xi, int xa, int yi, int ya, int a) {
xmin = xi;
xmax = xa;
ymin = yi;
ymax = ya;
mass = a;
}
/**
* 行yのセグメントsegをこのblobにマージします.
*
* @param blob2
*/
public void merge(int y, Segment1D seg) {
xmin = Math.min(xmin, seg.xmin);
xmax = Math.max(xmax, seg.xmax);
ymin = Math.min(ymin, y);
ymax = Math.max(ymax, y);
mass += seg.length;
}
/**
* blob2をこのblobにマージします.
*
* @param blob2
*/
public void merge(Blob blob2) {
xmin = Math.min(xmin, blob2.xmin);
xmax = Math.max(xmax, blob2.xmax);
ymin = Math.min(ymin, blob2.ymin);
ymax = Math.max(ymax, blob2.ymax);
mass += blob2.mass;
}
public String toString() {
return String.format("X: %d-%d Y:%d-%d Mass:%d", xmin, xmax, ymin,
ymax, mass);
}
public Rectangle getArea() {
return new Rectangle(xmin, ymin, xmax - xmin + 1, ymax - ymin + 1);
}
}
/**
*
*/
public BlobVision() {
// BLACKとGREENはblobにならないので -2
nBlobs = new int[GCD.COLOR_NUM - 2];
blobInfo = new Blob[GCD.COLOR_NUM - 2][BlobVision.MAX_BLOBS];
for (int i = 0; i < blobInfo.length; i++) {
for (int j = 0; j < BlobVision.MAX_BLOBS; j++)
blobInfo[i][j] = new Blob();
}
}
public void formBlobs() {
byte[] plane = getContext().gcdPlane;
int width = getContext().image.getWidth();
int height = getContext().image.getHeight() - 1;
// 初期化
Arrays.fill(nBlobs, 0);
for (int i = 0; i < blobInfo.length; i++)
for (int j = 0; j < BlobVision.MAX_BLOBS; j++)
blobInfo[i][j].mass = 0;
// ラインごとの線
List<List<Segment1D>> segments = new ArrayList<List<Segment1D>>(height);
// 各行でSegmentを作る
for (int i = 0; i < width * height; i += width) {
List<Segment1D> list = new ArrayList<Segment1D>(16);
// 二つの同じ色に挟まれた1pixelを補完する
// ex:C*CとなっているのをCCCにする
for (int j = i; j < i + width - 2; j++) {
if (plane[j] == plane[j + 2])
plane[j + 1] = plane[j];
}
// 一行分の画像からSegmentを抽出する
for (int start = 0, end = 0, j = i; end < width; start = end) {
byte color = plane[j];
while (end < width && plane[j] == color) {
end++;
j++;
}
// 黒, 緑, UNKNOWN以外なら線分を作成
if (color != GCD.cBLACK && color != GCD.cGREEN && color != -1) {
list.add(new Segment1D(start, end - 1, color));
}
}
segments.add(list);
}
// Blob を作るのだ! とりあえず, 最初のラインに含まれる blob と成り得る物を列挙
for (int i = 0; i < segments.get(0).size(); i++) {
Segment1D seg = segments.get(0).get(i);
allocateBlob(0, seg);
}
// Merge blobs every 2 lines
// 各水平ラインごとにマージしていきましょう
for (int y = 1; y < height; y++) {
int prev_line_i = 0;
// 現在の行のSegmentをマージ
for (int segNo = 0; segNo < segments.get(y).size(); segNo++) {
assert (0 <= segNo && segNo < width);
// 現在見ている segment
Segment1D current = segments.get(y).get(segNo);
// 直前のラインのすべての segment を見ていく
for (; prev_line_i < segments.get(y - 1).size(); prev_line_i++) {
// 直前のラインで現在見ている segment
Segment1D prev = segments.get(y - 1).get(prev_line_i);
// 確実に交差しない条件
if (prev.xmin > current.xmax)
break;
// マージが必要とされる条件
// - オーバーラップしている
// - 同じ色である
// - 異なった blob 番号を持っている
if (prev.color == current.color && // same color condition
prev.xmax >= current.xmin && // overlap check
prev.blobId != current.blobId // blob id check
) {
// current.blob_number == -1
// ということは現在のsegmentはまだblobになっていない
// じゃぁ、前のラインのblob にマージするのだ!
if (current.blobId == -1) {
current.blobId = prev.blobId;
blobInfo[prev.color][prev.blobId].merge(y, current);
} else if (prev.blobId == -1) {
// current は blob に成ってるけど info1 が blob になっていない場合
prev.blobId = current.blobId;
blobInfo[current.color][current.blobId].merge(
y - 1, prev);
} else if (prev.blobId < MAX_BLOBS) {
// info1, info2 ともに blob に成っている場合
// blob 番号が小さい方にマージする
// とりあえずprevのblob番号のほうが小さいと仮定
Blob minBlob = blobInfo[prev.color][prev.blobId];
Blob maxBlob = blobInfo[current.color][current.blobId];
if (prev.blobId > current.blobId) {
// 逆だった
Blob t = minBlob;
minBlob = maxBlob;
maxBlob = t;
prev.blobId = current.blobId;
} else {
current.blobId = prev.blobId;
}
minBlob.merge(maxBlob);
// blob 番号が大きい方の blob は mass = 0 とすることで無効化
maxBlob.mass = 0;
}
}
}
// マージの条件にマッチしなかった場合は新しい blob として生成される
if (current.blobId == -1) {
allocateBlob(y, current);
}
// ちょっと行き過ぎたので一つ戻す
if (prev_line_i > 0)
prev_line_i--;
}
}
}
/**
* segmentに新しいblobを割り当てます. 割り当てに成功した場合はtrueを,blobに空きがない場合はfalseを返します.
*
* @param blobInfo
* @param nblob
* @param y
* @param segment
*/
private boolean allocateBlob(int y, Segment1D segment) {
// blob 番号が MAX_BLOBS までいった場合は空き(mass == 0) の blob
// 番号を探して, 再利用
if (nBlobs[segment.color] >= MAX_BLOBS) {
for (int i = 0; i < MAX_BLOBS; i++) {
if (blobInfo[segment.color][i].mass == 0) {
blobInfo[segment.color][i].set(segment.xmin, segment.xmax,
y, y, segment.length);
segment.blobId = i;
return true;
}
}
// blobの空きがない.
return false;
} else {
segment.blobId = nBlobs[segment.color]++;
blobInfo[segment.color][segment.blobId].set(segment.xmin,
segment.xmax, y, y, segment.length);
}
return true;
}
/**
* Blobからオブジェクトになりそうなやつを探す.
*
* 選択基準として Blob の中から大きいやつを選択していき max個まで格納.
*/
public List<Blob> findBlobs(byte colorIndex, int max, int massThreshold) {
Blob[] binfo = blobInfo[colorIndex];
int upto = Math.min(max, nBlobs[colorIndex]);
List<Blob> list = new ArrayList<Blob>(upto);
for (int i = 0; i < upto; i++) {
int biggest = -1, bigIndex = -1;
for (int j = 0; j < nBlobs[colorIndex]; j++) {
if (binfo[j].mass > biggest && !list.contains(binfo[j])) {
// すでに探索したblob以外なら候補にする
biggest = binfo[j].mass;
bigIndex = j;
}
}
if (bigIndex == -1) {
// その色のblobは一個もないよ!
return list;
}
if (biggest < massThreshold) {
// all remaining blobs are so small,
// not considered as half beacon
return list;
}
// now bigIndex is set to the index of the next biggest blob
list.add(binfo[bigIndex]);
}
// set the variable so the caller knows how many halfbeacons of this
// type we found
return list;
}
}