C# 象棋界面的图形识别和比对

using System;
using System.Collections.Generic;
 
using System.Text;

namespace boardDemo
{
    class verticalLine
    {
        定义一个竖线结构体
        public verticalLine(int x, int startPoint, int endPoint)
        {
            this.X = x;
            this.StartPoint = startPoint;
            this.EndPoint = endPoint;
            this.Length = endPoint - startPoint;

        }

        public int X;//横坐标位置
        public int StartPoint;//开始点
        public int EndPoint;//终止点
        public int Length;//长度
        public int Width=0;//距离上一条线的宽度
        public bool IsUseful=false;//是否有用,默认没用
    }
}

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;

namespace boardDemo
{
    public partial class Form1 : Form
    {
        //0,总体核心算法思路设想:找出9条平行的棋盘竖线的X坐标,和之间的距离,以及楚河上线位置Y
        //1,先抓屏幕。
        //2,沿X轴扫描,每次扫描一条线,判断亮度低的连续点就是线,其中20像素以上长度为有效的。
        //3,象棋棋盘上线段是分段有规律的,和屏幕干扰线段不同,排除不符合规律的。
        //4,四条斜线为了显示在调试运行的代码,可以随时去掉注释观察中间结果。
        //5,使用了泛型链表List,如果在C++中无对应功能,可以查阅MSDN,自己做一个简易的链表。
        //6,绝大多数界面都可以,极少花哨的界面可能识别不出来。

