用C#调用Matlab图像处理自制QQ游戏2D桌球瞄准器

时间:2023-03-09 05:46:57
用C#调用Matlab图像处理自制QQ游戏2D桌球瞄准器

平时不怎么玩游戏,有时消遣就玩玩QQ里的2D桌球,但是玩的次数少,不能像骨灰级玩家一样百发百中,肿么办呢?于是某天突发奇想,决定自己也来做个“外挂”。说是外挂,其实只是一个瞄准器,毕竟外挂是修改别人的软件,有点违法的意思,况且自己还没有能力去那么做,所以自己还是弄个瞄准器,做做弊,过下小瘾,同时也提高一下自己的编程能力。

起初(也就是半年前),自己尝试做一个瞄准器的初始版本,用C#做,想法很简单:

Step1.把鼠标移到洞口,获取鼠标位置;

Step2.将鼠标放到要击打的球的圆心上,获取鼠标当前位置

Step3.根据进球时三点共线的原则按照球的半径自动将鼠标移动到准确的击球点。

示意图如下:

于是当初就按照这个想法做了,开始给自己做了个C#版,调用Windows API中的GetDesktopWindow,GetWindowDC,SetCursorPos三个函数,经过简单的数学运算,就基本实现了功能。代码如下:

  1. using System.Drawing;
  2. using System.Windows.Forms;
  3. using System.Windows;
  4. using System.Runtime.InteropServices;
  5. using System;
  6. namespace TaiqiuGua
  7. {
  8. public partial class Form1 : Form
  9. {
  10. const int ra=25;
  11. [DllImport("user32.dll")]
  12. static extern IntPtr GetDesktopWindow();
  13. [DllImport("user32.dll")]
  14. static extern IntPtr GetWindowDC(IntPtr hWnd);
  15. [DllImport("user32.dll")]
  16. static extern bool SetCursorPos(int X, int Y);
  17. public Form1()
  18. {
  19. InitializeComponent();
  20. }
  21. Point startP;
  22. Point endP;
  23. private void Form1_KeyDown(object sender, KeyEventArgs e)
  24. {
  25. switch(e.KeyData)
  26. {
  27. case Keys.F1:
  28. startP=Control.MousePosition;
  29. break;
  30. case Keys.F2:
  31. endP = Control.MousePosition;
  32. break;
  33. case Keys.D1:
  34. int x1 = (int)(endP.X + ra * ((endP.X - startP.X) / Math.Sqrt((endP.X - startP.X) * (endP.X - startP.X) + (endP.Y - startP.Y) * (endP.Y - startP.Y))));
  35. int y1 = (int)(endP.Y + ra * ((endP.Y - startP.Y) / Math.Sqrt((endP.X - startP.X) * (endP.X - startP.X) + (endP.Y - startP.Y) * (endP.Y - startP.Y))));
  36. SetCursorPos(x1, y1);
  37. break;
  38. case Keys.D2:
  39. int x2 = (int)(endP.X - ra * ((-endP.X + startP.X) / Math.Sqrt((-endP.X + startP.X) * (-endP.X + startP.X) + (endP.Y - startP.Y) * (endP.Y - startP.Y))));
  40. int y2 = (int)(endP.Y + ra * ((endP.Y - startP.Y) / Math.Sqrt((-endP.X + startP.X) * (-endP.X + startP.X) + (endP.Y - startP.Y) * (endP.Y - startP.Y))));
  41. SetCursorPos(x2, y2);
  42. break;
  43. case Keys.D3:
  44. int x3 = (int)(endP.X + ra * ((endP.X - startP.X) / Math.Sqrt((endP.X - startP.X) * (endP.X - startP.X) + (-endP.Y + startP.Y) * (-endP.Y + startP.Y))));
  45. int y3 = (int)(endP.Y - ra * ((-endP.Y + startP.Y) / Math.Sqrt((endP.X - startP.X) * (endP.X - startP.X) + (-endP.Y + startP.Y) * (-endP.Y + startP.Y))));
  46. SetCursorPos(x3, y3);
  47. break;
  48. case Keys.D4:
  49. int x4 = (int)(endP.X - ra * ((-endP.X + startP.X) / Math.Sqrt((-endP.X + startP.X) * (-endP.X + startP.X) + (-endP.Y + startP.Y) * (-endP.Y + startP.Y))));
  50. int y4 = (int)(endP.Y - ra * ((-endP.Y + startP.Y) / Math.Sqrt((-endP.X + startP.X) * (-endP.X + startP.X) + (-endP.Y + startP.Y) * (-endP.Y + startP.Y))));
  51. SetCursorPos(x4, y4);
  52. break;
  53. }
  54. GC.Collect();
  55. }
  56. }
  57. }

