勇哥的小娱乐:Halcon做fc游戏的自动打怪AI(1)连续窗口截图

勇哥注:

这个系列是勇哥的小娱乐。这么多年来感觉自己就是工作机器,没有一点属于自己的乐子。
突然想到儿时的小梦想是写个fc的坦克大战。要不,先弄个AI,让它自动玩坦克大战吧。


fc的坦克大战是fc模拟器的游戏,要做自动打怪的AI,可以考虑连续窗口截图,然后交由halcon进行处理。

处理结果交由策略代码处理,最后发布手柄控制指令。

因此,首先先要考虑窗口的连续帧如何实时截取,然后就是如何实现虚拟手柄(或者虚拟键盘按键也可以)。

这两点做不到,就玩不下去了。

image.png

图1 fc游戏:坦克大战



勇哥首先试一下截取窗体显示的图像。

写了一个小程序,其中Start Grab使用的win32API来取屏幕快照。

Start Grab2是使用了netMarketing类库的WindowSnap类来截屏幕快照。

image.png


代码如下:

using netMarketing.winAPI;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        [DllImport("user32.dll")]
        public static extern bool PrintWindow(
        IntPtr hwnd,               // Window to copy,Handle to the window that will be copied. 
        IntPtr hdcBlt,             // HDC to print into,Handle to the device context. 
        UInt32 nFlags              // Optional flags,Specifies the drawing options. It can be one of the following values. 
        );

        [DllImport("User32.dll", EntryPoint = "FindWindow")]
        private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);


        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            IntPtr handle = IntPtr.Zero;
            try
            {
                handle = FindWindow(null, textBox1.Text);
                pictureBox1.Image = GetWindowImage(handle);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"{ex.Message}" );
            }
        }

        //传入窗口句柄,获取该窗口的图像信息

        private Image GetWindowImage(IntPtr windownHandle)
        {
         
            Bitmap image = new Bitmap(600, 600);
            Graphics gp = Graphics.FromImage(image);
            IntPtr dc = gp.GetHdc();
            PrintWindow(windownHandle, dc, 0);
            gp.ReleaseHdc();
            gp.Dispose();
            return image;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            WindowSnap grab = new WindowSnap();
            grab.Title = @"SMYNES 执行H:\娱乐\游戏\FC任天堂红白机中文模拟器\FC任天堂红白机中文模拟器\任天堂FC全集\任天堂FC全集\坦克仔.nes";
            grab.GetWindowSnap();
            pictureBox1.Image = grab.Image;
        }
    }
}


由于需要知道待截窗口的标题,可以用C++的 spy++工具来取。

这个小工具是vs2017自带的。

image.png


如下所示的办法取得窗体标题,即下图中的“文本”

image.png


程序跑的结果来看,我们只截取到了窗体,但是显示内容为空白。

即使是netMarketing类的WindowSnap类也取不到内容。

image.png



因为上面是使用Win32API, PrintWindow()进行的抓图。

我们知道按键盘的  PrtSc键,就是屏幕取图, 用的也应该是PrintWindow(猜测,没有证实)。

所以勇哥试着按了一下PrtSc键,然后粘贴到“画图”程序中,发现是可以看到游戏窗口的内容的。

这暗示了可能PrintWindow的调用参数有问题。


查了一下微软的api说明,说这个PrintWindow的第三个参数,可以取值0,1

说的是默认为0, 即打印整个窗口内容,而为1则只复制工作区的内容(即除标题栏和状态栏之外的部分)。

勇哥试了一下,无论是0,还1都不行。

后来参考了一位大神的文章,人家说,还有一个值是2,即这个api后来升级了,提供了对DirecX的支持。

如果没有这个支持,则是截取不到使用DirecX显示的游戏画面的。

(微软官方文档居然这一点没写清楚,真是少见情况。。。。。)



如下:

  //只有窗口的工作区被复制到hdcBlt。默认情况下,复制整个窗口
  [Description(
    "Only the client area of the window is copied to hdcBlt. By default, the entire window is copied.")]
     PW_CLIENTONLY = 0x00000001,

     //适用于使用DirectX或DirectComposition的窗口
     [Description("works on windows that use DirectX or DirectComposition")]
     PW_RENDERFULLCONTENT = 0x00000002


在winapi中有这么一种设计,使用 |  逻辑运算叠加两个属性功能。

比如上面的参数,我希望打印窗口的工作区,并且可以复制DirectX画面。

这个时候可以 1 |  2,结果就是3。

用windows定义的常量来表示,就是: 

(uint)Win32Consts.PrintWindowMode.PW_CLIENTONLY |
                  (uint)Win32Consts.PrintWindowMode.PW_RENDERFULLCONTENT


现在可以了。

image.png


程序计时发现,拍一帧花了22毫秒。

