diff --git a/demo/findBreakPoints.html b/demo/findBreakPoints.html new file mode 100644 index 0000000..e52e7c4 --- /dev/null +++ b/demo/findBreakPoints.html @@ -0,0 +1,65 @@ + + + + + 查找拐点 + + + + + +
+
+ + + + + diff --git a/demo/fitImage.html b/demo/fitImage.html new file mode 100644 index 0000000..52b268e --- /dev/null +++ b/demo/fitImage.html @@ -0,0 +1,50 @@ + + + + + canvas拟合图像 + + + + + + + +
+ + 灰度阈值: +
+ + +
+ + + + + diff --git a/demo/js/canvasFindBreakPoint.js b/demo/js/canvasFindBreakPoint.js index aeb8acf..5942189 100644 --- a/demo/js/canvasFindBreakPoint.js +++ b/demo/js/canvasFindBreakPoint.js @@ -114,7 +114,7 @@ define( else if (p.inflexion) { ctx.fillStyle = 'blue'; } - ctx.fillRect(p.x, p.y, p.right == 1 ? 4 : 2, p.right == 1 ? 4 : 2); + ctx.fillRect(p.x, p.y, p.right == 1 ? 6 : 3, p.right == 1 ? 6 : 3); }); } @@ -215,7 +215,7 @@ define( refresh(); } - img.src = '../test/rw1.jpg'; + img.src = '../test/qg.gif'; } }; diff --git a/demo/js/findBreakPoints.js b/demo/js/findBreakPoints.js new file mode 100644 index 0000000..e30614c --- /dev/null +++ b/demo/js/findBreakPoints.js @@ -0,0 +1,75 @@ +/** + * @file 寻找关键点 + * @author mengke01(kekee000@gmail.com) + */ + +define( + function (require) { + var fitBezier = require('graphics/image/contour/fitBezier'); + var findBreakPoints = require('graphics/image/contour/findBreakPoints'); + var pathUtil = require('graphics/pathUtil'); + var data = require('demo/../data/image-contours3'); + var drawPath = require('render/util/drawPath'); + + var entry = { + + /** + * 初始化 + */ + init: function () { + var breakPoints = []; + var html = ''; + + data.forEach(function(c) { + c.splice(c.length - 1, 1); + }); + + + data.forEach(function (contour) { + contour.forEach(function(p) { + html += ''; + }); + }); + + $('#points').html(html); + + data.forEach(function (contour) { + contour = pathUtil.scale(contour, 10); + var points = findBreakPoints(contour, 10); + if (points) { + points.forEach(function (p) { + breakPoints.push(p); + }); + } + contour = pathUtil.scale(contour, 0.1); + }); + + breakPoints.forEach(function (p) { + console.log(p.right); + }); + + html = ''; + for (var i = 0, l = breakPoints.length; i < l; i++) { + var c = "break"; + if (breakPoints[i].tangency) { + c = 'tangency'; + } + else if(breakPoints[i].inflexion) { + c = 'inflexion'; + } + var width = ''; + if (breakPoints[i].right == 1) { + width = 'width: 4px;height: 4px'; + } + html += ''; + } + + $('#points-break').html(html); + } + }; + + entry.init(); + + return entry; + } +); diff --git a/demo/js/fitImage.js b/demo/js/fitImage.js new file mode 100644 index 0000000..ae22358 --- /dev/null +++ b/demo/js/fitImage.js @@ -0,0 +1,139 @@ +/** + * @file canvas读取图片 + * @author mengke01(kekee000@gmail.com) + */ + +define( + function (require) { + + var lang = require('common/lang'); + + var ImageProcessor = require('graphics/image/ImageProcessor'); + var ContourPointsProcessor = require('graphics/image/ContourPointsProcessor'); + var pathsUtil = require('graphics/pathsUtil'); + var editor = require('editor/main'); + + var ctx = null; + var canvas = null; + var curImage = null; + + function onUpFileChange(e) { + var file = e.target.files[0]; + var reader = new FileReader(); + reader.onload = function(e) { + + var image = curImage = new Image(); + image.onload = function () { + getContours(image); + }; + + image.src = e.target.result; + } + + reader.onerror = function(e) { + console.error(e); + }; + + reader.readAsDataURL(file); + } + + function getContours(image) { + ctx.clearRect(0,0, canvas.width, canvas.height); + var width = image.width; + var height = image.height; + canvas.width = image.width; + canvas.height = image.height; + + ctx.drawImage(image, 0, 0, width, height); + var imgData = ctx.getImageData(0, 0, width, height); + var result = new ImageProcessor(imgData).binarize(+$('#threshold-gray').val()).get(); + + var putData = imgData.data; + for (var y = 0; y < height; y ++) { + var line = width * y; + for (var x = 0; x < width; x++) { + var offset = line + x; + if (result.data[offset] === 0) { + putData[offset * 4] = 208; + putData[offset * 4 + 1] = 247; + putData[offset * 4 + 2] = 113; + putData[offset * 4 + 3] = 255; + } + else { + putData[offset * 4] = 255; + putData[offset * 4 + 1] = 255; + putData[offset * 4 + 2] = 255; + putData[offset * 4 + 3] = 255; + } + } + } + contourProcessor = new ContourPointsProcessor(); + contourProcessor.import(result); + var contours = contourProcessor.get(); + + contours.forEach(function (contour) { + contour.forEach(function (p) { + var offset = p.y * width + p.x; + putData[offset * 4] = 255; + putData[offset * 4 + 1] = 0; + putData[offset * 4 + 2] = 0; + putData[offset * 4 + 3] = 255; + }); + }); + ctx.putImageData(imgData, 0, 0); + + getBreakPoint(); + + var contours = contourProcessor.getContours(); + window.editor.setFont({ + contours: pathsUtil.flip(contours) + }); + } + + + function getBreakPoint() { + var breakPoints = contourProcessor.getBreakPoints(); + breakPoints.forEach(function (p) { + ctx.beginPath(); + if (p.breakPoint) { + ctx.fillStyle = 'red'; + } + else if (p.inflexion) { + ctx.fillStyle = 'blue'; + } + ctx.fillRect(p.x, p.y, p.right == 1 ? 6 : 3, p.right == 1 ? 6 : 3); + }); + + } + + function refresh() { + curImage && getContours(curImage); + } + + var entry = { + + /** + * 初始化 + */ + init: function () { + + window.editor = editor.create($('#render-view').get(0)); + document.getElementById('upload-file').addEventListener('change', onUpFileChange); + canvas = document.getElementById("canvas"); + ctx = canvas.getContext("2d"); + $('#threshold-gray').on('change', refresh); + + var img = new Image(); + img.onload = function () { + curImage = img; + refresh(); + }; + img.src = '../test/qg.gif'; + } + }; + + entry.init(); + + return entry; + } +); diff --git a/test/findBreakPoints.js b/test/findBreakPoints.js new file mode 100644 index 0000000..5b6c08d --- /dev/null +++ b/test/findBreakPoints.js @@ -0,0 +1,471 @@ +/** + * @file 查找轮廓中的关键点 + * @author mengke01(kekee000@gmail.com) + * + * 查找方法为此处文章的方法 + * http://wenku.baidu.com/link?url=Sz1BbYjfA7IeHAMVJ1TPlXiMhACc6sWhHliPJqAUxxtC5K9PYf4j3ile6patUEUMOE_9uJdsS1EGJBFLVbhXcn7Qe8l_FxuT3QVI8nm1aZG + * + * 关键点标记定义: + * + * p.left 左侧类型 + * p.right: 右侧类型 + * 直线为1, + * 曲线为2 + */ + + +define( + function (require) { + + var makeLink = require('graphics/pathUtil').makeLink; + var getCos = require('graphics/vector').getCos; + var getDist = require('graphics/vector').getDist; + + var THETA_TANGENCY = 0.1; // 相切抑制 + var THETA_CORNER = 0.5; // 拐点抑制 + var THETA_MARK_COUNT = 0.8; // 标记点个数抑制 + var THETA_INFLEXION_RANGE = 0.05; // 切线点之间的距离抑制 + + + function isSegmentLine(contour, start, end, isLast) { + var contourSize = contour.length; + var mid; + var thetaDistance = 10; // 判断直线点距离 + + // 判断中间点距离 + if (isLast) { + mid = contour[Math.floor(start.index + (contourSize - start.index + end.index) / 2) % contourSize]; + } + else { + mid = contour[Math.floor((start.index + end.index) / 2)]; + } + + if (getDist(start, end, mid) > thetaDistance) { + return false; + } + + // 随机选取 几个点进行直线判断 + var startIndex = start.index; + var endIndex = end.index; + if (isLast) { + startIndex = start.index; + endIndex = contourSize + end.index; + } + + // 距离比较长的话可以适当放大 + if (endIndex - startIndex > 100) { + thetaDistance = 20; + } + + var step = Math.floor(Math.max((endIndex - startIndex) / 10, 4)); + var lineFlag = true; + + for (var j = startIndex + step; j < endIndex; j += step) { + var dist = getDist(start, end, contour[j % contourSize]); + if (dist > thetaDistance) { + lineFlag = false; + break; + } + } + + return lineFlag; + } + + + /** + * 计算当前点的theta值 + * + * @param {Object} cur 当前点 + * @param {Object} leftR 左侧平均 + * @param {Object} rightR 右侧平均 + * @return {number} theta值 + */ + function computeTheta(p, leftR, rightR) { + var leftX = 0; + var leftY = 0; + var t = 0; + var cur = p; + while (t++ < leftR) { + cur = cur.prev; + leftX += cur.x; + leftY += cur.y; + } + leftX = leftX / leftR; + leftY = leftY / leftR; + + // 右侧分量 + var rightX = 0; + var rightY = 0; + t = 0; + cur = p; + while (t++ < rightR) { + cur = cur.next; + rightX += cur.x; + rightY += cur.y; + } + rightX = rightX / rightR; + rightY = rightY / rightR; + + leftX = p.x - leftX; + leftY = p.y - leftY; + rightX = rightX - p.x; + rightY = rightY - p.y; + var theta = Math.acos(getCos(leftX, leftY, rightX, rightY)); + var m = leftX * rightY - rightX * leftY; + if (m > 0) { + return theta; + } + else if (m < 0) { + return -theta; + } + + return 0; + } + + /** + * 标记点的左右已经被访问过了 + * + * @param {Object} p 点 + * @param {number} rl 左半径 + * @param {number} rr 右半径 + */ + function markVisited(p, rl, rr) { + var j; + var left = p; + var right = p; + p.visited = true; + + if (!rr || rr === rl) { + j = 0; + while (j++ < rl) { + left = left.prev; + right = right.next; + left.visited = right.visited = true; + } + } + else { + + j = 0; + while (j++ < rl) { + left = left.prev; + left.visited = true; + } + + j = 0; + while (j++ < rr) { + right = right.next; + right.visited = true; + } + } + } + + /** + * 查找和标记直线点 + * + * @param {Array} contour 轮廓 + * @param {Array} breakPoints 关键点 + * @param {number} r 查找范围 + * @return {Array} 关键点 + */ + function findLinePoints(contour, breakPoints, r) { + + // 根据角点查找竖直和水平线 + var linePoints = []; + + var contourSize = contour.length; + for (var i = 0, l = breakPoints.length; i < l; i++) { + var isLast = i === l - 1; + var p = breakPoints[i]; + var next = isLast ? breakPoints[0] : breakPoints[i + 1]; + + if (p.right === 1) { + continue; + } + + var range = isLast ? contourSize - p.index + next.index : next.index - p.index; + if (range < r) { + p.right = 1; + next.left = 1; + } + else if (isSegmentLine(contour, p, next, isLast)){ + p.right = 1; + next.left = 1; + } + } + + return breakPoints; + } + + + + + + /** + * 查找竖直和水平直线点 + * + * @param {Array} contour 轮廓 + * @param {Array} breakPoints 关键点 + * @param {number} r 查找范围 + * @return {Array} 关键点 + */ + function findVerticalAndHorizontalPoints(contour, breakPoints, r) { + + var contourSize = contour.length; + for (var i = 0, l = contourSize; i < l; i++) { + var p = contour[i]; + var cur; + var count; + + // 查找竖直直线点 + if (p.x === p.next.x && p.x === p.next.next.x) { + cur = p.next.next; + count = 2; + + while (cur.x === cur.next.x) { + cur = cur.next; + count++; + } + + if (count > 40) { + + cur = cur.prev; + //p.right = 1; + if (-1 == breakPoints.indexOf(p)) { + breakPoints.push(p); + } + + markVisited(p, r, count + r); + // 判断是否角点 + p.theta = computeTheta(p, r, 1); + p.absTheta = Math.abs(p.theta); + + if (p.absTheta > THETA_CORNER) { + p.breakPoint = true; + } + else if (p.absTheta <= THETA_TANGENCY) { + p.tangency = true; + } + + if (count > 80) { + //cur.left = 1; + if (-1 == breakPoints.indexOf(cur)) { + breakPoints.push(cur); + } + + cur.theta = computeTheta(cur, 1, r); + cur.absTheta = Math.abs(cur.theta); + + if (cur.absTheta > THETA_CORNER) { + cur.breakPoint = true; + } + else if (cur.absTheta < THETA_TANGENCY) { + cur.tangency = true; + } + } + i += count; + } + + } + else if (p.y === p.next.y && p.y === p.next.next.y) { + cur = p.next.next; + count = 2; + + while (cur.y === cur.next.y) { + cur = cur.next; + count++; + } + + if (count > 40) { + + cur = cur.prev; + + //p.right = 1; + if (-1 == breakPoints.indexOf(p)) { + breakPoints.push(p); + } + //cur.left = 1; + if (-1 == breakPoints.indexOf(cur)) { + breakPoints.push(cur); + } + + markVisited(p, r, count + r); + // 判断是否角点 + p.theta = computeTheta(p, r, 1); + p.absTheta = Math.abs(p.theta); + + if (p.absTheta > THETA_CORNER) { + p.breakPoint = true; + } + else if (p.absTheta > THETA_TANGENCY) { + p.tangency = true; + } + + cur.theta = computeTheta(cur, 1, r); + cur.absTheta = Math.abs(cur.theta); + + if (cur.absTheta > THETA_CORNER) { + cur.breakPoint = true; + } + else if (cur.absTheta < THETA_TANGENCY) { + cur.tangency = true; + } + + i += count; + } + } + + } + + breakPoints.sort(function (a, b) { + return a.index - b.index; + }); + + return breakPoints; + } + + + + /** + * 查找和标记拐点 + * + * @param {Array} contour 轮廓 + * @param {Array} breakPoints 关键点 + * @param {number} r 查找范围 + * @return {Array} 关键点 + */ + function findBreakPoints(contour, breakPoints, r) { + + var j; + var left; + var right; + var max; + contour.forEach(function (p) { + if (p.mark && !p.visited) { + j = 0; + left = p; + right = p; + + max = p.absTheta; + while (j++ < r) { + left = left.prev; + right = right.next; + + if (left.absTheta > max) { + max = left.absTheta; + } + + if (right.absTheta > max) { + max = right.absTheta; + } + } + + if (max === p.absTheta) { + p.breakPoint = true; + breakPoints.push(p); + markVisited(p, r); + } + } + }); + + return breakPoints; + } + + function fixStartPoint(contour) { + + var i; + var l; + + var minY = contour[0].y; + var firstLine = [0]; + for (i = 1, l = contour.length; i < l; i++) { + if (contour[i].y < minY) { + minY = contour[i].y; + firstLine = [i]; + } + else if (contour[i].y === minY) { + firstLine.push(i); + } + } + + var index = firstLine[0]; + var minX = contour[index].x; + for (i = 1, l = firstLine.length; i < l; i++) { + if (contour[firstLine[i]].x < minX) { + minX = contour[firstLine[i]].x; + index = firstLine[i]; + } + } + + if (index !== 0) { + return contour.slice(index).concat(contour.slice(0, index)); + } + + return contour; + } + + + /** + * 查找轮廓中的关键点 + * + * @param {Array} contour 轮廓点集合 + * @return {Array} 轮廓点集合 + */ + function getBreakPoints(contour) { + + //contour = fixStartPoint(contour); + + contour = makeLink(contour); + + var r = contour.length > 16 ? 10 : 4; + + var contourSize = contour.length; + var breakPoints = []; + + if (contour.length > 60) { + breakPoints = findVerticalAndHorizontalPoints(contour, breakPoints, r); + } + + + for (var i = 0, l = contourSize; i < l; i++) { + p = contour[i]; + if (!p.visited) { + p.theta = computeTheta(p, r, r); + p.absTheta = Math.abs(p.theta); + + if (p.absTheta > THETA_CORNER) { + p.mark = true; + } + else if (p.absTheta > THETA_TANGENCY) { + p.markTangency = true; + } + } + } + + // 非极大抑制查找角点 + breakPoints = findBreakPoints(contour, breakPoints, r); + + // 没有找到拐点,说明拐点不明显,或者为连续曲线,这里采用4等分 + if (breakPoints.length < 2) { + + if (contourSize < 100) { + var step = Math.floor(contourSize / 2) + 1; + } + else { + var step = Math.floor(contourSize / 4) + 1; + } + var startIndex = breakPoints[0] ? breakPoints[0].index : 0; + for (var i = 0; i < contourSize; i += step) { + breakPoints.push(contour[(startIndex + i) % contourSize]); + } + } + + breakPoints.sort(function (a, b) { + return a.index - b.index; + }); + + return breakPoints; + } + + return getBreakPoints; + } +); diff --git a/test/qg.jpg b/test/qg.jpg new file mode 100644 index 0000000..089bdfc Binary files /dev/null and b/test/qg.jpg differ