使用时,只需要激活瞄准器窗口,按F1,F2获取鼠标位置,再根据洞口位置分别选按1、2、3、4数字键就行。

经过N次试验,成功率还挺高,只是有时候手动放置鼠标到被击打球圆心会出现误差,导致击球不准,当然,后来我赢了很多场比赛(嘿嘿,有点不道德!)。

再后来,又用C写了一遍,给同学用了。代码如下:

  1. #include <windows.h>
  2. #include <math.h>
  3. int ra=26;
  4. int flag=0;
  5. POINT startP,endP;
  6. int x5,y5,x2,y2,x3,y3,x4,y4;
  7. LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
  8. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)
  9. {
  10. static TCHAR szAppName[] = TEXT ("GUA") ;
  11. HWND hwnd ;
  12. MSG msg ;
  13. WNDCLASS wndclass ;
  14. wndclass.style = CS_HREDRAW | CS_VREDRAW ;
  15. wndclass.lpfnWndProc = WndProc ;
  16. wndclass.cbClsExtra = 0 ;
  17. wndclass.cbWndExtra = 0 ;
  18. wndclass.hInstance = hInstance ;
  19. wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
  20. wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
  21. wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
  22. wndclass.lpszMenuName = NULL ;
  23. wndclass.lpszClassName = szAppName ;
  24. if (!RegisterClass (&wndclass))
  25. {
  26. MessageBox ( NULL, TEXT ("Program requires Windows NT!"),
  27. szAppName, MB_ICONERROR) ;
  28. return 0 ;
  29. }
  30. hwnd = CreateWindow (szAppName, TEXT ("Programmed By DC"),
  31. WS_OVERLAPPEDWINDOW,
  32. CW_USEDEFAULT, CW_USEDEFAULT,
  33. CW_USEDEFAULT, CW_USEDEFAULT,
  34. NULL, NULL, hInstance, NULL) ;
  35. ShowWindow (hwnd, iCmdShow) ;
  36. SetForegroundWindow(hwnd);
  37. MoveWindow(hwnd,100,100,200,200,TRUE);
  38. UpdateWindow (hwnd) ;
  39. while (GetMessage (&msg, NULL, 0, 0))
  40. {
  41. TranslateMessage (&msg) ;
  42. DispatchMessage (&msg) ;
  43. }
  44. return msg.wParam ;
  45. }
  46. void Draw(HWND hwnd,LPCSTR lpString)
  47. {
  48. HDC hdc ;
  49. PAINTSTRUCT ps ;
  50. RECT rect ;
  51. hdc = BeginPaint (hwnd, &ps) ;
  52. GetClientRect (hwnd, &rect) ;
  53. DrawText (hdc, lpString, -1, &rect,  DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
  54. EndPaint (hwnd, &ps) ;
  55. ReleaseDC(hwnd,hdc);
  56. }
  57. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
  58. {
  59. HBRUSH hBrush ;
  60. HDC hdc ;
  61. PAINTSTRUCT ps ;
  62. RECT rc ;
  63. switch (message)
  64. {
  65. case WM_CREATE:
  66. return 0 ;
  67. case WM_PAINT :
  68. return 0 ;
  69. case WM_KEYDOWN:
  70. switch (wParam)
  71. {
  72. case VK_F1:
  73. GetCursorPos(&startP);
  74. flag=1;
  75. InvalidateRect (hwnd, NULL, TRUE) ;
  76. Draw(hwnd,"第1点已锁定!");
  77. break ;
  78. case VK_F2:
  79. GetCursorPos(&endP);
  80. flag=2;
  81. InvalidateRect (hwnd, NULL, TRUE) ;
  82. Draw(hwnd,"第2点已锁定!");
  83. break ;
  84. case 0x31:
  85. x5 = (int)(endP.x + ra * ((endP.x - startP.x) / sqrt((endP.x - startP.x) * (endP.x - startP.x) + (endP.y - startP.y) * (endP.y - startP.y))));
  86. y5 = (int)(endP.y + ra * ((endP.y - startP.y) / sqrt((endP.x - startP.x) * (endP.x - startP.x) + (endP.y - startP.y) * (endP.y - startP.y))));
  87. SetCursorPos(x5, y5);
  88. break;
  89. case 0x32:
  90. x2 = (int)(endP.x - ra * ((-endP.x + startP.x) / sqrt((-endP.x + startP.x) * (-endP.x + startP.x) + (endP.y - startP.y) * (endP.y - startP.y))));
  91. y2 = (int)(endP.y + ra * ((endP.y - startP.y) / sqrt((-endP.x + startP.x) * (-endP.x + startP.x) + (endP.y - startP.y) * (endP.y - startP.y))));
  92. SetCursorPos(x2, y2);
  93. break;
  94. case 0x33:
  95. x3 = (int)(endP.x + ra * ((endP.x - startP.x) / sqrt((endP.x - startP.x) * (endP.x - startP.x) + (-endP.y + startP.y) * (-endP.y + startP.y))));
  96. y3 = (int)(endP.y - ra * ((-endP.y + startP.y) / sqrt((endP.x - startP.x) * (endP.x - startP.x) + (-endP.y + startP.y) * (-endP.y + startP.y))));
  97. SetCursorPos(x3, y3);
  98. break;
  99. case 0x34:
  100. x4 = (int)(endP.x - ra * ((-endP.x + startP.x) / sqrt((-endP.x + startP.x) * (-endP.x + startP.x) + (-endP.y + startP.y) * (-endP.y + startP.y))));
  101. y4 = (int)(endP.y - ra * ((-endP.y + startP.y) / sqrt((-endP.x + startP.x) * (-endP.x + startP.x) + (-endP.y + startP.y) * (-endP.y + startP.y))));
  102. SetCursorPos(x4, y4);
  103. break;
  104. }
  105. return 0;
  106. case WM_SIZE :
  107. if(flag==1)
  108. {
  109. Draw(hwnd,"第1点已锁定!");
  110. }
  111. else if(flag==2)
  112. {
  113. Draw(hwnd,"第2点已锁定!");
  114. }
  115. else
  116. {InvalidateRect (hwnd, NULL, TRUE) ; }
  117. return 0 ;
  118. case WM_KILLFOCUS:
  119. InvalidateRect (hwnd, NULL, TRUE) ;
  120. hdc = BeginPaint (hwnd, &ps) ;
  121. GetClientRect (hwnd, &rc) ;
  122. hBrush = CreateSolidBrush ( RGB(255,0,0) ) ;
  123. FillRect (hdc, &rc, hBrush) ;
  124. EndPaint (hwnd, &ps) ;
  125. ReleaseDC(hwnd,hdc);
  126. DeleteObject (hBrush) ;
  127. return 0;
  128. case WM_SETFOCUS:
  129. InvalidateRect (hwnd, NULL, TRUE) ;
  130. if(flag==1)
  131. {
  132. Draw(hwnd,"第1点已锁定!");
  133. }
  134. else if(flag==2)
  135. {
  136. Draw(hwnd,"第2点已锁定!");
  137. }
  138. return 0;
  139. case WM_DESTROY :
  140. PostQuitMessage (0) ;
  141. return 0 ;
  142. }
  143. return DefWindowProc(hwnd, message, wParam, lParam) ;
  144. }

但是问题还存在,就是手动找圆心太麻烦,一般用触摸板比鼠标方便,但是仍然很不智能,于是一直想着用图像处理的方法去自动找圆心。

这几天在做数模,经常用到Matlab,刚做了一道要处理图像的题,正好想起这个问题来,于是,搁置的瞄准器继续开始完善了。

很快就有了思路,通过C#截图,然后Matlab进行图像滤波,灰度化,二值化以及边缘提取,然后进行圆曲线拟合,最后找到圆心,返回到C#中使用,代替手动找点。

首先,我用Matlab写了个函数:

  1. function [x,y]=findcenter()
  2. %close all,clear,clc
  3. format short
  4. a=imread('E:\360data\重要数据\桌面\test.bmp');
  5. b=rgb2gray(a);%转化为灰度图像
  6. %figure;imshow(b)
  7. b=filter2(fspecial('average',1),b)/255;
  8. %b=medfilt2(b);%中值滤波
  9. level=graythresh(b);%自动获取灰度图片的阈值
  10. c=im2bw(b,level);%二值化
  11. %figure;imshow(c)
  12. bw=edge(c,'canny');
  13. bw1=~bw;%取反,黑变白,白变黑
  14. %figure;imshow(bw1)
  15. %figure;imshow(bw1)
  16. [yf,xf]=find(bw1==0);
  17. xmin=min(xf);
  18. xmax=max(xf);
  19. ymin=min(yf);
  20. ymax=max(yf);
  21. %cirPx=[xmin;(xmax-xmin)/2;(xmax-xmin)/2;xmax]
  22. %cirPy=[(ymax-ymin)/2;ymin;ymax;(ymax-ymin)/2]
  23. %fitellipse(cirPx,cirPy)
  24. centerX=(xmax+xmin)/2;
  25. centerY=(ymax+ymin)/2;
  26. ra=(ymax-ymin)/2;
  27. x=centerX;y=centerY;
  28. %hold on
  29. %x=0:size(bw1,2);
  30. %degree=[0:0.01:pi*2];
  31. %degree=[0:0.01:pi*2];
  32. %plot(ra*cos(degree)+centerX,ra*sin(degree)+centerY,'r-');
  33. %plot(centerX,centerY,'r+');

然后用Matlab2010b里的deploytool导出.net能使用的程序集dll文件(不知道为什么malab2009b在build时出现.net framework相关的错误),通过C#添加引用,调用其返回的参数,成功完成自动拾取圆心。

用C#调用Matlab图像处理自制QQ游戏2D桌球瞄准器

用C#调用Matlab图像处理自制QQ游戏2D桌球瞄准器

改进后代码如下:

  1. using System.Drawing;
  2. using System.Windows.Forms;
  3. using System.Windows;
  4. using System.Runtime.InteropServices;
  5. using System;
  6. using MathWorks.MATLAB.NET.Arrays;
  7. using MathWorks.MATLAB.NET.Utility;
  8. using findcenter;
  9. namespace TaiqiuGua
  10. {
  11. public partial class Form1 : Form
  12. {
  13. const int ra=25;
  14. [DllImport("user32.dll")]
  15. static extern IntPtr GetDesktopWindow();
  16. [DllImport("user32.dll")]
  17. static extern IntPtr GetWindowDC(IntPtr hWnd);
  18. [DllImport("user32.dll")]
  19. static extern bool SetCursorPos(int X, int Y);
  20. public Form1()
  21. {
  22. InitializeComponent();
  23. }
  24. Point startP,startP1;
  25. Point endP,endP1;
  26. private void Form1_KeyDown(object sender, KeyEventArgs e)
  27. {
  28. switch(e.KeyData)
  29. {
  30. case Keys.F1:
  31. startP=Control.MousePosition;
  32. break;
  33. case Keys.F5:
  34. endP = Control.MousePosition;
  35. break;
  36. case Keys.F2:
  37. startP1 = Control.MousePosition;
  38. break;
  39. case Keys.F3:
  40. endP1 = Control.MousePosition;
  41. break;
  42. case Keys.D1:
  43. int x1 = (int)(endP.X + ra * ((endP.X - startP.X) / Math.Sqrt((endP.X - startP.X) * (endP.X - startP.X) + (endP.Y - startP.Y) * (endP.Y - startP.Y))));
  44. int y1 = (int)(endP.Y + ra * ((endP.Y - startP.Y) / Math.Sqrt((endP.X - startP.X) * (endP.X - startP.X) + (endP.Y - startP.Y) * (endP.Y - startP.Y))));
  45. SetCursorPos(x1, y1);
  46. break;
  47. case Keys.D2:
  48. int x2 = (int)(endP.X - ra * ((-endP.X + startP.X) / Math.Sqrt((-endP.X + startP.X) * (-endP.X + startP.X) + (endP.Y - startP.Y) * (endP.Y - startP.Y))));
  49. int y2 = (int)(endP.Y + ra * ((endP.Y - startP.Y) / Math.Sqrt((-endP.X + startP.X) * (-endP.X + startP.X) + (endP.Y - startP.Y) * (endP.Y - startP.Y))));
  50. SetCursorPos(x2, y2);
  51. break;
  52. case Keys.D3:
  53. int x3 = (int)(endP.X + ra * ((endP.X - startP.X) / Math.Sqrt((endP.X - startP.X) * (endP.X - startP.X) + (-endP.Y + startP.Y) * (-endP.Y + startP.Y))));
  54. int y3 = (int)(endP.Y - ra * ((-endP.Y + startP.Y) / Math.Sqrt((endP.X - startP.X) * (endP.X - startP.X) + (-endP.Y + startP.Y) * (-endP.Y + startP.Y))));
  55. SetCursorPos(x3, y3);
  56. break;
  57. case Keys.D4:
  58. int x4 = (int)(endP.X - ra * ((-endP.X + startP.X) / Math.Sqrt((-endP.X + startP.X) * (-endP.X + startP.X) + (-endP.Y + startP.Y) * (-endP.Y + startP.Y))));
  59. int y4 = (int)(endP.Y - ra * ((-endP.Y + startP.Y) / Math.Sqrt((-endP.X + startP.X) * (-endP.X + startP.X) + (-endP.Y + startP.Y) * (-endP.Y + startP.Y))));
  60. SetCursorPos(x4, y4);
  61. break;
  62. case Keys.F4:
  63. //Graphics g1 = pictureBox1.CreateGraphics();
  64. //g1.CopyFromScreen(startP1.X,startP1.Y,0,0,new Size(endP1.X-startP1.X,endP1.Y-startP1.Y));
  65. int w=endP1.X - startP1.X;
  66. int h=endP1.Y - startP1.Y;
  67. Bitmap bmSave = new Bitmap(w,h);
  68. Graphics g=Graphics.FromImage(bmSave);
  69. g.CopyFromScreen(startP1.X,startP1.Y,0,0,new Size(w,h),CopyPixelOperation.SourceCopy);
  70. bmSave.Save(@"E:\360data\重要数据\桌面\test.bmp");
  71. g.Dispose();
  72. //g1.Dispose();
  73. bmSave.Dispose();
  74. findcenter.Class1 f = new findcenter.Class1();
  75. MWArray centerx = f.findcenter();
  76. MWArray centery = f.findy();
  77. double[,] x = (double[,])centerx.ToArray();
  78. double[,] y = (double[,])centery.ToArray();
  79. SetCursorPos((int)(x[0, 0] + startP1.X), (int)(y[0, 0] + startP1.Y));
  80. //int [] d=center.Dimensions;
  81. //int x=int.Parse((center[1, 1]).ToString());
  82. //int y= int.Parse((center[1, 2]).ToString());
  83. //MessageBox.Show((y[0,0]).ToString());
  84. f.Dispose();
  85. break;
  86. }
  87. GC.Collect();
  88. }
  89. private void Form1_Activated(object sender, EventArgs e)
  90. {
  91. //this.BackColor = Color.Red;
  92. }
  93. private void Form1_Deactivate(object sender, EventArgs e)
  94. {
  95. //this.BackColor = Color.Blue;
  96. }
  97. }
  98. }

经试验,成功率也很高,偶尔出现不准(估计是边缘提取和计算精度的问题),但是大多数偏差可以手动修正。

到此为止,改进版全部完成。希望以后继续改进,更加智能化。C# 真是码农的利器啊!