Pax*_*x0r 6 algorithm android neural-network handwriting-recognition
我正在寻找关于识别三种手写形状的一些建议 - 圆形,菱形和矩形.我尝试了不同的aproaches,但他们失败了所以也许你可以指出我在另一个更好的方向.
我尝试了什么:
1)基于手写形状和理想形状点之间的点积的简单算法.它在识别矩形方面并不是那么糟糕,但在圆形和钻石上失败了.问题是,即使对于理想的形状,圆形和菱形的点积也非常相似.
2)相同的方法,但使用动态时间扭曲作为相似性的度量.类似的问题.
3)神经网络.我尝试了一些方法 - 给神经网络(Feedforward和Kohonen)提供点数据或给出光栅化图像.对于Kohonen来说,它始终将所有数据(用于训练的样本)分类到同一类别.前馈与点较好(但在同级别的形式给出了1和2),并与栅格图像这是非常慢(我需要至少大小^ 2个输入神经元和小型光栅圆的没有什么区别,甚至对我来说;))也没有成功.我想是因为所有这些形状都是封闭的数字?我不是ANN的大专家(有一个学期的课程)所以也许我使用它们错了?
4)将形状保存为Freeman Chain Code并使用一些算法来计算相似度.我认为在FCC中,形状将彼此不同.这里没有成功(但我没有深入探索这条道路).
我正在使用这个为Android构建应用程序,但我认为这里的语言无关紧要.
这是形状分类器的一些工作代码。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>
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)
考虑到手写输入可能存在的变化,我建议采用神经网络方法;您会发现很难或不可能手动准确地对这些类进行建模。LastCoder 的尝试在一定程度上是有效的,但它不能应对太多的变化,也不能保证如果进一步努力就能实现高精度——这种手工设计的方法很久以前就被放弃了。
如今,手写字符分类的最先进结果通常是通过卷积神经网络 (CNN)实现的。考虑到你只有 3 个类别,这个问题应该比数字或字符分类更容易,尽管根据MNIST手写数字数据集的经验,我预计你的圆形、正方形和菱形有时可能甚至连人类也难以区分。
所以,如果由我决定的话,我会使用 CNN。我将从绘图区域获取的二进制图像输入到网络的第一层。这些可能需要一些预处理。如果绘制的形状覆盖输入空间的很小区域,您可能会受益于将它们增大(即增加线条粗细),以使形状对微小差异更加不变。将形状置于图像的中心也可能是有益的,尽管池化步骤可能会减轻对此的需要。
我还想指出,训练数据越多越好。人们经常面临着增加数据集大小和改进模型之间的权衡。综合更多的例子(例如通过倾斜、旋转、移动、拉伸等)或花几个小时绘制形状可能比尝试改进模型的同时提供更多的好处。
祝您的应用好运!
| 归档时间: |
|
| 查看次数: |
2264 次 |
| 最近记录: |