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请在设置方案的时候确保棋盘没被覆盖,棋盘不是过大或者过小。过于花哨");
}
}
}
}