modify contours fitting

This commit is contained in:
kekee000 2015-04-06 13:40:03 +08:00
parent 1aa3f9ccfb
commit d7d93aba8a
10 changed files with 240 additions and 98 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,10 +5,10 @@
define( define(
function (require) { function (require) {
var reducePoints = require('graphics/image/contour/douglasPeuckerReducePoints'); var reducePoints = require('graphics/image/contour/reducePoints');
var findBreakPoints = require('graphics/image/contour/findBreakPoints'); var findBreakPoints = require('graphics/image/contour/findBreakPoints');
var pathUtil = require('graphics/pathUtil'); var pathUtil = require('graphics/pathUtil');
var data = require('demo/../data/image-contours10'); var data = require('demo/../data/image-contours2');
var entry = { var entry = {
@ -61,7 +61,7 @@ define(
if (p.right == 1) { if (p.right == 1) {
width = 'width: 4px;height: 4px'; width = 'width: 4px;height: 4px';
} }
html += '<i data-index="'+ p.index +'" '+ (p.tangency ? 'data-tangency="1"' : '') + (p.visited ? 'data-visited="1"' : '') +' title="'+ p.theta +'" style="left:'+p.x+'px;top:'+p.y+'px;'+width+'" class="'+c+'"></i>'; html += '<i data-index="'+ p.index +'" '+ (p.ntiny || p.ptiny ? 'data-tiny="1"' : '') + (p.tangency ? 'data-tangency="1"' : '') + (p.visited ? 'data-visited="1"' : '') +' title="'+ p.theta +'" style="left:'+p.x+'px;top:'+p.y+'px;'+width+'" class="'+c+'"></i>';
} }
$('#points-break').html(html); $('#points-break').html(html);

View File

@ -7,7 +7,7 @@ define(
function (require) { function (require) {
var fitContour = require('graphics/image/contour/fitContour'); var fitContour = require('graphics/image/contour/fitContour');
var data = require('demo/../data/image-contours10'); var data = require('demo/../data/image-contours5');
var drawPath = require('render/util/drawPath'); var drawPath = require('render/util/drawPath');
var pathUtil = require('graphics/pathUtil'); var pathUtil = require('graphics/pathUtil');

View File

@ -13,7 +13,7 @@ define(
function reduce(contour, firstIndex, lastIndex, tolerance, splitArray) { function reduce(contour, firstIndex, lastIndex, tolerance, splitArray) {
if (lastIndex - firstIndex <= 3) { if (lastIndex - firstIndex < 3) {
return; return;
} }
@ -55,12 +55,17 @@ define(
if (splitArray.length) { if (splitArray.length) {
splitArray.unshift(firstIndex); splitArray.unshift(firstIndex);
return splitArray.map(function (index) { splitArray = splitArray.map(function (index) {
contour[index].index = index; contour[index].index = index;
return contour[index]; return contour[index];
}).sort(function (a, b) { })
// 去除临近的点
splitArray.sort(function (a, b) {
return a.index - b.index; return a.index - b.index;
}); });
return splitArray;
} }
return contour; return contour;

View File

@ -11,7 +11,7 @@ define(
var vector = require('graphics/vector'); var vector = require('graphics/vector');
var getCos = vector.getCos; var getCos = vector.getCos;
var getDist = vector.getDist; var getDist = vector.getDist;
var THETA_CORNER = 0.8; // 拐点抑制 var THETA_CORNER = 1.0; // 拐点抑制
function dist(p0, p1) { function dist(p0, p1) {
return Math.sqrt(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2)); return Math.sqrt(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2));
@ -27,132 +27,153 @@ define(
contour = makeLink(contour); contour = makeLink(contour);
var start = contour[0]; var start = contour[0];
var cur = start; var longDist = 30 * scale;
var longDist = 80 * scale;
var shortDist = 20 * scale; var shortDist = 20 * scale;
var lineDist = 10 * scale; var lineDist = 10 * scale;
var tinyDist = 2 * scale; var tinyDist = 3 * scale;
var cur = start;
do { do {
cur.ndist = cur.next.pdist = dist(cur, cur.next);
// 距离比较近的点判断切角会不准确,需要特殊处理 // 距离比较近的点判断切角会不准确,需要特殊处理
if (Math.abs(cur.x - cur.next.x) <= tinyDist && Math.abs(cur.y - cur.next.y) <= tinyDist) { if (Math.abs(cur.x - cur.next.x) <= tinyDist && Math.abs(cur.y - cur.next.y) <= tinyDist) {
cur.ntiny = true; cur.ntiny = true;
cur.next.ptiny = true; cur.next.ptiny = true;
} }
var cos = getCos(
cur.ptiny ? cur.prev.prev : cur.prev,
cur,
cur.ntiny ? cur.next.next : cur.next
);
var cos = getCos(cur.ptiny ? cur.prev.prev : cur.prev, cur, cur.ntiny ? cur.next.next : cur.next);
cur.theta = Math.acos(cos > 1 ? 1 : cos) ; cur.theta = Math.acos(cos > 1 ? 1 : cos) ;
if (cur.theta > THETA_CORNER) { if (cur.theta > THETA_CORNER) {
cur.breakPoint = true; cur.corner = true;
}
// 判断水平和竖直线 // 判断水平和竖直线
if ( if (
(Math.abs(cur.next.x - cur.x) < scale) (Math.abs(cur.next.x - cur.x) < scale)
&& Math.abs(cur.next.y - cur.y) > lineDist && Math.abs(cur.next.y - cur.y) > lineDist
) { ) {
cur.next.vertical = true; cur.next.vertical = true;
cur.vertical = true; cur.vertical = true;
cur.right = 1; }
} else if (
else if ( (Math.abs(cur.next.y - cur.y) < scale)
(Math.abs(cur.next.y - cur.y) < scale) && Math.abs(cur.next.x - cur.x) > lineDist
&& Math.abs(cur.next.x - cur.x) > lineDist ) {
) { cur.next.hoz = true;
cur.next.hoz = true; cur.hoz = true;
cur.hoz = true;
cur.right = 1;
}
} }
// 判断顶角 // 判断顶角
if(cur.x <= cur.prev.x && cur.x <= cur.next.x if(cur.x <= cur.prev.x && cur.x <= cur.next.x) {
|| cur.y <= cur.prev.y && cur.y <= cur.next.y cur.xTop = true;
) {
cur.apexTop = true;
cur.apex = true; cur.apex = true;
} }
if (cur.x >= cur.prev.x && cur.x >= cur.next.x if(cur.y <= cur.prev.y && cur.y <= cur.next.y) {
|| cur.y >= cur.prev.y && cur.y >= cur.next.y cur.yTop = true;
) {
cur.apexBottom = true;
cur.apex = true; cur.apex = true;
} }
// 标记上一个点和下一个点的距离 if (cur.x >= cur.prev.x && cur.x >= cur.next.x) {
if (Math.abs(cur.x - cur.next.x) > longDist) { cur.xBottom = true;
cur.nxl = true; cur.apex = true;
cur.next.pxl = true;
} }
if (Math.abs(cur.y - cur.next.y) > longDist) { if (cur.y >= cur.prev.y && cur.y >= cur.next.y) {
cur.nyl = true; cur.yBottom = true;
cur.next.pyl = true; cur.apex = true;
}
if (Math.abs(cur.x - cur.next.x) < shortDist) {
cur.nxs = true;
cur.next.pxs = true;
}
if (Math.abs(cur.y - cur.next.y) < shortDist) {
cur.nys = true;
cur.next.pys = true;
} }
cur = cur.next; cur = cur.next;
} while (cur !== start); } while (cur !== start);
cur = start; cur = start;
// 判断角点和切线点
do { do {
if (cur.visited) {
cur = cur.next;
continue;
}
// 修正直角连接点的xy坐标 // 修正直角连接点的xy坐标
if (cur.breakPoint && cur.hoz && cur.vertical) { if (cur.corner && cur.hoz && cur.vertical) {
cur.x = (Math.abs(cur.prev.x - cur.x) <= scale) ? cur.prev.x : cur.next.x; cur.x = (Math.abs(cur.prev.x - cur.x) <= scale) ? cur.prev.x : cur.next.x;
cur.y = (Math.abs(cur.prev.y - cur.y) <= scale) ? cur.prev.y : cur.next.y; cur.y = (Math.abs(cur.prev.y - cur.y) <= scale) ? cur.prev.y : cur.next.y;
cur.rightAngle = true; cur.prev.right = 1;
cur.right = 1;
cur.visited = true;
cur.breakPoint = true;
}
// 判断单独的角点
else if (cur.corner) {
cur.visited = true;
cur.breakPoint = true;
}
// 判断单独的顶角切线点
else if (cur.apex && !cur.prev.apex && !cur.next.apex) {
if (cur.theta < 0.3) {
cur.tangency = true;
}
cur.visited = true;
cur.breakPoint = true;
} }
// 判断切线 // 判断边界点中的切线点
if(cur.theta < 0.1 && (cur.pxl || cur.pyl || cur.nxl || cur.nyl)) { // else if (cur.theta < 0.3 && (cur.ndist > longDist || cur.pdist > longDist)) {
// cur.visited = true;
// cur.tangency = true;
// cur.breakPoint = true;
// }
// 判断边界点中的切线点
else if (cur.apex && (cur.ndist > longDist || cur.pdist > longDist)) {
cur.visited = true;
cur.breakPoint = true;
}
// 直线段
else if (cur.theta > 0.8 && (cur.ndist > longDist || cur.pdist > longDist)) {
cur.visited = true;
cur.breakPoint = true;
}
// 判断成对的顶角切线点
else if (cur.apex && cur.next.apex && cur.prev.theta < 0.4 && cur.next.theta < 0.4) {
// 修正切线点位置
if (cur.xTop && cur.next.xTop || cur.xBottom && cur.next.xBottom) {
var minus = Math.max(Math.floor(Math.abs(cur.next.y - cur.y) / 4), 4 * scale);
cur.y = cur.y > cur.next.y ? cur.y - minus : cur.y + minus;
cur.next.y = cur.next.y > cur.y ? cur.next.y - minus : cur.next.y + minus;
}
if (cur.yTop && cur.next.yTop || cur.yBottom && cur.next.yBottom) {
var minus = Math.max(Math.floor(Math.abs(cur.next.x - cur.x) / 4), 4 * scale);
cur.x = cur.x > cur.next.x ? cur.x - minus : cur.x + minus;
cur.next.x = cur.next.x > cur.x ? cur.next.x - minus : cur.next.x + minus;
}
cur.visited = true;
cur.tangency = true; cur.tangency = true;
} cur.breakPoint = true;
cur.next.visited = true;
// 移除非真正的breakPoint cur.next.tangency = true;
if (cur.breakPoint && !cur.ptiny && !cur.ntiny && (cur.nxs && cur.pxs)) { cur.next.breakPoint = true;
delete cur.breakPoint;
}
cur = cur.next;
} while (cur !== start);
var breakPoints = [];
cur = start;
do {
if (cur.rightAngle) {
breakPoints.push(cur);
}
else if (cur.right === 1) {
breakPoints.push(cur);
}
else if (cur.ntiny || cur.rtiny) {
breakPoints.push(cur);
}
else if (cur.apex) {
breakPoints.push(cur);
}
else if (cur.pxl && cur.next.pxs || cur.pyl && cur.next.pys) {
breakPoints.push(cur);
} }
cur = cur.next; cur = cur.next;
} while (cur !== start); } while (cur !== start);
var breakPoints = contour.filter(function (p) {
return p.breakPoint;
});
breakPoints.sort(function (a, b) { breakPoints.sort(function (a, b) {
return a.index - b.index; return a.index - b.index;
}); });

View File

@ -21,7 +21,7 @@ define(
function fitBezier(points, scale, tHat1, tHat2) { function fitBezier(points, scale, tHat1, tHat2) {
scale = scale || 1; scale = scale || 1;
var size = points.length; var size = points.length;
var maxError = 10 * scale * scale; var maxError = 4 * scale * scale;
var cubicBezier = fitCurve(points, maxError, tHat1, tHat2); var cubicBezier = fitCurve(points, maxError, tHat1, tHat2);
var start = points[0]; var start = points[0];

View File

@ -7,7 +7,7 @@
define( define(
function (require) { function (require) {
var reducePoints = require('graphics/image/contour/douglasPeuckerReducePoints'); var reducePoints = require('graphics/image/contour/reducePoints');
var findBreakPoints = require('./findBreakPoints'); var findBreakPoints = require('./findBreakPoints');
var fitBezier = require('./fitBezier'); var fitBezier = require('./fitBezier');
var pathUtil = require('graphics/pathUtil'); var pathUtil = require('graphics/pathUtil');
@ -49,13 +49,11 @@ define(
curvePoints = reducedData.slice(start.index, end.index + 1); curvePoints = reducedData.slice(start.index, end.index + 1);
} }
if (curvePoints.length <= 2) { if (curvePoints.length <= 2) {
tHat1Point = end;
continue; continue;
} }
if ((start.inflexion || start.tangency) && tHat1Point) { if (start.tangency && tHat1Point && start !== tHat1Point) {
tHat1 = vector.normalize({ tHat1 = vector.normalize({
x: start.x - tHat1Point.x, x: start.x - tHat1Point.x,
y: start.y - tHat1Point.y y: start.y - tHat1Point.y
@ -65,7 +63,7 @@ define(
tHat1 = null; tHat1 = null;
} }
var bezierCurve = fitBezier(curvePoints, scale, tHat1); var bezierCurve = fitBezier(curvePoints, scale);
if (bezierCurve.length) { if (bezierCurve.length) {
bezierCurve.forEach(function (p) { bezierCurve.forEach(function (p) {
if (!isNaN(p.x) && !isNaN(p.y)) { if (!isNaN(p.x) && !isNaN(p.y)) {
@ -75,6 +73,9 @@ define(
onCurve: p.onCurve onCurve: p.onCurve
}); });
} }
else {
console.log('nan');
}
}); });
end = bezierCurve[bezierCurve.length - 2]; end = bezierCurve[bezierCurve.length - 2];
@ -98,19 +99,19 @@ define(
// 去除直线 // 去除直线
if (resultContour.length <= 2) { if (resultContour.length <= 2) {
return null; return [];
} }
// 去除拟合后变成了直线轮廓 // 去除拟合后变成了直线轮廓
else if ( else if (
resultContour.length <= 4 resultContour.length <= 4
&& vector.getDist(resultContour[0], resultContour[1], resultContour[2]) < scale && vector.getDist(resultContour[0], resultContour[1], resultContour[2]) < scale
) { ) {
return null; return [];
} }
return pathUtil.deInterpolate(reducePath(resultContour).map(function (p) { return pathUtil.deInterpolate(reducePath(resultContour).map(function (p) {
p.x = Math.floor(p.x); p.x = Math.round(p.x);
p.y = Math.floor(p.y); p.y = Math.round(p.y);
return p; return p;
})); }));

View File

@ -0,0 +1,97 @@
/**
* @file 消减点
* @author mengke01(kekee000@gmail.com)
*/
define(
function (require) {
var douglasPeuckerReducePoints = require('graphics/image/contour/douglasPeuckerReducePoints');
var makeLink = require('graphics/pathUtil').makeLink;
var vector = require('graphics/vector');
var getCos = vector.getCos;
var getDist = vector.getDist;
function dist(p0, p1) {
return Math.sqrt(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2));
}
/**
* 消减非必要的点
*
* @param {Array} contour 轮廓点集
* @param {number} firstIndex 起始索引
* @param {number} lastIndex 结束索引
* @return {Array} 消减后的点集
*/
function reducePoints(contour, firstIndex, lastIndex, scale, tolerance) {
var points = douglasPeuckerReducePoints(contour, firstIndex, lastIndex, scale, tolerance);
points = makeLink(points);
var start = points[0];
var tinyDist = 3 * scale;
var shortDistance = 10 * scale;
var longDistance = 40 * scale;
var rightAngle = Math.PI / 2;
var cur = start;
do {
cur.ndist = cur.next.pdist = dist(cur, cur.next);
cur = cur.next;
} while (cur !== start);
cur = start;
do {
if (cur.visited) {
cur = cur.next;
continue;
}
if (Math.abs(cur.x - cur.next.x) <= tinyDist && Math.abs(cur.y - cur.next.y) <= tinyDist) {
var theta = Math.acos(getCos(cur.x - cur.prev.x, cur.y - cur.prev.y, cur.next.next.x - cur.next.x, cur.next.next.y - cur.next.y)) || 1;
// 小于直角则考虑移除点
if (theta < rightAngle) {
// 顶角
if (
cur.x >= cur.prev.x && cur.x >= cur.next.x
|| cur.x <= cur.prev.x && cur.x <= cur.next.x
|| cur.y >= cur.prev.y && cur.y >= cur.next.y
|| cur.y <= cur.prev.y && cur.y <= cur.next.y
) {
cur.next.deleted = true;
}
else {
cur.deleted = true;
}
}
cur.visited = cur.next.visited = true;
}
// 一个点距离比较近一个非常远
else if (
cur.ndist > longDistance && cur.pdist < shortDistance
|| cur.pdist > longDistance && cur.ndist < shortDistance
&& getCos(cur.prev, cur, cur.next) > 0.9
) {
cur.deleted = true;
cur.visited = cur.next.visited = true;
}
cur = cur.next;
} while (cur !== start);
return points.filter(function (p) {
delete p.visited;
return !p.deleted;
});
}
return reducePoints;
}
);