这样一秒最快可以截图45帧,这个速度是满足PAL制式的帧速要求的。

我们知道中国大陆的电视采用PAL制式的帧率是25帧/每秒。

当年FC的红白机就是用电视机做显示终端的。


因此,连续采图的要求使用最简单的PrintWindow就可以满足要求了。



下一篇我们讨论一下如果虚拟按键盘(或者虚拟按手柄)


改动后的代码:

using netMarketing.winAPI;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        [DllImport("user32.dll")]
        public static extern bool PrintWindow(
        IntPtr hwnd,               // Window to copy,Handle to the window that will be copied. 
        IntPtr hdcBlt,             // HDC to print into,Handle to the device context. 
        UInt32 nFlags              // Optional flags,Specifies the drawing options. It can be one of the following values. 
        );

        [DllImport("User32.dll", EntryPoint = "FindWindow")]
        private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);


        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            IntPtr handle = IntPtr.Zero;
            var sw1 = new Stopwatch();
            try
            {
                sw1.Start();
                handle = FindWindow(null, textBox1.Text);
                pictureBox1.Image = GetWindowImage(handle);
                var t1 = sw1.ElapsedTicks;
                var f1 = Stopwatch.IsHighResolution;
                var t2= (1000L * 1000L * 1000L) / Stopwatch.Frequency;
                var t3 = t1 * t2 / (1000L * 1000L);  //毫秒
                sw1.Stop();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"{ex.Message}" );
            }
        }

        //传入窗口句柄,获取该窗口的图像信息

        private Image GetWindowImage(IntPtr windownHandle)
        {
         
            Bitmap image = new Bitmap(600, 600);
            Graphics gp = Graphics.FromImage(image);
            IntPtr dc = gp.GetHdc();
            PrintWindow(windownHandle, dc, (uint)Win32Consts.PrintWindowMode.PW_CLIENTONLY |
                  (uint)Win32Consts.PrintWindowMode.PW_RENDERFULLCONTENT);
            gp.ReleaseHdc();
            gp.Dispose();
            return image;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            WindowSnap grab = new WindowSnap();
            grab.Title = @"SMYNES 执行H:\娱乐\游戏\FC任天堂红白机中文模拟器\FC任天堂红白机中文模拟器\任天堂FC全集\任天堂FC全集\坦克仔.nes";
            grab.GetWindowSnap();
            pictureBox1.Image = grab.Image;
        }
    }



    public sealed class Win32Consts
    {
        public enum DibColorMode : uint
        {
            DIB_RGB_COLORS = 0x00,
            DIB_PAL_COLORS = 0x01,
            DIB_PAL_INDICES = 0x02
        }

        public enum BitmapCompressionMode : uint
        {
            BI_RGB = 0,
            BI_RLE8 = 1,
            BI_RLE4 = 2,
            BI_BITFIELDS = 3,
            BI_JPEG = 4,
            BI_PNG = 5
        }

        public enum RasterOperationMode : uint
        {
            SRCCOPY = 0x00CC0020,
            SRCPAINT = 0x00EE0086,
            SRCAND = 0x008800C6,
            SRCINVERT = 0x00660046,
            SRCERASE = 0x00440328,
            NOTSRCCOPY = 0x00330008,
            NOTSRCERASE = 0x001100A6,
            MERGECOPY = 0x00C000CA,
            MERGEPAINT = 0x00BB0226,
            PATCOPY = 0x00F00021,
            PATPAINT = 0x00FB0A09,
            PATINVERT = 0x005A0049,
            DSTINVERT = 0x00550009,
            BLACKNESS = 0x00000042,
            WHITENESS = 0x00FF0062,
            CAPTUREBLT = 0x40000000 //only if WinVer >= 5.0.0 (see wingdi.h)
        }

        public enum PrintWindowMode : uint
        {
            //只有窗口的工作区被复制到hdcBlt。默认情况下,复制整个窗口
            [Description(
                "Only the client area of the window is copied to hdcBlt. By default, the entire window is copied.")]
            PW_CLIENTONLY = 0x00000001,

            //适用于使用DirectX或DirectComposition的窗口
            [Description("works on windows that use DirectX or DirectComposition")]
            PW_RENDERFULLCONTENT = 0x00000002
        }
    }
}




参考资料:

C#使用BitBlt进行窗口抓图   https://www.cnblogs.com/xhubobo/p/12789466.html

C#抓图服务   https://www.cnblogs.com/xhubobo/p/12809988.html

C#使用PrintWindow进行窗口抓图   https://www.cnblogs.com/xhubobo/p/12789482.html



源码下载:

------------------------------


支付2元或购买VIP会员后,才能查看本内容!立即支付升级会员查询订单




--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年5月    »
12345
6789101112
13141516171819
20212223242526
2728293031
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864