modify contours fitting
This commit is contained in:
parent
1aa3f9ccfb
commit
d7d93aba8a
6
demo/data/image-contours13.js
Normal file
6
demo/data/image-contours13.js
Normal file
File diff suppressed because one or more lines are too long
6
demo/data/image-contours14.js
Normal file
6
demo/data/image-contours14.js
Normal file
File diff suppressed because one or more lines are too long
6
demo/data/image-contours15.js
Normal file
6
demo/data/image-contours15.js
Normal file
File diff suppressed because one or more lines are too long
@ -5,10 +5,10 @@
|
||||
|
||||
define(
|
||||
function (require) {
|
||||
var reducePoints = require('graphics/image/contour/douglasPeuckerReducePoints');
|
||||
var reducePoints = require('graphics/image/contour/reducePoints');
|
||||
var findBreakPoints = require('graphics/image/contour/findBreakPoints');
|
||||
var pathUtil = require('graphics/pathUtil');
|
||||
var data = require('demo/../data/image-contours10');
|
||||
var data = require('demo/../data/image-contours2');
|
||||
|
||||
|
||||
var entry = {
|
||||
@ -61,7 +61,7 @@ define(
|
||||
if (p.right == 1) {
|
||||
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);
|
||||
|
@ -7,7 +7,7 @@ define(
|
||||
function (require) {
|
||||
|
||||
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 pathUtil = require('graphics/pathUtil');
|
||||
|
||||
|
@ -13,7 +13,7 @@ define(
|
||||
|
||||
function reduce(contour, firstIndex, lastIndex, tolerance, splitArray) {
|
||||
|
||||
if (lastIndex - firstIndex <= 3) {
|
||||
if (lastIndex - firstIndex < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -55,12 +55,17 @@ define(
|
||||
|
||||
if (splitArray.length) {
|
||||
splitArray.unshift(firstIndex);
|
||||
return splitArray.map(function (index) {
|
||||
splitArray = splitArray.map(function (index) {
|
||||
contour[index].index = index;
|
||||
return contour[index];
|
||||
}).sort(function (a, b) {
|
||||
})
|
||||
|
||||
// 去除临近的点
|
||||
|
||||
splitArray.sort(function (a, b) {
|
||||
return a.index - b.index;
|
||||
});
|
||||
return splitArray;
|
||||
}
|
||||
|
||||
return contour;
|
||||
|
@ -11,7 +11,7 @@ define(
|
||||
var vector = require('graphics/vector');
|
||||
var getCos = vector.getCos;
|
||||
var getDist = vector.getDist;
|
||||
var THETA_CORNER = 0.8; // 拐点抑制
|
||||
var THETA_CORNER = 1.0; // 拐点抑制
|
||||
|
||||
function dist(p0, p1) {
|
||||
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);
|
||||
|
||||
var start = contour[0];
|
||||
var cur = start;
|
||||
var longDist = 80 * scale;
|
||||
var longDist = 30 * scale;
|
||||
var shortDist = 20 * scale;
|
||||
var lineDist = 10 * scale;
|
||||
var tinyDist = 2 * scale;
|
||||
var tinyDist = 3 * scale;
|
||||
|
||||
var cur = start;
|
||||
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) {
|
||||
cur.ntiny = 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) ;
|
||||
|
||||
if (cur.theta > THETA_CORNER) {
|
||||
cur.breakPoint = true;
|
||||
cur.corner = true;
|
||||
}
|
||||
|
||||
// 判断水平和竖直线
|
||||
if (
|
||||
(Math.abs(cur.next.x - cur.x) < scale)
|
||||
&& Math.abs(cur.next.y - cur.y) > lineDist
|
||||
) {
|
||||
cur.next.vertical = true;
|
||||
cur.vertical = true;
|
||||
cur.right = 1;
|
||||
}
|
||||
else if (
|
||||
(Math.abs(cur.next.y - cur.y) < scale)
|
||||
&& Math.abs(cur.next.x - cur.x) > lineDist
|
||||
) {
|
||||
cur.next.hoz = true;
|
||||
cur.hoz = true;
|
||||
cur.right = 1;
|
||||
}
|
||||
// 判断水平和竖直线
|
||||
if (
|
||||
(Math.abs(cur.next.x - cur.x) < scale)
|
||||
&& Math.abs(cur.next.y - cur.y) > lineDist
|
||||
) {
|
||||
cur.next.vertical = true;
|
||||
cur.vertical = true;
|
||||
}
|
||||
else if (
|
||||
(Math.abs(cur.next.y - cur.y) < scale)
|
||||
&& Math.abs(cur.next.x - cur.x) > lineDist
|
||||
) {
|
||||
cur.next.hoz = true;
|
||||
cur.hoz = true;
|
||||
}
|
||||
|
||||
|
||||
// 判断顶角
|
||||
if(cur.x <= cur.prev.x && cur.x <= cur.next.x
|
||||
|| cur.y <= cur.prev.y && cur.y <= cur.next.y
|
||||
) {
|
||||
cur.apexTop = true;
|
||||
if(cur.x <= cur.prev.x && cur.x <= cur.next.x) {
|
||||
cur.xTop = true;
|
||||
cur.apex = true;
|
||||
}
|
||||
|
||||
if (cur.x >= cur.prev.x && cur.x >= cur.next.x
|
||||
|| cur.y >= cur.prev.y && cur.y >= cur.next.y
|
||||
) {
|
||||
cur.apexBottom = true;
|
||||
if(cur.y <= cur.prev.y && cur.y <= cur.next.y) {
|
||||
cur.yTop = true;
|
||||
cur.apex = true;
|
||||
}
|
||||
|
||||
// 标记上一个点和下一个点的距离
|
||||
if (Math.abs(cur.x - cur.next.x) > longDist) {
|
||||
cur.nxl = true;
|
||||
cur.next.pxl = true;
|
||||
if (cur.x >= cur.prev.x && cur.x >= cur.next.x) {
|
||||
cur.xBottom = true;
|
||||
cur.apex = true;
|
||||
}
|
||||
|
||||
if (Math.abs(cur.y - cur.next.y) > longDist) {
|
||||
cur.nyl = true;
|
||||
cur.next.pyl = 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;
|
||||
if (cur.y >= cur.prev.y && cur.y >= cur.next.y) {
|
||||
cur.yBottom = true;
|
||||
cur.apex = true;
|
||||
}
|
||||
|
||||
cur = cur.next;
|
||||
} while (cur !== start);
|
||||
|
||||
cur = start;
|
||||
// 判断角点和切线点
|
||||
do {
|
||||
|
||||
if (cur.visited) {
|
||||
cur = cur.next;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 修正直角连接点的x,y坐标
|
||||
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.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;
|
||||
}
|
||||
|
||||
// 移除非真正的breakPoint
|
||||
if (cur.breakPoint && !cur.ptiny && !cur.ntiny && (cur.nxs && cur.pxs)) {
|
||||
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.breakPoint = true;
|
||||
cur.next.visited = true;
|
||||
cur.next.tangency = true;
|
||||
cur.next.breakPoint = true;
|
||||
}
|
||||
|
||||
cur = cur.next;
|
||||
} while (cur !== start);
|
||||
|
||||
var breakPoints = contour.filter(function (p) {
|
||||
return p.breakPoint;
|
||||
});
|
||||
|
||||
breakPoints.sort(function (a, b) {
|
||||
return a.index - b.index;
|
||||
});
|
||||
|
@ -21,7 +21,7 @@ define(
|
||||
function fitBezier(points, scale, tHat1, tHat2) {
|
||||
scale = scale || 1;
|
||||
var size = points.length;
|
||||
var maxError = 10 * scale * scale;
|
||||
var maxError = 4 * scale * scale;
|
||||
|
||||
var cubicBezier = fitCurve(points, maxError, tHat1, tHat2);
|
||||
var start = points[0];
|
||||
|
@ -7,7 +7,7 @@
|
||||
define(
|
||||
function (require) {
|
||||
|
||||
var reducePoints = require('graphics/image/contour/douglasPeuckerReducePoints');
|
||||
var reducePoints = require('graphics/image/contour/reducePoints');
|
||||
var findBreakPoints = require('./findBreakPoints');
|
||||
var fitBezier = require('./fitBezier');
|
||||
var pathUtil = require('graphics/pathUtil');
|
||||
@ -49,13 +49,11 @@ define(
|
||||
curvePoints = reducedData.slice(start.index, end.index + 1);
|
||||
}
|
||||
|
||||
|
||||
if (curvePoints.length <= 2) {
|
||||
tHat1Point = end;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((start.inflexion || start.tangency) && tHat1Point) {
|
||||
if (start.tangency && tHat1Point && start !== tHat1Point) {
|
||||
tHat1 = vector.normalize({
|
||||
x: start.x - tHat1Point.x,
|
||||
y: start.y - tHat1Point.y
|
||||
@ -65,7 +63,7 @@ define(
|
||||
tHat1 = null;
|
||||
}
|
||||
|
||||
var bezierCurve = fitBezier(curvePoints, scale, tHat1);
|
||||
var bezierCurve = fitBezier(curvePoints, scale);
|
||||
if (bezierCurve.length) {
|
||||
bezierCurve.forEach(function (p) {
|
||||
if (!isNaN(p.x) && !isNaN(p.y)) {
|
||||
@ -75,6 +73,9 @@ define(
|
||||
onCurve: p.onCurve
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.log('nan');
|
||||
}
|
||||
});
|
||||
|
||||
end = bezierCurve[bezierCurve.length - 2];
|
||||
@ -98,19 +99,19 @@ define(
|
||||
|
||||
// 去除直线
|
||||
if (resultContour.length <= 2) {
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
// 去除拟合后变成了直线轮廓
|
||||
else if (
|
||||
resultContour.length <= 4
|
||||
&& vector.getDist(resultContour[0], resultContour[1], resultContour[2]) < scale
|
||||
) {
|
||||
return null;
|
||||
return [];
|
||||
}
|
||||
|
||||
return pathUtil.deInterpolate(reducePath(resultContour).map(function (p) {
|
||||
p.x = Math.floor(p.x);
|
||||
p.y = Math.floor(p.y);
|
||||
p.x = Math.round(p.x);
|
||||
p.y = Math.round(p.y);
|
||||
return p;
|
||||
}));
|
||||
|
||||
|
97
src/graphics/image/contour/reducePoints.js
Normal file
97
src/graphics/image/contour/reducePoints.js
Normal 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;
|
||||
}
|
||||
);
|
Loading…
x
Reference in New Issue
Block a user