我正在寻找关于识别三种手写形状的一些建议 - 圆形,菱形和矩形。我试过不同的aproaches,但他们失败了所以也许你可以指出我在另一个更好的方向。
我尝试了什么:
1)基于手写形状和理想形状点之间的点积的简单算法。它在识别矩形方面并不是那么糟糕,但在圆形和钻石上失败了。问题是圆形和菱形的点积非常类似于理想的形状。
2)同样的方法,但使用动态时间扭曲作为相似性的度量。类似的问题。
3)神经网络。我尝试了一些方法 - 给神经网络(Feedforward和Kohonen)提供点数据或给出光栅化图像。对于Kohonen来说,它始终将所有数据(用于训练的样本)分类到同一类别。带点的前馈更好(但与aproach 1和2处于同一水平)并且光栅化图像非常慢(我需要至少大小^ 2输入神经元,对于小尺寸的光栅圆,即使对我来说也难以区分;))也没有成功。我想是因为所有这些形状都是封闭的数字?我不是ANN的大专家(有一个学期的课程)所以也许我使用它们错了?
4)将形状保存为Freeman Chain Code并使用一些算法来计算相似度。我认为在FCC中,形状将彼此不同。这里没有成功(但我没有深入探索这条道路)。
我正在为Android构建应用程序,但我认为这里的语言无关紧要。
答案 0 :(得分:4)
这是形状分类器的一些工作代码。 http://jsfiddle.net/R3ns3/我将阈值数字(代码中的阈值变量)拉出以太,因此当然可以调整它们以获得更好的结果。
我使用边界框,子部分中的平均点,点之间的角度,边界框中心的极角和角点识别。它可以对手绘的矩形,菱形和圆形进行分类。鼠标按钮关闭时代码记录点,并在您停止绘图时尝试分类。
HTML
<canvas id="draw" width="300" height="300" style="position:absolute; top:0px; left:0p; margin:0; padding:0; width:300px; height:300px; border:2px solid blue;"></canvas>
JS
var state = {
width: 300,
height: 300,
pointRadius: 2,
cornerThreshold: 125,
circleThreshold: 145,
rectangleThreshold: 45,
diamondThreshold: 135,
canvas: document.getElementById("draw"),
ctx: document.getElementById("draw").getContext("2d"),
drawing: false,
points: [],
getCorners: function(angles, pts) {
var list = pts || this.points;
var corners = [];
for(var i=0; i<angles.length; i++) {
if(angles[i] <= this.cornerThreshold) {
corners.push(list[(i + 1) % list.length]);
}
}
return corners;
},
draw: function(color, pts) {
var list = pts||this.points;
this.ctx.fillStyle = color;
for(var i=0; i<list.length; i++) {
this.ctx.beginPath();
this.ctx.arc(list[i].x, list[i].y, this.pointRadius, 0, Math.PI * 2, false);
this.ctx.fill();
}
},
classify: function() {
// get bounding box
var left = this.width, right = 0,
top = this.height, bottom = 0;
for(var i=0; i<this.points.length; i++) {
var pt = this.points[i];
if(left > pt.x) left = pt.x;
if(right < pt.x) right = pt.x;
if(top > pt.y) top = pt.y;
if(bottom < pt.y) bottom = pt.y;
}
var center = {x: (left+right)/2, y: (top+bottom)/2};
this.draw("#00f", [
{x: left, y: top},
{x: right, y: top},
{x: left, y: bottom},
{x: right, y: bottom},
]);
// find average point in each sector (9 sectors)
var sects = [
{x:0,y:0,c:0},{x:0,y:0,c:0},{x:0,y:0,c:0},
{x:0,y:0,c:0},{x:0,y:0,c:0},{x:0,y:0,c:0},
{x:0,y:0,c:0},{x:0,y:0,c:0},{x:0,y:0,c:0}
];
var x3 = (right + (1/(right-left)) - left) / 3;
var y3 = (bottom + (1/(bottom-top)) - top) / 3;
for(var i=0; i<this.points.length; i++) {
var pt = this.points[i];
var sx = Math.floor((pt.x - left) / x3);
var sy = Math.floor((pt.y - top) / y3);
var idx = sy * 3 + sx;
sects[idx].x += pt.x;
sects[idx].y += pt.y;
sects[idx].c ++;
if(sx == 1 && sy == 1) {
return "UNKNOWN";
}
}
// get the significant points (clockwise)
var sigPts = [];
var clk = [0, 1, 2, 5, 8, 7, 6, 3]
for(var i=0; i<clk.length; i++) {
var pt = sects[clk[i]];
if(pt.c > 0) {
sigPts.push({x: pt.x / pt.c, y: pt.y / pt.c});
} else {
return "UNKNOWN";
}
}
this.draw("#0f0", sigPts);
// find angle between consecutive 3 points
var angles = [];
for(var i=0; i<sigPts.length; i++) {
var a = sigPts[i],
b = sigPts[(i + 1) % sigPts.length],
c = sigPts[(i + 2) % sigPts.length],
ab = Math.sqrt(Math.pow(b.x-a.x,2)+Math.pow(b.y-a.y,2)),
bc = Math.sqrt(Math.pow(b.x-c.x,2)+ Math.pow(b.y-c.y,2)),
ac = Math.sqrt(Math.pow(c.x-a.x,2)+ Math.pow(c.y-a.y,2)),
deg = Math.floor(Math.acos((bc*bc+ab*ab-ac*ac)/(2*bc*ab)) * 180 / Math.PI);
angles.push(deg);
}
console.log(angles);
var corners = this.getCorners(angles, sigPts);
// get polar angle of corners
for(var i=0; i<corners.length; i++) {
corners[i].t = Math.floor(Math.atan2(corners[i].y - center.y, corners[i].x - center.x) * 180 / Math.PI);
}
console.log(corners);
// whats the shape ?
if(corners.length <= 1) { // circle
return "CIRCLE";
} else if(corners.length == 2) { // circle || diamond
// difference of polar angles
var diff = Math.abs((corners[0].t - corners[1].t + 180) % 360 - 180);
console.log(diff);
if(diff <= this.circleThreshold) {
return "CIRCLE";
} else {
return "DIAMOND";
}
} else if(corners.length == 4) { // rectangle || diamond
// sum of polar angles of corners
var sum = Math.abs(corners[0].t + corners[1].t + corners[2].t + corners[3].t);
console.log(sum);
if(sum <= this.rectangleThreshold) {
return "RECTANGLE";
} else if(sum >= this.diamondThreshold) {
return "DIAMOND";
} else {
return "UNKNOWN";
}
} else {
alert("draw neater please");
return "UNKNOWN";
}
}
};
state.canvas.addEventListener("mousedown", (function(e) {
if(!this.drawing) {
this.ctx.clearRect(0, 0, 300, 300);
this.points = [];
this.drawing = true;
console.log("drawing start");
}
}).bind(state), false);
state.canvas.addEventListener("mouseup", (function(e) {
this.drawing = false;
console.log("drawing stop");
this.draw("#f00");
alert(this.classify());
}).bind(state), false);
state.canvas.addEventListener("mousemove", (function(e) {
if(this.drawing) {
var x = e.pageX, y = e.pageY;
this.points.push({"x": x, "y": y});
this.ctx.fillStyle = "#000";
this.ctx.fillRect(x-2, y-2, 4, 4);
}
}).bind(state), false);
答案 1 :(得分:3)
鉴于手写输入可能存在差异,我建议采用神经网络方法;你会发现很难或不可能手动准确地模拟这些类。 LastCoder的尝试在某种程度上起作用,但如果进一步研究,它无法应对很多变化,或者有很高的准确性 - 很久以前就放弃了这种手工设计的方法。
目前手写字符分类的最新结果通常是通过convolutional neural networks (CNNs)实现的。鉴于您只有3个类,问题应该比数字或字符分类更容易,尽管根据MNIST手写数字数据集的经验,我希望您的圈子,正方形和钻石偶尔可能最终难以区分人类。
所以,如果由我决定,我会使用CNN。我会将从绘图区域拍摄的二进制图像输入到网络的第一层。这些可能需要一些预处理。如果绘制的形状覆盖输入空间的非常小的区域,则可以使它们膨胀(即增加线厚度),以使形状对于小的差异更加不变。尽管pooling步骤可能会减少对图像的需求,但在图像中将形状居中也可能是有益的。
我还要指出,训练数据越多越好。人们常常在增加一个数据集的大小和改进一个模型之间进行权衡。合成更多示例(例如通过倾斜,旋转,移动,拉伸等)或花费几个小时绘制形状可能会提供比在尝试改进模型的同时获得的更多好处。
祝你的应用好运!
答案 2 :(得分:2)
方形或菱形的线性Hough变换应该易于识别。它们都会产生四点质量。正方形将在零和90度成对,两个对的y坐标相同;换句话说,一个矩形。钻石将处于对应于钻石如何变薄的另外两个角度,例如钻石。 45和135或60和120。
对于圆,你需要一个圆形的Hough变换,它将在3d(x,y,r)Hough空间中产生一个亮点。
线性和圆形Hough变换都在OpenCV和it's possible to run OpenCV on Android中实现。这些实现包括阈值处理以识别线和圆。见pg。 329和pg。 the documentation here的331。
如果您不熟悉Hough变换,the Wikipedia page也不错。
this paper about polygon similarity中给出了另一种有趣且可能有用的算法。我多年前实现了它,它仍然在here左右。如果您可以将数字转换为向量循环,则此算法可以将它们与模式进行比较,并且相似性度量将显示匹配的良好性。该算法忽略旋转方向,因此如果您对方形和菱形的定义是相对于绘图表面的轴,则必须稍微修改算法以区分这些情况。
答案 3 :(得分:1)
你在这里有一个相当标准的分类任务,在一个可论证的愿景领域。 您可以通过多种方式完成此操作,但最佳方法尚不清楚,有时可能依赖于问题的详细信息。
所以,这本身并不是一个答案,但有一个网站 - Kaggle.com,它负责分类竞争。他们列出的样本/实验任务之一是阅读单手写的数字。这足够接近这个问题,相同的方法几乎肯定会适用得相当好。
我建议你去https://www.kaggle.com/c/digit-recognizer环顾四周。
但是如果这太模糊了,我可以告诉你,在阅读它时,并且在那个问题空间中玩,随机森林是一个比神经网络更好的基本起点。
答案 4 :(得分:1)
在这种情况下(你的3个简单物体)你可以尝试RanSaC拟合椭圆(获得圆)和线(获得矩形或菱形的边) - 如果有几个对象要分类在每个连接的对象上同一时间。根据实际设置(预期大小等),RanSaC参数(必须将一个点作为选民计算的接近程度,最小化时需要多少选民)必须进行调整。当您找到RanSaC拟合线时,删除“关闭”它的点并转到下一行。线条的角度应该容易区分diamand和rectangle。
答案 5 :(得分:1)
为了准确分类这3个对象而优化的一种非常简单的方法可能如下:
答案 6 :(得分:1)
我建议采用以下步骤来实现: -
答案 7 :(得分:1)
我认为已经存在的答案是好的,但也许更好的思考方式是你应该尝试将问题分解为有意义的部分。
我怀疑您会发现您不需要复杂的算法来查找圆,线,金字塔,但更适合构建适当的代码。
答案 8 :(得分:0)
如果我是你,我将使用已经可用的图像处理库,如“AForge”
看看这篇示例文章:
http://www.aforgenet.com/articles/shape_checker
答案 9 :(得分:0)
我have a jar on github如果您愿意解压缩并遵守apache许可证,可以提供帮助。您也可以尝试使用任何其他语言重新创建它。
它是一个边缘探测器。那里最好的一步可能是:
玩弄它并找到你想要的东西。
我的罐子在这个地址向公众开放。它尚未准备就绪,但可以提供帮助。
我想我可以帮忙。如果有人想成为项目的一部分,请做。
答案 10 :(得分:0)
我最近在医学影像中识别出圆圈(骨骼中心)。
注意:步骤1-2是您从图像中抓取的。
Psuedo代码步骤
步骤1.突出显示边缘
edges = edge_map(of the source image)
(使用边缘检测器)
(laymens:显示线条/边缘 - 让它们可搜索)
步骤2.追踪每个唯一边缘
我会(使用最近邻搜索9x9或25x25)来识别/跟踪/跟踪每条边,将每个点收集到列表中(它们成为邻居),并注意每个点的gradient
。
这一步产生:一组边
(其中一个edge/curve/line = list of [point_gradient_data_structure]s
(laymens:沿图像边缘收集一组点)
步骤3.分析每个边缘(点和梯度数据)
对于每个边缘,
如果某个给定区域/一组邻居(沿边缘的一组点)的渐变相似,那么我们有一个straight line
。
如果渐变逐渐变化,我们会得到curve
。
每个区域/一组点是直线或曲线,具有平均值(中心)和其他梯度统计量。
步骤4.检测对象
我们可以使用步骤3中的摘要信息来建立有关钻石,圆形或正方形的结论。 (即4条直线,具有适当渐变的端点附近是菱形或方形。一条(或多条)曲线具有足够的点/梯度(具有共同的焦点),形成一个完整的圆形)。
注意:使用image pyramid
可以在结果和速度方面提高算法性能。
这项技术(步骤1-4)可以为定义良好的形状完成工作,还可以检测到绘制得不够完美的形状,并且可以处理略微断开的线条(如果需要)。
注意:使用一些机器学习技术(其他海报提到),拥有良好的“分类器”基本上可以将问题分解为更小的部件/组件可能是有帮助的/重要的,因此在链条下方的决策者可以用来更好地理解/“看到”对象。我认为机器学习对于这个问题可能有点笨拙,但仍然可以产生合理的结果。 PCA(面部检测)也可能起作用。