使用 MEMCMP 进行图像匹配?

using memcmp for image matching?

提问人:Eric 提问时间:10/26/2023 最后编辑:Eric 更新时间:10/26/2023 访问量:104

问:

根据我的理解,典型的BMP格式存储从左下角到右上角的像素数据。所以我有
24 位 bmp 的 rgb 值。
unsigned char* screenshotArrayunsigned char* croppedArray

这是我尝试编写代码以匹配 BMP 的 RGB 并在匹配时返回坐标的尝试:

#include <stdio.h>   // Include standard input/output library for printing messages
#include <stdlib.h>  // Include standard library for memory management
#include <windows.h> // Include the Windows API for screen capture

int main()
{
    FILE *log = fopen("log.txt", "w");

    // Load the BMP image from Microsoft Paint 24-bit BMP
    int croppedWidth, croppedHeight;

    FILE *file = fopen("cropped.bmp", "rb"); // Open the BMP image file in binary read mode
    if (!file)                               // Check if the file couldn't be opened
    {
        fprintf(log, "Failed to open the cropped image file.\n"); // Print an error message to the standard error stream
        return 1;                                                    // Return an error code and exit
    }

    // Read the BMP header to get image dimensions
    fseek(file, 18, SEEK_SET);                   // Move the file pointer to the width field in the BMP header
    fread(&croppedWidth, sizeof(int), 1, file);  // Read the width of the image
    fseek(file, 22, SEEK_SET);                   // Move the file pointer to the height field in the BMP header
    fread(&croppedHeight, sizeof(int), 1, file); // Read the height of the image

    // Calculate the size of the cropped image
    int cropWidthRGB = 3 * croppedWidth;                                     // Calculate the size of one row RGB of the image
    int croppedImageSize = cropWidthRGB * croppedHeight;                     // Calculate the total size of the image needed
    unsigned char *croppedArray = (unsigned char *)malloc(croppedImageSize); // Allocate memory to store the image
    if (!croppedArray)                                                       // Check if memory allocation failed
    {
        fclose(file);                                                          // Close the image file
        fprintf(log, "Failed to allocate memory for the cropped image.\n"); // Print an error message
        return 1;                                                              // Return an error code and exit
    }

    // Read the pixel data of the cropped image
    fseek(file, 54, SEEK_SET);                      // Move the file pointer to the start of the pixel data
    fread(croppedArray, 1, croppedImageSize, file); // Read the pixel data into the allocated memory
    fclose(file);                                   // Close the image file

    // Capture a 1920x1080 screenshot
    int screenWidth = 1920;
    int screenHeight = 1080;
    HDC hScreenDC = GetDC(NULL); // Get a handle to the screen device context

    // Create a compatible DC and bitmap to store the screenshot
    HDC hMemoryDC = CreateCompatibleDC(hScreenDC);                                        // Create a compatible device context
    HBITMAP hBitmapScreen = CreateCompatibleBitmap(hScreenDC, screenWidth, screenHeight); // Create a compatible bitmap
    SelectObject(hMemoryDC, hBitmapScreen);                                               // Select the bitmap into the device context
    BitBlt(hMemoryDC, 0, 0, screenWidth, screenHeight, hScreenDC, 0, 0, SRCCOPY);         // Copy the screen content to the bitmap

    // Get the pixel data of the screenshot
    BITMAPINFOHEADER bmiHeader;
    bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmiHeader.biWidth = screenWidth;
    bmiHeader.biHeight = screenHeight; // Use a positive value
    bmiHeader.biPlanes = 1;
    bmiHeader.biBitCount = 24;
    bmiHeader.biCompression = BI_RGB;

    int screenImageSize = screenWidth * 3 * screenHeight;                                                            // Calculate the size of RGB of the screenshot
    unsigned char *screenshotArray = (unsigned char *)malloc(screenImageSize);                                       // Allocate memory to store the screenshot
    GetDIBits(hScreenDC, hBitmapScreen, 0, screenHeight, screenshotArray, (BITMAPINFO *)&bmiHeader, DIB_RGB_COLORS); // Retrieve the pixel data

    // Clean up screenshot resources
    DeleteObject(hBitmapScreen); // Delete the bitmap
    DeleteDC(hMemoryDC);         // Delete the device context
    ReleaseDC(NULL, hScreenDC);  // Release the screen device context

    // Find the exact match
    int screenXMax = screenWidth - croppedWidth;
    int screenYMax = screenHeight - croppedHeight;
    int matchX = -1;
    int matchY = -1;

    // Iterate through the screenshot and find the matching region
    for (int screenY = 0; screenY <= screenYMax; screenY++)
    {
        for (int screenX = 0; screenX <= screenXMax; screenX++)
        {
            int match = 1;
            // Compare pixel blocks using memcmp for faster comparison
            for (int croppedY = 0; croppedY < croppedHeight; croppedY++)
            {
                int offsetCropped = cropWidthRGB * croppedY;                           // Calculate the starting position in the cropped image
                int offsetScreen = 3 * ((screenY + croppedY) * screenWidth + screenX); // Calculate the starting position in the screenshot
                // Compare entire pixel blocks using memcmp
                if (memcmp(&screenshotArray[offsetScreen], &croppedArray[offsetCropped], cropWidthRGB) != 0)
                {
                    match = 0; // Not a match
                    break;
                }
            }

            if (match)
            {
                matchX = screenX;                                // Record the x-coordinate of the match
                matchY = screenHeight - croppedHeight - screenY; // Record the y-coordinate of the match
                break;
            }
        }

        if (matchX >= 0) // post-checking, if not found proceed to next row
        {
            break;
        }
    }

    // Save match coordinates to a file
    FILE *matchFile = fopen("match_coordinates.txt", "w"); // Open a file to write the match coordinates
    if (matchX != -1)
    {
        fprintf(matchFile, "Image match found at (%d, %d)\n", matchX, matchY); // Print the coordinates to the file
    }
    else
    {
        fprintf(matchFile, "Image match not found.\n"); // Print a message indicating no match
    }
    fclose(matchFile); // Close the file
    fclose(log);       // Close the file


    // Clean up memory
    free(croppedArray);    // Free the memory allocated for the cropped image
    free(screenshotArray); // Free the memory allocated for the screenshot

    return 0; // Exit the program
}

我的代码适用于较小的 bmp。

编辑:我想我知道原因,某些维度会进行填充,因此我们需要在将其存储到数组之前跳过它。但是,我仍然不确定GetDIBits是否也执行填充

C RGB BMP 内存

评论

0赞 Weather Vane 10/26/2023
您可能希望涉及图像,因为 .bmp 图像的每一行字节都是 的倍数。维基百科说:每行的大小通过填充四舍五入为 4 个字节(32 位 DWORD)的倍数。stride4
0赞 Eric 10/26/2023
@500-InternalServerError我修改了代码并更改了我的问题,你能发现任何错误吗?
0赞 Eric 10/26/2023
@WeatherVane bmp 来自 mspaint,保证 24 位思想,我修改了它,但它仍然无法匹配更大的 bmp
0赞 Weather Vane 10/26/2023
@Eric,小于 32 位的大小可能是 24 位。如果说有 3 个像素,则每行需要 12 个字节,而不是 9 个字节。所以你不能从像素宽度 * 3 来计算行索引。
0赞 Eric 10/26/2023
@WeatherVane 在我的理解中,只要是 24 位图像,多个不同尺寸的 24 位 BMP 图像将具有相同的步幅。你是说错了吗?int cropWidthRGB = 3 * croppedWidth;

答: 暂无答案