识别手写圆圈,钻石和矩形

时间:2013-11-27 22:37:48

标签: android algorithm neural-network handwriting-recognition

我正在寻找关于识别三种手写形状的一些建议 - 圆形,菱形和矩形。我试过不同的aproaches,但他们失败了所以也许你可以指出我在另一个更好的方向。

我尝试了什么:

1)基于手写形状和理想形状点之间的点积的简单算法。它在识别矩形方面并不是那么糟糕,但在圆形和钻石上失败了。问题是圆形和菱形的点积非常类似于理想的形状。

2)同样的方法,但使用动态时间扭曲作为相似性的度量。类似的问题。

3)神经网络。我尝试了一些方法 - 给神经网络(Feedforward和Kohonen)提供点数据或给出光栅化图像。对于Kohonen来说,它始终将所有数据(用于训练的样本)分类到同一类别。带点的前馈更好(但与aproach 1和2处于同一水平)并且光栅化图像非常慢(我需要至少大小^ 2输入神经元,对于小尺寸的光栅圆,即使对我来说也难以区分;))也没有成功。我想是因为所有这些形状都是封闭的数字?我不是ANN的大专家(有一个学期的课程)所以也许我使用它们错了?

4)将形状保存为Freeman Chain Code并使用一些算法来计算相似度。我认为在FCC中,形状将彼此不同。这里没有成功(但我没有深入探索这条道路)。

我正在为Android构建应用程序,但我认为这里的语言无关紧要。

11 个答案:

答案 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个对象而优化的一种非常简单的方法可能如下:

  1. 计算要分类的对象的重心
  2. 然后计算中心到物点的距离作为角度的函数(从0到2 pi)。
  3. 根据平滑度和/或方差以及局部最大值和最小值的位置和高度(可能在平滑图表后)对结果图进行分类。

答案 6 :(得分:1)

我建议采用以下步骤来实现: -

  1. 取图像的凸包(考虑形状为凸面)
  2. 使用聚类算法划分为分段
  3. 尝试将曲线或直线拟合到它并测量&amp;使用可用于分类的训练集的阈值
  4. 对于您的应用程序,请尝试分为4个群集。
  5. 将群集分类为线条或曲线后,您可以使用该信息来推导曲线是圆形,矩形还是菱形

答案 7 :(得分:1)

我认为已经存在的答案是好的,但也许更好的思考方式是你应该尝试将问题分解为有意义的部分。

  1. 如果可能的话,完全避免这个问题。例如,如果您正在识别手势,只需实时分析手势即可。通过手势,您可以向用户提供有关您的程序如何解释其手势的反馈,并且用户将更改他们正在做的事情。
  2. 清理相关图片。在你做任何事情之前想出一个算法来试图选择你想要分析的正确的东西。在开始此过程之前,还可以使用适当的过滤器(可能是卷积)来删除图像伪影。
  3. 一旦你弄清楚你要分析的是什么,然后分析它并返回一个分数,一个用于圆圈,一个用于噪声,一个用于线,最后一个用于金字塔。
  4. 与下一位可行的候选人一起重复此步骤,直至找到最佳候选人,而不是噪音。
  5. 我怀疑您会发现您不需要复杂的算法来查找圆,线,金字塔,但更适合构建适当的代码。

答案 8 :(得分:0)

如果我是你,我将使用已经可用的图像处理库,如“AForge” 看看这篇示例文章:
http://www.aforgenet.com/articles/shape_checker

答案 9 :(得分:0)

have a jar on github如果您愿意解压缩并遵守apache许可证,可以提供帮助。您也可以尝试使用任何其他语言重新创建它。

它是一个边缘探测器。那里最好的一步可能是:

  1. 找到角落(中位数为90度)
  2. 找到平均中位数和最大半径
  3. 从水平
  4. 找到倾斜/角度
  5. 让决策代理决定形状是什么
  6. 玩弄它并找到你想要的东西。

    我的罐子在这个地址向公众开放。它尚未准备就绪,但可以提供帮助。

    我想我可以帮忙。如果有人想成为项目的一部分,请做。

答案 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(面部检测)也可能起作用。