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請在設置方案的時候確保棋盤沒被覆蓋,棋盤不是過大或者過小。過於花哨");
            }

        }

 

 

 


    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章