本文旨在自己动手实现一个类似于“按键精灵”的桌面软件。第一部分介绍了简单的模拟方式,但是有些软件能够屏蔽掉这种简单模拟带来的效果,因此第二部分将介绍如何从驱动级层面进行模拟。
“游戏外挂一般分为三个级别:初级是鼠标、键盘模拟,中级是Call游戏内部函数,读写内存,高级是抓包,封包的“脱机挂”(完全模拟客户端网络数据,不用运行游戏)。用C#写外挂的不是很多,大部分是C++,主要原因是MS的C#目前不支持内联汇编功能。因此用C++写底层库,然后用C#调用成为DONET爱好者开发外挂的首选。”——某开发者言
简单的模拟方式
模拟鼠标操作
Windows API介绍
.NET没有提供改变鼠标指针位置、模拟点击操作的函数,但是可以通过调用Windows API函数实现。
[DllImport(“user32.dll”)]
static extern bool SetCursorPos(int X,int Y);
该函数用于设置鼠标的位置,其中X和Y是相对于屏幕左上角的绝对位置.
[DllImport(“user32.dll”)]
static extern void mouse_event(MouseEventFlag flags,int dx,int dy,uint data,UIntPtr extraInfo);
该函数不仅可以设置鼠标指针绝对位置,而且可以以相对坐标来设置位置.
其中flags标志位集,指定点击按钮和鼠标动作的多种情况.dx指鼠标沿x轴绝对位置或上次鼠标事件位置产生以来移动的数量.dy指沿y轴的绝对位置或从上次鼠标事件以来移动的数量.data如果flags为MOUSE_WHEEL则该值指鼠标轮移动的数量(否则为0),正值向前转动.extraInfo指定与鼠标事件相关的附加32位值.
[DllImport(“user32.dll”)]
static extern IntPtr FindWindow(string strClass, string strWindow);
该函数根据类名和窗口名来得到窗口句柄,但是这个函数不能查找子窗口,也不区分大小写.如果要从一个窗口的子窗口查找需要使用FIndWindowEX函数.
[DllImport(“user32.dll”)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string strClass, string strWindow);
该函数获取一个窗口的句柄,该窗口的类名和窗口名与给定的字符串相匹配,该函数查找子窗口时从排在给定的子窗口后面的下一个子窗口开始。其中参数hwnParent为要查找子窗口的父窗口句柄,若该值为NULL则函数以桌面窗口为父窗口,查找桌面窗口的所有子窗口。 hwndChildAfter子窗口句柄,查找从在Z序中的下一个子窗口开始,子窗口必须为hwnParent直接子窗口而非后代窗口,若hwnChildAfter为NULL,查找从父窗口的第一个子窗口开始。 strClass指向一个指定类名的空结束字符串或一个标识类名字符串的成员的指针。 strWindow指向一个指定窗口名(窗口标题)的空结束字符串.若为NULL则所有窗体全匹配。返回值:如果函数成功,返回值为具有指定类名和窗口名的窗口句柄,如果函数失败,返回值为NULL。
1-引入命名空间using System.Runtime.InteropServices; 因为要使用user32.dll中的接口。
2-创建一个新类MouseFlag
3-声明函数
– 此处的位置都是屏幕绝对位置
– 设置鼠标位置:public static extern int SetCursorPos(int x, int y);
– 鼠标事件:static extern void mouse_event(MouseEventFlag flags, int dx, int dy, uint data, UIntPtr extraInfo);虽然这个已经弃用了,但是上手简单。这里需要一个类型:MouseEventFlag
4-创建MouseEventFlag :enum MouseEventFlag : uint
5-用类包起来
(一个控制台程序)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace Mouse
class Program
public class MouseFlag
enum MouseEventFlag : uint
Move = 0x0001,
LeftDown = 0x0002,
LeftUp = 0x0004,
RightDown = 0x0008,
RightUp = 0x0010,
MiddleDown = 0x0020,
MiddleUp = 0x0040,
XDown = 0x0080,
XUp = 0x0100,
Wheel = 0x0800,
VirtualDesk = 0x4000,
Absolute = 0x8000
[DllImport(“user32.dll”)]
static extern void mouse_event(MouseEventFlag flags, int dx, int dy, uint data, UIntPtr extraInfo);
[DllImport(“user32.dll”)]
public static extern int SetCursorPos(int x, int y);
public static void MouseLeftClickEvent(int dx, int dy, uint data)
SetCursorPos(dx, dy);
System.Threading.Thread.Sleep(2 * 1000);
mouse_event(MouseEventFlag.LeftDown, dx, dy, data, UIntPtr.Zero);
mouse_event(MouseEventFlag.LeftUp, dx, dy, data, UIntPtr.Zero);
public static void MouseRightClickEvent(int dx, int dy, uint data)
SetCursorPos(dx, dy);
System.Threading.Thread.Sleep(2 * 1000);
mouse_event(MouseEventFlag.RightDown, dx, dy, data, UIntPtr.Zero);
mouse_event(MouseEventFlag.RightUp, dx, dy, data, UIntPtr.Zero);
static void Main(string[] args)
System.Threading.Thread.Sleep(6 * 1000);
MouseFlag.MouseLeftClickEvent(10, 1000, 0);
模拟键盘操作
Windows API介绍
/// <param name=”bVk” >按键的虚拟键值</param>
/// <param name= “bScan” >扫描码,一般不用设置,用0代替就行</param>
/// <param name= “dwFlags” >选项标志:0:表示按下,2:表示松开</param>
/// <param name= “dwExtraInfo”>一般设置为0</param>
[DllImport(“user32.dll”)]
public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
可类比“模拟鼠标操作”的实现步骤,直接学习以下实例吧。
(一个控制台程序)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace VKB
class Program
public class KeyBoard
public const byte vKeyLButton = 0x1; // 鼠标左键
public const byte vKeyRButton = 0x2; // 鼠标右键
public const byte vKeyCancel = 0x3; // CANCEL 键
public const byte vKeyMButton = 0x4; // 鼠标中键
public const byte vKeyBack = 0x8; // BACKSPACE 键
public const byte vKeyTab = 0x9; // TAB 键
public const byte vKeyClear = 0xC; // CLEAR 键
public const byte vKeyReturn = 0xD; // ENTER 键
public const byte vKeyShift = 0x10; // SHIFT 键
public const byte vKeyControl = 0x11; // CTRL 键
public const byte vKeyAlt = 18; // Alt 键 (键码18)
public const byte vKeyMenu = 0x12; // MENU 键
public const byte vKeyPause = 0x13; // PAUSE 键
public const byte vKeyCapital = 0x14; // CAPS LOCK 键
public const byte vKeyEscape = 0x1B; // ESC 键
public const byte vKeySpace = 0x20; // SPACEBAR 键
public const byte vKeyPageUp = 0x21; // PAGE UP 键
public const byte vKeyEnd = 0x23; // End 键
public const byte vKeyHome = 0x24; // HOME 键
public const byte vKeyLeft = 0x25; // LEFT ARROW 键
public const byte vKeyUp = 0x26; // UP ARROW 键
public const byte vKeyRight = 0x27; // RIGHT ARROW 键
public const byte vKeyDown = 0x28; // DOWN ARROW 键
public const byte vKeySelect = 0x29; // Select 键
public const byte vKeyPrint = 0x2A; // PRINT SCREEN 键
public const byte vKeyExecute = 0x2B; // EXECUTE 键
public const byte vKeySnapshot = 0x2C; // SNAPSHOT 键
public const byte vKeyDelete = 0x2E; // Delete 键
public const byte vKeyHelp = 0x2F; // HELP 键
public const byte vKeyNumlock = 0x90; // NUM LOCK 键
//常用键 字母键A到Z
public const byte vKeyA = 65;
public const byte vKeyB = 66;
public const byte vKeyC = 67;
public const byte vKeyD = 68;
public const byte vKeyE = 69;
public const byte vKeyF = 70;
public const byte vKeyG = 71;
public const byte vKeyH = 72;
public const byte vKeyI = 73;
public const byte vKeyJ = 74;
public const byte vKeyK = 75;
public const byte vKeyL = 76;
public const byte vKeyM = 77;
public const byte vKeyN = 78;
public const byte vKeyO = 79;
public const byte vKeyP = 80;
public const byte vKeyQ = 81;
public const byte vKeyR = 82;
public const byte vKeyS = 83;
public const byte vKeyT = 84;
public const byte vKeyU = 85;
public const byte vKeyV = 86;
public const byte vKeyW = 87;
public const byte vKeyX = 88;
public const byte vKeyY = 89;
public const byte vKeyZ = 90;
//数字键盘0到9
public const byte vKey0 = 48; // 0 键
public const byte vKey1 = 49; // 1 键
public const byte vKey2 = 50; // 2 键
public const byte vKey3 = 51; // 3 键
public const byte vKey4 = 52; // 4 键
public const byte vKey5 = 53; // 5 键
public const byte vKey6 = 54; // 6 键
public const byte vKey7 = 55; // 7 键
public const byte vKey8 = 56; // 8 键
public const byte vKey9 = 57; // 9 键
public const byte vKeyNumpad0 = 0x60; //0 键
public const byte vKeyNumpad1 = 0x61; //1 键
public const byte vKeyNumpad2 = 0x62; //2 键
public const byte vKeyNumpad3 = 0x63; //3 键
public const byte vKeyNumpad4 = 0x64; //4 键
public const byte vKeyNumpad5 = 0x65; //5 键
public const byte vKeyNumpad6 = 0x66; //6 键
public const byte vKeyNumpad7 = 0x67; //7 键
public const byte vKeyNumpad8 = 0x68; //8 键
public const byte vKeyNumpad9 = 0x69; //9 键
public const byte vKeyMultiply = 0x6A; // MULTIPLICATIONSIGN(*)键
public const byte vKeyAdd = 0x6B; // PLUS SIGN(+) 键
public const byte vKeySeparator = 0x6C; // ENTER 键
public const byte vKeySubtract = 0x6D; // MINUS SIGN(-) 键
public const byte vKeyDecimal = 0x6E; // DECIMAL POINT(.) 键
public const byte vKeyDivide = 0x6F; // DIVISION SIGN(/) 键
//F1到F12按键
public const byte vKeyF1 = 0x70; //F1 键
public const byte vKeyF2 = 0x71; //F2 键
public const byte vKeyF3 = 0x72; //F3 键
public const byte vKeyF4 = 0x73; //F4 键
public const byte vKeyF5 = 0x74; //F5 键
public const byte vKeyF6 = 0x75; //F6 键
public const byte vKeyF7 = 0x76; //F7 键
public const byte vKeyF8 = 0x77; //F8 键
public const byte vKeyF9 = 0x78; //F9 键
public const byte vKeyF10 = 0x79; //F10 键
public const byte vKeyF11 = 0x7A; //F11 键
public const byte vKeyF12 = 0x7B; //F12 键
[DllImport(“user32.dll”)]
public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
public static void keyPress(byte keyName)
KeyBoard.keybd_event(keyName, 0, 0, 0);
KeyBoard.keybd_event(keyName, 0, 2, 0);
static void Main(string[] args)
System.Threading.Thread.Sleep(6 * 1000);
///MouseFlag.MouseLeftClickEvent(350, 70, 0);
///System.Threading.Thread.Sleep(5*1000);
///MouseFlag.MouseLeftClickEvent(20, 30, 0);
///System.Threading.Thread.Sleep(5 * 1000);
KeyBoard.keyPress(KeyBoard.vKeyBack);
System.Threading.Thread.Sleep(5 * 1000);
驱动级模拟
有时候,按照上面说的方式进行模拟会失败,桌面软件尤其是一些游戏,对于消息命令一点也不“服从”,我们有这样的疑问:它是如何识别真实键盘的按键的呢?难道是程序中有判断吗?在DirectX编程中有个叫DirectInput的API,就是它绕过了Windows的消息机制,它的目的是为了让软件(游戏)的实时性控制更好、更快。Windows消息是队列形式的,在传递过程中会有延时,比如即时对战类游戏对实时性控制要求是非常高的,Window消息机制不能满足这个需求。而DirectInput直接和键盘驱动程序打交道,效率当然要高出一大截。
那么怎么解决这个问题呢,有人发现“按键精灵”使用了WINIO驱动的原理。于是我们也可以通过WINIO实现这样的模拟。
注意要使用winio.dll这个动态库,是需要一些条件的:
1- WinIO函数库只允许被具有管理者权限的应用程序调用。如果使用者不是以管理者的身份进入的,则WinIO.DLL不能够被安装,也不能激活WinIO驱动器。通过在管理者权限下安装驱动器软件就可以克服这种限制。然而,在这种情况下,ShutdownWinIo函数不能在应用程序结束之前被调用,因为该函数将WinIO驱动程序从系统注册表中删除。
2- 64位操作系统下需要将WinIo.sys驱动文件添加数字签名。
3- 注意WinIo.sys和WinIo.dll这两个文件的存放路径,如果路径不对,会导致初始化失败。
可能遇到各种麻烦,请耐心解决。
(本实例摘自:点击打开链接)
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace lizheAionWG
public class WinIo
public const int KBC_KEY_CMD = 0x64;
public const int KBC_KEY_DATA = 0x60;
[DllImport(“winio.dll”)]
public static extern bool InitializeWinIo();
[DllImport(“winio.dll”)]
public static extern bool GetPortVal(IntPtr wPortAddr, out int pdwPortVal, byte bSize);
[DllImport(“winio.dll”)]
public static extern bool SetPortVal(uint wPortAddr, IntPtr dwPortVal, byte bSize);
[DllImport(“winio.dll”)]
public static extern byte MapPhysToLin(byte pbPhysAddr, uint dwPhysSize, IntPtr PhysicalMemoryHandle);
[DllImport(“winio.dll”)]
public static extern bool UnmapPhysicalMemory(IntPtr PhysicalMemoryHandle, byte pbLinAddr);
[DllImport(“winio.dll”)]
public static extern bool GetPhysLong(IntPtr pbPhysAddr, byte pdwPhysVal);
[DllImport(“winio.dll”)]
public static extern bool SetPhysLong(IntPtr pbPhysAddr, byte dwPhysVal);
[DllImport(“winio.dll”)]
public static extern void ShutdownWinIo();
[DllImport(“user32.dll”)]
public static extern int MapVirtualKey(uint Ucode, uint uMapType);
public void sendwinio()
if (InitializeWinIo())
KBCWait4IBE();
///Wait for Buffer gets empty
private void KBCWait4IBE()
int dwVal = 0;
bool flag = GetPortVal((IntPtr)0x64, out dwVal, 1);
while ((dwVal & 0x2) > 0);
/// key down
public void MykeyDown(int vKeyCoad)
int btScancode = 0;
btScancode = MapVirtualKey((byte)vKeyCoad, 0);
KBCWait4IBE();
SetPortVal(KBC_KEY_CMD, (IntPtr)0xD2, 1);
KBCWait4IBE();
SetPortVal(KBC_KEY_DATA, (IntPtr)0xe2, 1);
KBCWait4IBE();
SetPortVal(KBC_KEY_CMD, (IntPtr)0xD2, 1);
KBCWait4IBE();
SetPortVal(KBC_KEY_DATA, (IntPtr)btScancode, 1);
/// Key up
public void MykeyUp(int vKeyCoad)
int btScancode = 0;
btScancode = MapVirtualKey((byte)vKeyCoad, 0);
KBCWait4IBE();
SetPortVal(KBC_KEY_CMD, (IntPtr)0xD2, 1);
KBCWait4IBE();
SetPortVal(KBC_KEY_DATA, (IntPtr)0xe0, 1);
KBCWait4IBE();
SetPortVal(KBC_KEY_CMD, (IntPtr)0xD2, 1);
KBCWait4IBE();
SetPortVal(KBC_KEY_DATA, (IntPtr)btScancode, 1);
/// Simulate mouse down
public void MyMouseDown(int vKeyCoad)
int btScancode = 0;
btScancode = MapVirtualKey((byte)vKeyCoad, 0);
KBCWait4IBE(); // ‘wait for buffer gets empty
SetPortVal(KBC_KEY_CMD, (IntPtr)0xD3, 1);// ‘send write command
KBCWait4IBE();
SetPortVal(KBC_KEY_DATA, (IntPtr)(btScancode | 0x80), 1);// ‘write in io
/// Simulate mouse up
public void MyMouseUp(int vKeyCoad)
int btScancode = 0;
btScancode = MapVirtualKey((byte)vKeyCoad, 0);
KBCWait4IBE(); // ‘wait for buffer gets empty
SetPortVal(KBC_KEY_CMD, (IntPtr)0xD3, 1); //’send write command
KBCWait4IBE();
SetPortVal(KBC_KEY_DATA, (IntPtr)(btScancode | 0x80), 1);// ‘write in io
//———————————————————————————-
//VK codes
//———————————————————————————-
public enum Key
// mouse movements
move = 0x0001,
leftdown = 0x0002,
leftup = 0x0004,
rightdown = 0x0008,
rightup = 0x0010,
middledown = 0x0020,
//keyboard stuff
VK_LBUTTON = 1,
VK_RBUTTON = 2,
VK_CANCEL = 3,
VK_MBUTTON = 4,
VK_BACK = 8,
VK_TAB = 9,
VK_CLEAR = 12,
VK_RETURN = 13,
VK_SHIFT = 16,
VK_CONTROL = 17,
VK_MENU = 18,
VK_PAUSE = 19,
VK_CAPITAL = 20,
VK_ESCAPE = 27,
VK_SPACE = 32,
VK_PRIOR = 33,
VK_NEXT = 34,
VK_END = 35,
VK_HOME = 36,
VK_LEFT = 37,
VK_UP = 38,
VK_RIGHT = 39,
VK_DOWN = 40,
VK_SELECT = 41,
VK_PRINT = 42,
VK_EXECUTE = 43,
VK_SNAPSHOT = 44,
VK_INSERT = 45,
VK_DELETE = 46,
VK_HELP = 47,
VK_NUM0 = 48, //0
VK_NUM1 = 49, //1
VK_NUM2 = 50, //2
VK_NUM3 = 51, //3
VK_NUM4 = 52, //4
VK_NUM5 = 53, //5
VK_NUM6 = 54, //6
VK_NUM7 = 55, //7
VK_NUM8 = 56, //8
VK_NUM9 = 57, //9
VK_A = 65, //A
VK_B = 66, //B
VK_C = 67, //C
VK_D = 68, //D
VK_E = 69, //E
VK_F = 70, //F
VK_G = 71, //G
VK_H = 72, //H
VK_I = 73, //I
VK_J = 74, //J
VK_K = 75, //K
VK_L = 76, //L
VK_M = 77, //M
VK_N = 78, //N
VK_O = 79, //O
VK_P = 80, //P
VK_Q = 81, //Q
VK_R = 82, //R
VK_S = 83, //S
VK_T = 84, //T
VK_U = 85, //U
VK_V = 86, //V
VK_W = 87, //W
VK_X = 88, //X
VK_Y = 89, //Y
VK_Z = 90, //Z
VK_NUMPAD0 = 96, //0
VK_NUMPAD1 = 97, //1
VK_NUMPAD2 = 98, //2
VK_NUMPAD3 = 99, //3
VK_NUMPAD4 = 100, //4
VK_NUMPAD5 = 101, //5
VK_NUMPAD6 = 102, //6
VK_NUMPAD7 = 103, //7
VK_NUMPAD8 = 104, //8
VK_NUMPAD9 = 105, //9
VK_NULTIPLY = 106,
VK_ADD = 107,
VK_SEPARATOR = 108,
VK_SUBTRACT = 109,
VK_DECIMAL = 110,
VK_DIVIDE = 111,
VK_F1 = 112,
VK_F2 = 113,
VK_F3 = 114,
VK_F4 = 115,
VK_F5 = 116,
VK_F6 = 117,
VK_F7 = 118,
VK_F8 = 119,
VK_F9 = 120,
VK_F10 = 121,
VK_F11 = 122,
VK_F12 = 123,
VK_NUMLOCK = 144,
VK_SCROLL = 145,
middleup = 0x0040,
xdown = 0x0080,
xup = 0x0100,
wheel = 0x0800,
virtualdesk = 0x4000,
absolute = 0x8000
某开发者自己封装了一个dll动态库,可参考这篇博文: 点击打开链接。
实际上,github上有许多类似的东西,可以找找合适的代码。
————————————————
版权声明:本文为CSDN博主「FarmerJohn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/fjsd155/article/details/79495242
发布者:
李, 思
2021年9月6日
发布于
C#
import cv2
def image_comparison(templatePath, sourcePath, targetPath='', defaultAccurate=0.7, defaultMethod="cv2.TM_CCOEFF_NORMED"):
result = {"match": False, "maxVal": 0, "maxLocX": 0, "maxLocY": 0, "scaleX": 0, "scaleY": 0}
img_rgb = cv2.imread(sourcePath)
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
img_template = cv2.imread(templatePath, 0)
w, h = img_template.shape[::-1]
method = eval(defaultMethod)
res = cv2.matchTemplate(img_gray, img_template, method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
if targetPath != '':
cv2.rectangle(img_rgb, top_left, bottom_right, (0, 0, 255), 4)
cv2.imwrite(targetPath, img_rgb)
result["match"] = max_val >= defaultAccurate
result["maxVal"] = max_val
result["maxLocX"] = max_loc[0] + w / float(2)
result["maxLocY"] = max_loc[1] + h / float(2)
return result
result = image_comparison('f:/12.png', 'f:/3.png')
print(result)
print(result["maxVal"])
{'match': True, 'maxVal': 0.9384326934814453, 'maxLocX': 124.5, 'maxLocY': 256.0, 'scaleX': 0, 'scaleY': 0}
0.9384326934814453
下述两张图片对比
else:
width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)
height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN)
left = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)
top = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)
hwindc = win32gui.GetWindowDC(hwin)
srcdc = win32ui.CreateDCFromHandle(hwindc)
memdc = srcdc.CreateCompatibleDC()
bmp = win32ui.CreateBitmap()
bmp.CreateCompatibleBitmap(srcdc, width, height)
memdc.SelectObject(bmp)
memdc.BitBlt((0, 0), (width, height), srcdc, (left, top), win32con.SRCCOPY)
signedIntsArray = bmp.GetBitmapBits(True)
img = np.fromstring(signedIntsArray, dtype='uint8')
img.shape = (height,width,4)
srcdc.DeleteDC()
memdc.DeleteDC()
win32gui.ReleaseDC(hwin, hwindc)
win32gui.DeleteObject(bmp.GetHandle())
return img
---------------------------------------------------
import cv2
import numpy as np
from grabscreen import grab_screen
# 这里是获取PC端屏幕
img_rgb = grab_screen()
# Convert it to grayscale
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
# 这里的2.png是我要查找的目标
template = cv2.imread('f:/2.png',0)
# Store width and heigth of template in w and h
w, h = template.shape[::-1]
# Perform match operations.
res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
# Specify a threshold
# 这里0.9是匹配度
threshold = 0.9
# Store the coordinates of matched area in a numpy array
loc = np.where(res >= threshold)
x=loc[0]
y=loc[1]
# Draw a rectangle around the matched region.
if len(x) and len(y):
for pt in zip(*loc[::-1]):
# 这里画矩形框并输出图片,也可以改为自动化的点击等操作
cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,255,255), 2)
cv2.imwrite("f:/13.png", img_rgb)
print('I found the watermark')
break
else:
print('there is no watermark')
2.png 13.png
img_rgb = cv2.imread('f:/3.png')
# Convert it to grayscale
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
# 这个1.png是小图,是图中的目标
template = cv2.imread('f:/1.png',0)
# Store width and heigth of template in w and h
w, h = template.shape[::-1]
# Perform match operations.
res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
# Specify a threshold
# 这里的0.7表示匹配度
threshold = 0.7
# Store the coordinates of matched area in a numpy array
loc = np.where(res >= threshold)
x=loc[0]
y=loc[1]
# Draw a rectangle around the matched region.
if len(x) and len(y):
for pt in zip(*loc[::-1]):
# 这里会把匹配到的位置用矩形框给框选出来
cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,255,255), 2)
cv2.imwrite("f:/12.png", img_rgb)
print('I found the watermark')
else:
print('there is no watermark')
1.png 3.png 12.png
执行以下命令,为 FTP 服务创建一个 Linux 用户,本文以 ftpuser 为例。
useradd ftpuser
执行以下命令,设置 ftpuser 用户的密码。
passwd ftpuser
输入密码后请按
Enter
确认设置,密码默认不显示,本文以
tf7295TFY
为例。
执行以下命令,创建 FTP 服务使用的文件目录,本文以
/var/ftp/test
为例。
mkdir /var/ftp/test
执行以下命令,修改目录权限。
chown -R ftpuser:ftpuser /var/ftp/test
执行以下命令,打开
vsftpd.conf
文件。
vim /etc/vsftpd/vsftpd.conf
按
i
切换至编辑模式,根据实际需求选择 FTP 模式,修改配置文件
vsftpd.conf
:注意:FTP 可通过主动模式和被动模式与客户端机器进行连接并传输数据。由于大多数客户端机器的防火墙设置及无法获取真实 IP 等原因,建议您选择
被动模式
搭建 FTP 服务。如下修改以设置被动模式为例,您如需选择主动模式,请前往
设置 FTP 主动模式
。
修改以下配置参数,设置匿名用户和本地用户的登录权限,设置指定例外用户列表文件的路径,并开启监听 IPv4 sockets。
anonymous_enable=NO local_enable=YES chroot_local_user=YES chroot_list_enable=YES chroot_list_file=/etc/vsftpd/chroot_list listen=YES
在行首添加
#
,注释
listen_ipv6=YES
配置参数,关闭监听 IPv6 sockets。
#listen_ipv6=YES
添加以下配置参数,开启被动模式,设置本地用户登录后所在目录,以及云服务器建立数据传输可使用的端口范围值。
local_root=/var/ftp/test allow_writeable_chroot=YES pasv_enable=YES pasv_address=xxx.xx.xxx.xx #请修改为您的 Linux 云服务器公网 IP pasv_min_port=40000 pasv_max_port=45000
按
Esc
后输入
:wq
保存后退出。
执行以下命令,创建并编辑
chroot_list
文件。
vim /etc/vsftpd/chroot_list
按
i
进入编辑模式,输入用户名,一个用户名占据一行,设置完成后按
Esc
并输入
:wq
保存后退出。
您若没有设置例外用户的需求,可跳过此步骤,输入
:wq
退出文件。
执行以下命令,重启 FTP 服务。
systemctl restart vsftpd
步骤4:设置安全组
搭建好 FTP 服务后,您需要根据实际使用的 FTP 模式给 Linux 云服务器放通
入站规则
,详情请参见
添加安全组规则
。
大多数客户端机器在局域网中,IP 地址是经过转换的。如果您选择了 FTP 主动模式,请确保客户端机器已获取真实的 IP 地址,否则可能会导致客户端无法登录 FTP 服务器。
主动模式:放通端口21。
被动模式:放通端口21,及
修改配置文件
中设置的
pasv_min_port
到
pasv_max_port
之间的所有端口,本文放通端口为40000 – 45000。
步骤5:验证 FTP 服务
您可通过 FTP 客户端软件、浏览器或文件资源管理器等工具验证 FTP 服务,本文以客户端的文件资源管理器为例。
打开客户端的 IE 浏览器,选择【工具】>【Internet 选项】>【高级】,根据您选择的 FTP 模式进行修改:
主动模式:取消勾选【使用被动 FTP】。
被动模式:勾选【使用被动 FTP】。
打开客户端的计算机,在路径栏中访问以下地址。如下图所示:
ftp://云服务器公网IP:21
在弹出的“登录身份”窗口中输入
配置 vsftpd
中已设置的用户名及密码。
本文使用的用户名为
ftpuser
,密码为
tf7295TFY
。
成功登录后,即可上传及下载文件。
设置 FTP 主动模式
主动模式需修改的配置如下,其余配置保持默认设置:
anonymous_enable=NO #禁止匿名用户登录
local_enable=YES #支持本地用户登录
chroot_local_user=YES #全部用户被限制在主目录
chroot_list_enable=YES #启用例外用户名单
chroot_list_file=/etc/vsftpd/chroot_list #指定用户列表文件,该列表中的用户不被锁定在主目录
listen=YES #监听IPv4 sockets
#在行首添加#注释掉以下参数
#listen_ipv6=YES #关闭监听IPv6 sockets
#添加下列参数
allow_writeable_chroot=YES
local_root=/var/ftp/test #设置本地用户登录后所在的目录
按
Esc
后输入
:wq
保存后退出,并前往
步骤8
完成 vsftpd 配置。
FTP 客户端上传文件失败
Linux 系统环境下,通过 vsftp 上传文件时,提示如下报错信息。
553 Could not create file
执行以下命令,检查服务器磁盘空间的使用率。
df -h
如果磁盘空间不足,将会导致文件无法上传,建议删除磁盘容量较大的文件。
如果磁盘空间正常,请执行下一步。
执行以下命令,检查 FTP 目录是否有写的权限。
ls -l /home/test # /home/test 为 FTP 目录,请修改为您实际的 FTP 目录。
执行以下命令,对 FTP 目录加上写的权限。
chmod +w /home/test # /home/test 为 FTP 目录,请修改为您实际的 FTP 目录。
执行以下命令,重新检查写的权限是否设置成功。
ls -l /home/test # /home/test 为 FTP 目录,请修改为您实际的 FTP 目录。
发布者:
李, 思
2021年9月2日
2021年9月5日
发布于
SERVER