Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
HTML5 canvas 画像処理 輪郭追跡 Freeman chain code
// 輪郭追跡を行い,輪郭部のみに色を出力する
function contourDetection(contextIn, contextOut, width, height) {
var imgData=contextIn.getImageData(0, 0, width, height);
// 読み取り用ピクセルデータ(書き換えない)
var pixelData = new Array(width);
for(var i=0; i<width; ++i) {
pixelData[i] = new Array(height);
for(var j=0; j<height; ++j) {
pixelData[i][j] = imgData.data[4*(width*j+i)];
}
}
// 更新用ピクセルデータ
var buf=new Array(width);
for(var i=0; i<width; ++i) {
buf[i] = new Array(height);
for(var j=0; j<height; ++j) {
buf[i][j] = 255;
}
}
// あるピクセルを * で表し、
// 周囲のピクセルを下のように番号を付けて表す
// 3 2 1
// 4 * 0
// 5 6 7
var nextCode=[7, 7, 1, 1, 3, 3, 5, 5];
// Freeman's chain code
var chainCode=[
[1, 0], [1, -1], [0, -1], [-1, -1],
[-1, 0], [-1, 1], [0, 1], [1, 1]
];
var rel; // relativee pisition
var relBuf; // previous rel
var dPx = []; // detected pixel 輪郭として検出されたピクセルのテンポラリー変数
var startPx = []; // 輪郭追跡の開始ピクセル
var sPx = []; // searching pixel
var isClosed = false; // 輪郭が閉じていれば true
var isStandAlone; // 孤立点ならば true
var pxs=[]; // 輪郭のピクセル座標の配列を格納するテンポラリー配列
var boundaryPxs=[]; // 複数の輪郭を格納する配列
var pxVal; // 着目するピクセルの色
var duplicatedPx = []; // 複数回、輪郭として検出されたピクセル座標を格納(将来的にこのような重複を許さないアルゴリズムにしたい)
while(1) {
// 輪郭追跡開始ピクセルを探す
dPx = searchStartPixel();
// 画像全体が検索された場合はループを終了
if(dPx[0]==width && dPx[1]==height) {
break;
}
pxs=[];
pxs.push([dPx[0], dPx[1]]);
startPx=[dPx[0], dPx[1]];
isStandAlone=false;
isClosed=false;
relBuf=5; // 最初に調べるのは5番
// 輪郭が閉じるまで次々に周囲のピクセルを調べる
while(!isClosed){
for(var i=0; i<8; ++i) {
rel = (relBuf+i)%8; // relBufから順に調べる
sPx[0] = dPx[0]+chainCode[rel][0];
sPx[1] = dPx[1]+chainCode[rel][1];
// sPx が画像上の座標外ならば白として評価する
if(sPx[0]<0 || sPx[0]>=width || sPx[1]<0 || sPx[1]>=height){
pxVal = 255;
} else {
pxVal = pixelData[sPx[0]][sPx[1]]
}
// もし調べるピクセルの色が黒ならば新しい輪郭とみなす
// 最初のピクセルに戻れば次の輪郭を探す
// 周囲の8ピクセルがすべて白ならば孤立点なので次の輪郭を探す
if(pxVal==0) {
if(buf[sPx[0]][sPx[1]]==0) {
duplicatedPx.push([sPx[0],sPx[1]]);
}
// 検出されたピクセルが輪郭追跡開始ピクセルならば
// 追跡を終了して次の輪郭に移る
if(sPx[0]==startPx[0] && sPx[1]==startPx[1]) {
isClosed=true;
break;
}
buf[sPx[0]][sPx[1]]=0; // 検出された点を黒にする
dPx[0]=sPx[0];
dPx[1]=sPx[1];
pxs.push([dPx[0], dPx[1]]);
relBuf=nextCode[rel];
break;
}
if(i==7) {
isStandAlone = true;
}
}
if(isStandAlone) {
break;
}
}
boundaryPxs.push(pxs);
}
// 左上から操作し開始点(白から黒に代わるピクセル)を見つける
function searchStartPixel() {
var idx;
var x, y;
var leftPx;
for(y=0; y<height; ++y) {
for(x=0; x<width; ++x) {
if(x==0) {
leftPx = 255;
} else {
leftPx=pixelData[x-1][y];
}
if(leftPx == 255 && pixelData[x][y] == 0 && buf[x][y]==255) {
buf[x][y]=0;
return [x, y];
}
}
}
return [width, height];
}
// 輪郭ごとに色を変えて描画する
contextOut.clearRect(0,0,width,height);
colors = ['red', 'green', 'blue', 'orange', 'purple', 'cyan'];
for(var i=0; i<boundaryPxs.length; ++i) {
contextOut.strokeStyle=colors[i%colors.length];
contextOut.beginPath();
contextOut.moveTo(boundaryPxs[i][0][0], boundaryPxs[i][0][1]);
for(var j=1; j<boundaryPxs[i].length; ++j) {
contextOut.lineTo(boundaryPxs[i][j][0], boundaryPxs[i][j][1]);
}
contextOut.lineTo(boundaryPxs[i][0][0], boundaryPxs[i][0][1]);
contextOut.stroke();
}
contextOut.strokeStyle='black';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.