        public Form1()
        {
            InitializeComponent();
        }
        //声明一个API函数  
        [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
        public static extern bool BitBlt(
        IntPtr hdcDest, //目标设备的句柄
        int nXDest, // 目标对象的左上角的X坐标
        int nYDest, // 目标对象的左上角的X坐标
        int nWidth, // 目标对象的矩形的宽度
        int nHeight, // 目标对象的矩形的长度
        IntPtr hdcSrc, // 源设备的句柄
        int nXSrc, // 源对象的左上角的X坐标
        int nYSrc, // 源对象的左上角的X坐标
        System.Int32 dwRop // 光栅的操作值
        );

        int[] howManyLines;
        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void display(List<verticalLine> myList)
        {
            //测试用函数显示数据用,不是必须的
            richTextBox1.Text += "一共:" + myList.Count + "/n";
            int index = 0;
            for (int i = 0; i < myList.Count; i++)
            {
                index++;
                richTextBox1.Text += index + ":";
                richTextBox1.Text += myList[i].X;
                richTextBox1.Text += "  长度" + myList[i].Length;
                richTextBox1.Text += " 段数" + howManyLines[myList[i].X];
                richTextBox1.Text += " 宽度" + myList[i].Width;
                richTextBox1.Text += " 头" + myList[i].StartPoint;
                richTextBox1.Text += " 尾" + myList[i].EndPoint;
                if (myList[i].IsUseful) { richTextBox1.Text += " 有效"; } else { richTextBox1.Text += " 无效"; }
                richTextBox1.Text += "/n";
                Application.DoEvents();
            }
        }

 

        private void button1_Click(object sender, EventArgs e)
        {
            //设置开始时间,用来测试估算整个过程需要的时间
            long CurrentTime = System.DateTime.Now.Ticks;

            //
            richTextBox1.Text = "";
            //获得当前屏幕的大小  
            //建立屏幕Graphics
            Graphics grpScreen = Graphics.FromHwnd(IntPtr.Zero);
            //根据屏幕大小建立位图
            Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, grpScreen);
            //建立位图相关Graphics
            Graphics grpBitmap = Graphics.FromImage(bitmap);
            //建立屏幕上下文
            IntPtr hdcScreen = grpScreen.GetHdc();
            //建立位图上下文
            IntPtr hdcBitmap = grpBitmap.GetHdc();
            //将屏幕捕获保存在图位中
            BitBlt(hdcBitmap, 0, 0, bitmap.Width, bitmap.Height, hdcScreen, 0, 0, 0x00CC0020);
            //关闭位图句柄
            grpBitmap.ReleaseHdc(hdcBitmap);
            //关闭屏幕句柄
            grpScreen.ReleaseHdc(hdcScreen);
            //释放位图对像
            grpBitmap.Dispose();
            //释放屏幕对像
            grpScreen.Dispose();

            //自此得到了屏幕抓屏          
            //---------------------------------------------------------------------------------------------------------------------------------------------------------
            //下面要得到可能的竖线

            //大于20认为是条竖线。把找到的verticalLine放到链表里,复杂度为O(w*h)
            List<verticalLine> myLines = new List<verticalLine>();
            //纪录一下这个x上,究竟有多少条竖的线段,作为将来排除依据
            //太少就排除掉,因为象棋初始局面由棋子分割,至少有3个线段。
            howManyLines = new int[bitmap.Width];


            for (int x = 0; x < bitmap.Width; x++)
            {
                int lineLength = 0;
                int startPoint = 0;


                for (int y = 0; y < bitmap.Height; y++)
                {
                    Color C = bitmap.GetPixel(x, y);
                    //如果亮度小就可能有竖线。亮度阀值是小于0.1                   
               
                    if (C.GetBrightness() < 0.2)
                    {

                        lineLength++;
                        //如果可能连续
                        if (lineLength == 2)
                        {
                            //是否可能是线段,在连续两个像素后设置为起点。
                            startPoint = y - 1;
                        }
                        bitmap.SetPixel(x, y, Color.Black);
                    }
                    else
                    {
                        //最小的棋盘也要大于20,小于20的不考虑,大于300的也不考虑(4,6线最长不超过300),的时候设置为终点,保存。

                        if (lineLength >= 20 && lineLength<300)
                        {
                            myLines.Add(new verticalLine(x, startPoint, y));
                            //纪录一下这个x上有几条竖线。
                            howManyLines[x]++;

                        }
                        //线中断;
                        lineLength = 0;
                        bitmap.SetPixel(x, y, Color.White);
                    }
                }

            }
            bitmap.Save(@"c:/2.jpg", ImageFormat.Jpeg);
            //display(myLines);
            //自此得到了可能的竖线
            //---------------------------------------------------------------------------------------------------------------------------------------------------------
            //下面需要排除掉不合理的线。

            //线段太少就排除掉,因为象棋初始局面由棋子分割,至少有2个线段。

            for (int i = 0; i < myLines.Count; i++)
            {
                if (howManyLines[myLines[i].X] < 2)
                {
                    //删除。
                    myLines.RemoveAt(i);
                    i--;
                }
            }
            //自此得到了所有可能的线。
            //display(myLines);
            //---------------------------------------------------------------------------------------------------------------------------------------------------------
            //开始判断最可能的单元格宽度


            //定义一个数组,用来纪录竖线之间的距离,下标代表宽度,下标可能值是10到80,数组的值纪录这个宽度出现次数。
            //宽度出现次数最多的就可能是我们棋盘的宽度。
            int[] allMaybeBoardwidth = new int[80];

            int lastX = 0;

            for (int i = 0; i < myLines.Count; i++)
            {
                int x = myLines[i].X;
                int width = x - lastX;
                if (width == 0 && i > 0)
                {
                    width = myLines[i - 1].Width;
                }
                myLines[i].Width = width;
                if (width >= 20 && width < 80)
                {
                    allMaybeBoardwidth[width]++;
                }
                lastX = x;
            }


            //得到最可能宽度
            int maybeWidth = 0;
            int maxProbability = 0;
            for (int i = 10; i < allMaybeBoardwidth.Length; i++)
            {
                if (allMaybeBoardwidth[i] > maxProbability)
                {
                    maybeWidth = i;
                    maxProbability = allMaybeBoardwidth[i];

                }
            }
            display(myLines);
             richTextBox1.Text +="棋盘单元格可能宽度是:" + maybeWidth + "/n";
            //自此得到最可能的宽度。

            //---------------------------------------------------------------------------------------------------------------------------------------------------------
            //得到其他可能正确的线,虽然可能受到屏幕干扰


            //先挑选一下距离正好的,肯定是有用的。
            for (int i = 0; i < myLines.Count; i++)
            {
                //误差为1都可以
                if (Math.Abs(myLines[i].Width - maybeWidth) < 2)
                {
                    myLines[i].IsUseful = true;//标记为有用的
                }
            }
            display(myLines);
            //如果无效的,那么和有效的距离是理想宽度也可以
            for (int i = 0; i < myLines.Count; i++)
            {
                if (!myLines[i].IsUseful)
                {
                    //如果无效的,那么和有效的距离是理想宽度也可以
                    for (int j = 0; j < myLines.Count; j++)
                    {
                        if (myLines[j].IsUseful && Math.Abs(myLines[j].X - myLines[i].X - maybeWidth) < 2)
                        {
                            myLines[i].IsUseful = true;//有用的
                            myLines[i].Width = maybeWidth;
                            break;
                        }
                    }
                }
            }
            //display(myLines);
            //---------------------------------------------------------------------------------------------------------------------------------------------------------
            //得到9条线。
            lastX = 0;
            int LineNum = 0;
            int[] boardViticalLineX = new int[9];
            for (int i = 0; i < myLines.Count; i++)
            {
                if (myLines[i].IsUseful)
                {
                    if (lastX != myLines[i].X && ((lastX + 1) != myLines[i].X))
                    {
                        if (LineNum < 9)
                        {
                            boardViticalLineX[LineNum] = myLines[i].X;
                        }
                        LineNum++;
                    }
                    lastX = myLines[i].X;
                }
            }
            richTextBox1.Text += "找到了"+ LineNum +"条线/n";
            display(myLines);
            //---------------------------------------------------------------------------------------------------------------------------------------------------------
            //查找楚河的上沿横线y轴位置
            int riverSide = 0;
            //定义一个索引,用来查找线段头的数量最多的。
            //maybeWidth * 5是因为不会有人把棋盘放到屏幕外边,因此楚河的上沿必然大于maybeWidth * 5
            int[] River = new int[bitmap.Height];

            for (int i = 0; i < myLines.Count; i++)
            {
                if (myLines[i].IsUseful && myLines[i].EndPoint > maybeWidth * 5)
                {
                    River[myLines[i].EndPoint]++;
                }
            }

            richTextBox1.Text += "/n";

            for (int i = maybeWidth * 5; i < River.Length - maybeWidth * 5; i++)
            {
                if (River[i] > 0)
                {
                    //大棋盘时候会识别出来7个头,小棋盘恰好识别4个头
                    if ((River[i] % 7== 0)  || (River[i] == 4 && maybeWidth < 40))
                    {
                        riverSide = i;
                        //break;
                    }
                    richTextBox1.Text += i +":"+ River[i]+"/n";
                }

            }
             richTextBox1.Text += "楚河的上沿:" + riverSide + "/n";

            //楚河的上沿找到了
            //---------------------------------------------------------------------------------------------------------------------------------------------------------
            //切图并且存储图片,作为将来识别的依据
            Rectangle rect;
            Bitmap Chess;
            richTextBox1.Text += "有效数量是:" + LineNum + "可能宽度是:" + maybeWidth +"楚河的上沿:" + riverSide + "/n";
            if (LineNum == 9 && riverSide > 0)
            {
                //成功找到了9条竖线
                //MessageBox.Show("有效数量是:" + LineNum + "可能宽度是:" + maybeWidth);
                for (int i = 0; i < 5; i++)
                {
                    //richTextBox1.Text += boardViticalLineX[i] + "/n";
                    rect = new Rectangle(boardViticalLineX[i] - (int)(maybeWidth / 2) + 1, riverSide - (int)(4.5 * maybeWidth) + 1, maybeWidth - 2, maybeWidth - 2);
                    Chess = bitmap.Clone(rect, bitmap.PixelFormat);
                    Chess.Save(@"c:/黑" + i + ".jpg", ImageFormat.Jpeg);

                    rect = new Rectangle(boardViticalLineX[i] - (int)(maybeWidth / 2) + 1, riverSide + (int)(4.5 * maybeWidth) + 1, maybeWidth - 2, maybeWidth - 2);
                    Chess = bitmap.Clone(rect, bitmap.PixelFormat);
                    Chess.Save(@"c:/红" + i + ".jpg", ImageFormat.Jpeg);
                    if (i == 0)
                    {
                        //兵和卒
                        rect = new Rectangle(boardViticalLineX[i] - (int)(maybeWidth / 2) + 1, riverSide - (int)(1.5 * maybeWidth) + 1, maybeWidth - 2, maybeWidth - 2);
                        Chess = bitmap.Clone(rect, bitmap.PixelFormat);
                        Chess.Save(@"c:/黑" + 6 + ".jpg", ImageFormat.Jpeg);

                        rect = new Rectangle(boardViticalLineX[i] - (int)(maybeWidth / 2) + 1, riverSide + (int)(1.5 * maybeWidth) + 1, maybeWidth - 2, maybeWidth - 2);
                        Chess = bitmap.Clone(rect, bitmap.PixelFormat);
                        Chess.Save(@"c:/红" + 6 + ".jpg", ImageFormat.Jpeg);
                    }
                    if (i == 1)
                    {
                        //炮
                        rect = new Rectangle(boardViticalLineX[i] - (int)(maybeWidth / 2) + 1, riverSide - (int)(2.5 * maybeWidth) + 1, maybeWidth - 2, maybeWidth - 2);
                        Chess = bitmap.Clone(rect, bitmap.PixelFormat);
                        Chess.Save(@"c:/黑" + 5 + ".jpg", ImageFormat.Jpeg);

                        rect = new Rectangle(boardViticalLineX[i] - (int)(maybeWidth / 2) + 1, riverSide + (int)(2.5 * maybeWidth) + 1, maybeWidth - 2, maybeWidth - 2);
                        Chess = bitmap.Clone(rect, bitmap.PixelFormat);
                        Chess.Save(@"c:/红" + 5 + ".jpg", ImageFormat.Jpeg);
                    }

                }
                //成功找到了9条竖线
                CurrentTime = System.DateTime.Now.Ticks - CurrentTime;
                //如果删除掉测试用的,一般是在2000毫秒左右。
                MessageBox.Show("已经把棋子存储到了C盘根目录,总共用时:" + TimeSpan.FromTicks(CurrentTime).TotalMilliseconds + "毫秒!~~/n目前可以得到和屏幕分辨率,大厅无关的参考图片了。/n剩余识别工作算法应该和原来一样,我就不研究了,性能可能还可以提高:)");
            }
            else
            {
                MessageBox.Show("没发现大小合适,并且有开局棋子,而且是唯一的棋盘!/n请在设置方案的时候确保棋盘没被覆盖,棋盘不是过大或者过小。过于花哨");
            }

        }

 

 

 


    }
}

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值