Example #1
0
 /// <summary>
 /// 检查另一笔画是否“单方面”被认为和这一笔画重叠。这个检查不是对称关系。
 /// </summary>
 /// <param name="other"></param>
 private bool CheckPosition(StrokeRecord other)
 {
     return((other.HorizontalStart < OverlayMaxStart) || (OverlayMinEnd < other.HorizontalEnd));
 }
Example #2
0
 public bool OverlayWith(StrokeRecord other)
 {
     return(this.CheckPosition(other) || other.CheckPosition(this));
 }
Example #3
0
        /// <summary>
        /// 手写区松开鼠标键,或手指离开屏幕时执行
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void writeArea_MouseUp(object sender, MouseEventArgs e)
        {
            // 鼠标事件较多,通过条件来仅在鼠标左键按下,或手指在屏幕上时,才执行。
            if (e.Button == MouseButtons.Left)
            {
                // 必须确实发生了鼠标移动事件,即有线段被画出,我们才认为有笔画存在。
                if (strokePoints.Any())
                {
                    var thisStrokeRecord = new StrokeRecord(strokePoints);
                    allStrokes.Add(thisStrokeRecord);

                    // 将所有笔画按水平起点排序,然后按重叠与否进行分组。
                    allStrokes = allStrokes.OrderBy(s => s.HorizontalStart).ToList();
                    int[] strokeGroupIds = new int[allStrokes.Count];
                    int   nextGroupId    = 1;

                    for (int i = 0; i < allStrokes.Count; i++)
                    {
                        // 为了避免水平方向太多笔画被连在一起,我们采取一种简单的办法:
                        // 当1、2笔画重叠时,我们就不会在检查笔画2和更右侧笔画是否重叠。
                        if (strokeGroupIds[i] != 0)
                        {
                            continue;
                        }

                        strokeGroupIds[i] = nextGroupId;
                        nextGroupId++;

                        var s1 = allStrokes[i];
                        for (int j = 1; i + j < allStrokes.Count; j++)
                        {
                            var s2 = allStrokes[i + j];

                            if (s2.HorizontalStart < s1.OverlayMaxStart)
                            {
                                if (strokeGroupIds[i + j] == 0)
                                {
                                    if (s1.OverlayWith(s2))
                                    {
                                        strokeGroupIds[i + j] = strokeGroupIds[i];
                                    }
                                }
                            }
                            else
                            {
                                break;
                            }
                        }
                    }

                    bool enableDebug = visualizeSwitch.Checked;
                    if (enableDebug)
                    {
                        graphics.Clear(Color.White);
                    }

                    // 清除之前显式的推理结果
                    outputText.Text = "";

                    var batchInferInput = new List <IEnumerable <float> >();

                    Pen penStyle = new Pen(Color.Black, 20)
                    {
                        StartCap = LineCap.Round,
                        EndCap   = LineCap.Round
                    };

                    List <IGrouping <int, StrokeRecord> > groups = allStrokes
                                                                   .Zip(strokeGroupIds, Tuple.Create)
                                                                   .GroupBy(tuple => tuple.Item2, tuple => tuple.Item1) // Item2是分组编号, Item1是StrokeRecord
                                                                   .ToList();
                    foreach (IGrouping <int, StrokeRecord> group in groups)
                    {
                        int gid            = group.Key;
                        var groupedStrokes = group.ToList(); // IGrouping<TKey, TElement>本质上也是一个可迭代的IEnumerable<TElement>

                        // 确定整个分组的所有笔画的范围。
                        int grpHorizontalStart  = groupedStrokes.Min(s => s.HorizontalStart);
                        int grpHorizontalEnd    = groupedStrokes.Max(s => s.HorizontalEnd);
                        int grpHorizontalLength = grpHorizontalEnd - grpHorizontalStart;

                        int      canvasEdgeLen  = writeArea.Height;
                        Bitmap   canvas         = new Bitmap(canvasEdgeLen, canvasEdgeLen);
                        Graphics canvasGraphics = Graphics.FromImage(canvas);
                        canvasGraphics.Clear(Color.White);

                        // 因为我们提取了每个笔画,就不能把长方形的绘图区直接当做输入了。
                        // 这里我们把宽度小于 writeArea.Height 的分组在 canvas 内居中。
                        int halfOffsetX = Math.Max(canvasEdgeLen - grpHorizontalLength, 0) / 2;

                        var grpClr  = GetDebugColor(gid);
                        var rectClr = Color.FromArgb(120, grpClr);

                        foreach (var stroke in groupedStrokes)
                        {
                            if (enableDebug)
                            {
                                graphics.FillRectangle(
                                    new SolidBrush(rectClr),
                                    stroke.OverlayMinEnd,
                                    0,
                                    Math.Max(2, stroke.OverlayMaxStart - stroke.OverlayMinEnd), // At least width of 2px
                                    30);
                            }

                            Point startPoint = stroke.Points[0];
                            foreach (var point in stroke.Points.Skip(1))
                            {
                                var from = startPoint;
                                var to   = point;

                                // 因为每个分组都是在长方形的绘图区被记录的,所以在单一位图上,需要先减去相对于长方形绘图区的偏移量 grpHorizontalStart
                                from.X = from.X - grpHorizontalStart + halfOffsetX;
                                to.X   = to.X - grpHorizontalStart + halfOffsetX;
                                canvasGraphics.DrawLine(penStyle, from, to);

                                /*
                                 * 调试用。
                                 * 取消注释后可以看到每一笔画,会按照其分组显示不同的颜色。
                                 */
                                if (enableDebug)
                                {
                                    graphics.DrawLine(
                                        new Pen(grpClr, 20)
                                    {
                                        StartCap = LineCap.Round,
                                        EndCap   = LineCap.Round
                                    },
                                        startPoint,
                                        point);
                                }

                                startPoint = point;
                            }
                        }

                        // 1. 将分割出的笔画图片缩小至 28 x 28,与训练数据格式一致。
                        Bitmap clonedBmp = new Bitmap(canvas, ImageSize, ImageSize);

                        var image = new List <float>(ImageSize * ImageSize);
                        for (var x = 0; x < ImageSize; x++)
                        {
                            for (var y = 0; y < ImageSize; y++)
                            {
                                var color = clonedBmp.GetPixel(y, x);
                                image.Add((float)(0.5 - (color.R + color.G + color.B) / (3.0 * 255)));
                            }
                        }

                        // 将这一组笔画对应的矩阵保存下来,以备批量推理。
                        batchInferInput.Add(image);
                    }

                    // 2. 进行批量推理
                    //    batchInferInput 是一个列表,它的每个元素都是一次推量的输入。
                    //IEnumerable<IEnumerable<long>> inferResult = model.Infer(batchInferInput);
                    var inferResult = batchInferInput.SelectMany(i => model.Infer(new List <IEnumerable <float> > {
                        i
                    })).ToList();

                    //    推量的结果是一个可枚举对象,它的每个元素代表了批量推理中一次推理的结果。我们用 仅一次.First() 将它们的结果都取出来,并格式化。
                    // outputText.Text = string.Join("", inferResult.Select(singleResult => singleResult.First().ToString()));

                    var recognizedLabels = inferResult.Select(singleResult => (int)singleResult.First()).ToList();
                    outputText.Text = EvaluateAndFormatExpression(recognizedLabels);

                    if (enableDebug)
                    {
                        // 这是调试用的。在上面的调试代码没有启用时,这句话没有特别作用。
                        writeArea.Invalidate();
                    }
                }
            }
        }