为何这么做:想用C++实现已知模板的简单图色对比,以用于自动化。
不用C++调用pyautogui ,并且需要安装python环境,无法独立运行。
问题: 下列我给出的代码 可以识别匹配,但是阈值有点低。我用相同函数截取出的两张图,
并且对比图 基本是从模板图上 选取的一部分,但是识别不出。
需求:通过改进算法或者优化代码 以提高识别的灵敏度,在高阈值下仍可准确识别。
或者有一个方便快捷且无需python环境调用pyautogui的方法也可以
截图代码如下:
#include <windows.h>
#include <chrono>
#include <thread>
#include <string>
#include <cwchar>
#include <iostream>
#include <cassert> // 引入assert宏
HDC g_hCompatibleDC = nullptr;
HBITMAP g_hBitmap = nullptr;
// 保存bmp图片
BOOL SaveBitmap(HBITMAP hBitmap, LPCWSTR filename)
{
HDC hDC;
int iBits;
WORD wBitCount;
DWORD dwPaletteSize = 0, dwBmBitsSize = 0, dwDIBSize = 0, dwWritten = 0;
BITMAP Bitmap;
BITMAPFILEHEADER bmfHdr;
BITMAPINFOHEADER bi;
LPBITMAPINFOHEADER lpbi;
HANDLE fh, hDib, hPal, hOldPal = NULL;
// 获取位图信息
hDC = CreateDC(L"DISPLAY", NULL, NULL, NULL);
iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES);
DeleteDC(hDC);
if (iBits <= 1)
wBitCount = 1;
else if (iBits <= 4)
wBitCount = 4;
else if (iBits <= 8)
wBitCount = 8;
else
wBitCount = 24;
GetObject(hBitmap, sizeof(Bitmap), (LPSTR)&Bitmap);
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = Bitmap.bmWidth;
bi.biHeight = Bitmap.bmHeight;
bi.biPlanes = 1;
bi.biBitCount = wBitCount;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrImportant = 0;
bi.biClrUsed = 256;
dwBmBitsSize = ((Bitmap.bmWidth * wBitCount + 31) / 32) * 4 * Bitmap.bmHeight;
// 创建位图的一个副本
hDib = GlobalAlloc(GHND, dwBmBitsSize + dwPaletteSize + sizeof(BITMAPINFOHEADER));
lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib);
*lpbi = bi;
hPal = GetStockObject(DEFAULT_PALETTE);
if (hPal) {
hDC = GetDC(NULL);
hOldPal = SelectPalette(hDC, (HPALETTE)hPal, FALSE);
RealizePalette(hDC);
}
// 获取位图数据
if (!GetDIBits(hDC, hBitmap, 0, (UINT)Bitmap.bmHeight,
(LPSTR)lpbi + sizeof(BITMAPINFOHEADER) + dwPaletteSize,
(BITMAPINFO*)lpbi, DIB_RGB_COLORS)) {
MessageBox(NULL, L"GetDIBits failed", L"Error", MB_ICONERROR);
GlobalUnlock(hDib);
GlobalFree(hDib);
return FALSE;
}
if (hOldPal) {
SelectPalette(hDC, (HPALETTE)hOldPal, TRUE);
RealizePalette(hDC);
ReleaseDC(NULL, hDC);
}
// 创建位图文件
fh = CreateFileW(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if (fh == INVALID_HANDLE_VALUE) {
MessageBox(NULL, L"CreateFile failed", L"Error", MB_ICONERROR);
GlobalUnlock(hDib);
GlobalFree(hDib);
return FALSE;
}
// 设置位图文件头
bmfHdr.bfType = 0x4D42; // "BM"
dwDIBSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize;
bmfHdr.bfSize = dwDIBSize;
bmfHdr.bfReserved1 = 0;
bmfHdr.bfReserved2 = 0;
bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize;
// 写入位图文件头
WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
// 写入位图信息头
WriteFile(fh, (LPSTR)lpbi, sizeof(BITMAPINFOHEADER) + dwPaletteSize, &dwWritten, NULL);
// 写入位图数据
WriteFile(fh, (LPSTR)lpbi + sizeof(BITMAPINFOHEADER) + dwPaletteSize, dwBmBitsSize, &dwWritten, NULL);
// 清理
GlobalUnlock(hDib);
GlobalFree(hDib);
CloseHandle(fh);
return TRUE;
}
void CaptureScreenRegion(HWND hWnd, int x, int y, int width, int height, const std::wstring& filename /*= L"Screenshots.bmp"*/)
{
HDC hWindowDC = GetWindowDC(hWnd);
HDC hMemDC = CreateCompatibleDC(NULL);
HBITMAP hBitmap = CreateCompatibleBitmap(hWindowDC, width, height);
SelectObject(hMemDC, hBitmap);
// 将窗口内容的指定部分复制到兼容的内存DC中
BitBlt(hMemDC, 0, 0, width, height, hWindowDC, x, y, SRCCOPY);
ReleaseDC(hWnd, hWindowDC);
// 将内存中的位图保存到文件
if (!SaveBitmap(hBitmap, filename.c_str())) {
std::wcerr << L"Failed to save bitmap to: " << filename << std::endl;
}
// 清理局部资源
DeleteObject(hBitmap);
DeleteDC(hMemDC);
}
int main()
{
HWND hwnd = FindWindow(NULL, L"游戏"); //指定窗口名字
if (hwnd) {
int startX = 400, startY = 400; // 起始坐标
int captureWidth = 300, captureHeight = 300; // 截取宽度和高度
// 直接调用CaptureScreenRegion函数进行屏幕截取,不需要额外的资源管理
CaptureScreenRegion(hwnd, startX, startY, captureWidth, captureHeight, L"德玛西亚.bmp");
// 无需额外的资源清理,因为CaptureScreenRegion内部已经处理
}
else {
MessageBox(NULL, L"Cannot find window", L"Error", MB_ICONERROR);
}
return 0;
}
对比代码如下:
#include <iostream>
#include <vector>
#include <cmath>
#include <fstream>
#include <thread>
#include <mutex>
#define NOMINMAX
#include <windows.h>
// 使用 std::vector 存储图像数据,每个元素代表一个像素的灰度值
typedef std::vector<double> Image;
// 加载 BMP 文件
Image loadBMP(const std::string& filename, int& width, int& height) {
std::ifstream file(filename, std::ios::binary); // 以二进制模式打开文件
if (!file) {
std::cerr << "Error: Failed to open file." << std::endl;
throw std::runtime_error("Failed to open file.");
}
BITMAPFILEHEADER bmfHdr; // BMP 文件头
file.read(reinterpret_cast<char*>(&bmfHdr), sizeof(bmfHdr));
if (bmfHdr.bfType != 0x4D42) { // 检查文件类型是否为 BMP
std::cerr << "Error: Not a valid BMP file." << std::endl;
throw std::runtime_error("Not a valid BMP file.");
}
BITMAPINFOHEADER bi; // BMP 信息头
file.read(reinterpret_cast<char*>(&bi), sizeof(bi));
if (bi.biBitCount != 24) { // 支持 24 位颜色深度的 BMP 文件
std::cerr << "Error: Unsupported BMP format." << std::endl;
throw std::runtime_error("Unsupported BMP format.");
}
width = bi.biWidth;
height = bi.biHeight;
std::cout << "Loaded image with dimensions: " << width << "x" << height << std::endl; // 输出图像尺寸
file.seekg(bmfHdr.bfOffBits); // 移动文件指针到像素数据起始位置
Image imageData(width * height); // 创建图像数据向量
for (int y = height - 1; y >= 0; --y) { // 从下往上读取像素
for (int x = 0; x < width; ++x) {
unsigned char blue, green, red; // 分别读取 BGR 值
file.read(reinterpret_cast<char*>(&blue), 1);
file.read(reinterpret_cast<char*>(&green), 1);
file.read(reinterpret_cast<char*>(&red), 1);
imageData[y * width + x] = 0.2989 * red + 0.5870 * green + 0.1140 * blue; // 计算灰度值
}
if ((width * 3) % 4 != 0) { // 行尾可能有填充字节
file.ignore(4 - (width * 3) % 4);
}
}
return imageData;
}
// 结构体用于存储归一化互相关 (NCC) 的统计信息
struct NccStats {
double sum1, sum2, sumSquare1, sumSquare2, sumProduct;
int size;
};
// 更新统计信息,当滑动窗口移动时调用
void updateStats(NccStats& stats, double newPixel, double oldPixel, int width) {
stats.sum1 += newPixel - oldPixel;
stats.sumSquare1 += newPixel * newPixel - oldPixel * oldPixel;
stats.sumProduct += newPixel * stats.sum2 / stats.size - oldPixel * stats.sum2 / stats.size;
}
// 计算 NCC 值
double calculateNCC(const NccStats& stats) {
double denom = std::sqrt((stats.sumSquare1 - stats.sum1 * stats.sum1 / stats.size) *
(stats.sumSquare2 - stats.sum2 * stats.sum2 / stats.size));
return denom == 0 ? 0 : stats.sumProduct / denom;
}
// 结构体用于存储匹配信息,包括中心位置和得分
struct MatchInfo {
std::pair<int, int> location; // 中心位置
double score; // 匹配得分
};
// 在背景图像中查找模板的最佳匹配位置
MatchInfo findBestMatchSlidingWindowWithScore(const Image& background, const Image& templateImg,
int bgWidth, int bgHeight, int tmplWidth, int tmplHeight,
double threshold, int startX, int startY, int endX, int endY) {
double bestScore = -1.0;
std::pair<int, int> bestMatchLocation(-1, -1);
NccStats stats;
stats.size = tmplWidth * tmplHeight;
for (int i = 0; i < stats.size; ++i) {
stats.sum1 += templateImg[i];
stats.sumSquare1 += templateImg[i] * templateImg[i];
}
for (int i = 0; i < stats.size; ++i) {
stats.sum2 += templateImg[i];
stats.sumSquare2 += templateImg[i] * templateImg[i];
}
for (int y = startY; y <= endY - tmplHeight; ++y) { // 遍历所有可能的y坐标
for (int x = startX; x <= endX - tmplWidth; ++x) { // 遍历所有可能的x坐标
if (x == startX) { // 第一次遍历初始化统计数据
stats.sum1 = 0;
stats.sumSquare1 = 0;
for (int ty = 0; ty < tmplHeight; ++ty) {
for (int tx = 0; tx < tmplWidth; ++tx) {
double pixel = background[(y + ty) * bgWidth + (x + tx)];
stats.sum1 += pixel;
stats.sumSquare1 += pixel * pixel;
}
}
}
else { // 更新统计数据,当窗口向右移动时
int lastColumnIndex = (y + tmplHeight - 1) * bgWidth + x - 1;
int nextColumnIndex = (y + tmplHeight - 1) * bgWidth + x + tmplWidth - 1;
double oldPixel = background[lastColumnIndex];
double newPixel = background[nextColumnIndex];
updateStats(stats, newPixel, oldPixel, bgWidth);
}
double score = calculateNCC(stats); // 计算 NCC 得分
if (score > bestScore && score >= threshold) { // 更新最佳匹配位置和得分
bestScore = score;
bestMatchLocation = { x, y };
}
}
if (y < endY - tmplHeight) { // 更新统计数据,当窗口向下移动时
for (int tx = 0; tx < tmplWidth; ++tx) {
int index = y * bgWidth + tx;
updateStats(stats, 0, background[index], bgWidth);
}
for (int tx = 0; tx < tmplWidth; ++tx) {
int index = (y + tmplHeight) * bgWidth + tx;
updateStats(stats, background[index], 0, bgWidth);
}
}
}
// 计算最佳匹配区域的中心坐标,并四舍五入
int centerX = std::round(bestMatchLocation.first + static_cast<double>(tmplWidth) / 2);
int centerY = std::round(bestMatchLocation.second + static_cast<double>(tmplHeight) / 2);
return { { centerX, centerY }, bestScore }; // 返回最佳匹配信息,包含中心坐标
}
int main() {
int bgWidth, bgHeight;
Image backgroundImage = loadBMP("111111.bmp", bgWidth, bgHeight); // 加载背景图像
std::cout << "Background image loaded successfully." << std::endl;
std::vector<std::string> templateFilenames = { "222222.bmp" }; // 模板图像文件名
double threshold = 0.1;
std::vector<std::pair<int, int>> results;
std::vector<double> scores;
int startX = 10, startY = 10, endX = bgWidth - 20, endY = bgHeight - 20;
std::cout << "Search area: (" << startX << ", " << startY << ") to (" << endX << ", " << endY << ")" << std::endl;
for (const auto& filename : templateFilenames) { // 遍历所有模板图像
int tmplWidth, tmplHeight;
Image templateImg = loadBMP(filename, tmplWidth, tmplHeight); // 加载模板图像
std::cout << "Template image loaded with dimensions: " << tmplWidth << "x" << tmplHeight << std::endl;
auto matchInfo = findBestMatchSlidingWindowWithScore(backgroundImage, templateImg,
bgWidth, bgHeight, tmplWidth, tmplHeight,
threshold, startX, startY, endX, endY); // 查找最佳匹配
// 更新存储结果的方式,现在matchInfo包含的是中心坐标
results.push_back(matchInfo.location);
scores.push_back(matchInfo.score);
std::cout << "Match found at center: " << matchInfo.location.first << ", "
<< matchInfo.location.second << " with score: " << matchInfo.score << std::endl;
}
double maxScore = -1.0;
std::pair<int, int> bestMatch;
for (size_t i = 0; i < scores.size(); ++i) {
if (scores[i] > maxScore) {
maxScore = scores[i];
bestMatch = results[i];
}
}
if (bestMatch.first != -1 && bestMatch.second != -1) {
std::cout << "The best match is at center location: (" << bestMatch.first << ", " << bestMatch.second << ")" << std::endl;
}
else {
std::cout << "No good match found." << std::endl;
}
return 0;
}