092. 使用C语言实现简单的图像识别算法

在C语言中实现一个简单的图像识别算法可以是一个很好的学习项目,帮助你理解图像处理和模式识别的基本概念。这里我将展示一个简单的模板匹配算法,用于在图像中识别特定的模板图案。模板匹配是一种基于像素的匹配方法,适用于简单的图像识别任务。

模板匹配算法简介

模板匹配的基本思想是将一个已知的模板图像在目标图像中逐像素滑动,计算模板与目标图像在每个位置的相似度,从而找到模板在目标图像中的位置。

算法步骤

  1. 读取图像:读取目标图像和模板图像。
  2. 计算相似度:在目标图像中逐像素滑动模板,计算模板与目标图像在每个位置的相似度(通常使用平方误差或归一化相关系数)。
  3. 找到最佳匹配位置:选择相似度最高的位置作为模板的匹配位置。

示例代码:简单的模板匹配算法

为了简化实现,我们假设图像为灰度图像,并使用简单的平方误差作为相似度度量。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#define MAX_IMAGE_SIZE 1000

// 图像结构
typedef struct {
    int width;
    int height;
    unsigned char data[MAX_IMAGE_SIZE][MAX_IMAGE_SIZE];
} Image;

// 读取PGM图像文件
int readPGM(const char* filename, Image* img) {
    FILE* file = fopen(filename, "rb");
    if (!file) {
        printf("Error opening file %s\n", filename);
        return -1;
    }

    char header[3];
    fscanf(file, "%s", header);
    if (strcmp(header, "P5") != 0) {
        printf("Invalid PGM file format\n");
        fclose(file);
        return -1;
    }

    fscanf(file, "%d %d", &img->width, &img->height);
    int maxVal;
    fscanf(file, "%d", &maxVal);
    if (maxVal != 255) {
        printf("Unsupported PGM file format\n");
        fclose(file);
        return -1;
    }

    for (int i = 0; i < img->height; i++) {
        for (int j = 0; j < img->width; j++) {
            img->data[i][j] = fgetc(file);
        }
    }

    fclose(file);
    return 0;
}

// 计算平方误差
double calculateError(Image* img, Image* template, int x, int y) {
    double error = 0.0;
    for (int i = 0; i < template->height; i++) {
        for (int j = 0; j < template->width; j++) {
            int imgX = x + j;
            int imgY = y + i;
            if (imgX < img->width && imgY < img->height) {
                int diff = img->data[imgY][imgX] - template->data[i][j];
                error += diff * diff;
            }
        }
    }
    return error;
}

// 模板匹配
void templateMatching(Image* img, Image* template, int* bestX, int* bestY) {
    double minError = DBL_MAX;
    for (int y = 0; y <= img->height - template->height; y++) {
        for (int x = 0; x <= img->width - template->width; x++) {
            double error = calculateError(img, template, x, y);
            if (error < minError) {
                minError = error;
                *bestX = x;
                *bestY = y;
            }
        }
    }
}

int main() {
    Image img, template;
    int bestX, bestY;

    if (readPGM("image.pgm", &img) != 0) {
        printf("Failed to read image.pgm\n");
        return -1;
    }

    if (readPGM("template.pgm", &template) != 0) {
        printf("Failed to read template.pgm\n");
        return -1;
    }

    templateMatching(&img, &template, &bestX, &bestY);

    printf("Best match at position: (%d, %d)\n", bestX, bestY);

    return 0;
}

代码说明

图像结构

  • 定义了一个 Image 结构来存储图像的宽度、高度和像素数据。

读取PGM图像

  • 使用 readPGM 函数读取灰度图像文件(PGM格式)。

  • PGM格式是一种简单的灰度图像格式,适合用于简单的图像处理任务。

计算平方误差

  • 使用 calculateError 函数计算模板与目标图像在某个位置的平方误差。

模板匹配

  • 使用 templateMatching 函数在目标图像中逐像素滑动模板,找到最佳匹配位置。

主函数

  • 读取目标图像和模板图像。

  • 调用模板匹配函数,找到模板的最佳匹配位置并打印结果。

示例运行

假设你有以下两个PGM文件:

  • image.pgm:目标图像。

  • template.pgm:模板图像。

运行程序后,输出可能:Best match at position: (100, 150)

扩展功能

  1. 支持彩色图像:可以扩展算法以支持彩色图像,通过分别处理RGB通道来计算相似度。
  2. 优化性能:使用多线程或并行计算来加速模板匹配过程。
  3. 归一化相关系数:使用归一化相关系数作为相似度度量,以提高匹配的鲁棒性。
  4. 模板旋转:在匹配过程中尝试模板的多种旋转角度,以提高匹配的灵活性。

示例:归一化相关系数

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

#define MAX_IMAGE_SIZE 1000

typedef struct {
    int width;
    int height;
    unsigned char data[MAX_IMAGE_SIZE][MAX_IMAGE_SIZE];
} Image;

int readPGM(const char* filename, Image* img) {
    FILE* file = fopen(filename, "rb");
    if (!file) {
        printf("Error opening file %s\n", filename);
        return -1;
    }

    char header[3];
    fscanf(file, "%s", header);
    if (strcmp(header, "P5") != 0) {
        printf("Invalid PGM file format\n");
        fclose(file);
        return -1;
    }

    fscanf(file, "%d %d", &img->width, &img->height);
    int maxVal;
    fscanf(file, "%d", &maxVal);
    if (maxVal != 255) {
        printf("Unsupported PGM file format\n");
        fclose(file);
        return -1;
    }

    for (int i = 0; i < img->height; i++) {
        for (int j = 0; j < img->width; j++) {
            img->data[i][j] = fgetc(file);
        }
    }

    fclose(file);
    return 0;
}

double calculateNormalizedCorrelation(Image* img, Image* template, int x, int y) {
    double sumImg = 0.0, sumTemplate = 0.0;
    double sumImg2 = 0.0, sumTemplate2 = 0.0;
    double sumProduct = 0.0;

    for (int i = 0; i < template->height; i++) {
        for (int j = 0; j < template->width; j++) {
            int imgX = x + j;
            int imgY = y + i;
            if (imgX < img->width && imgY < img->height) {
                int imgPixel = img->data[imgY][imgX];
                int templatePixel = template->data[i][j];

                sumImg += imgPixel;
                sumTemplate += templatePixel;
                sumImg2 += imgPixel * imgPixel;
                sumTemplate2 += templatePixel * templatePixel;
                sumProduct += imgPixel * templatePixel;
            }
        }
    }

    int n = template->width * template->height;
    double numerator = n * sumProduct - sumImg * sumTemplate;
    double denominator = sqrt((n * sumImg2 - sumImg * sumImg) * (n * sumTemplate2 - sumTemplate * sumTemplate));

    return (denominator == 0) ? 0 : numerator / denominator;
}

void templateMatching(Image* img, Image* template, int* bestX, int* bestY) {
    double maxCorrelation = -1.0;
    for (int y = 0; y <= img->height - template->height; y++) {
        for (int x = 0; x <= img->width - template->width; x++) {
            double correlation = calculateNormalizedCorrelation(img, template, x, y);
            if (correlation > maxCorrelation) {
                maxCorrelation = correlation;
                *bestX = x;
                *bestY = y;
            }
        }
    }
}

int main() {
    Image img, template;
    int bestX, bestY;

    if (readPGM("image.pgm", &img) != 0) {
        printf("Failed to read image.pgm\n");
        return -1;
    }

    if (readPGM("template.pgm", &template) != 0) {
        printf("Failed to read template.pgm\n");
        return -1;
    }

    templateMatching(&img, &template, &bestX, &bestY);

    printf("Best match at position: (%d, %d)\n", bestX, bestY);

    return 0;
}

示例运行

假设你有以下两个PGM文件:

  • image.pgm:目标图像。

  • template.pgm:模板图像。

运行程序后,输出可能如下:

Best match at position: (100, 150)

在模板匹配算法中,归一化相关系数(Normalized Cross-Correlation, NCC) 是一种常用的相似度度量方法,特别适用于处理图像中的光照变化和对比度变化。归一化相关系数的值范围在 [−1,1] 之间,其中 1 表示完全匹配,−1 表示完全不匹配,0 表示没有相关性。

归一化相关系数公式

假设我们有一个目标图像 I 和一个模板图像 T,模板图像的大小为 m×n。我们希望在目标图像中找到模板的最佳匹配位置。归一化相关系数公式如下:

R(x,y)=∑i=0m−1​∑j=0n−1​(I(x+i,y+j)−Iˉ)2​⋅∑i=0m−1​∑j=0n−1​(T(i,j)−Tˉ)2​∑i=0m−1​∑j=0n−1​[(I(x+i,y+j)−Iˉ)⋅(T(i,j)−Tˉ)]​

其中:

  • I(x+i,y+j) 是目标图像在位置 (x+i,y+j) 的像素值。

  • T(i,j) 是模板图像在位置 (i,j) 的像素值。

  • Iˉ 是目标图像在模板覆盖区域内的平均像素值。

  • Tˉ 是模板图像的平均像素值。

  • R(x,y) 是在位置 (x,y) 的归一化相关系数。

公式解释

分子部分

i=0∑m−1​j=0∑n−1[(I(x+i,y+j)−Iˉ)⋅(T(i,j)−Tˉ)]

  • 这是目标图像和模板图像在每个像素位置的偏差乘积之和。

  • 目的是衡量目标图像和模板图像在每个位置的相关性。

分母部分

i=0∑m−1​j=0∑n−1​(I(x+i,y+j)−Iˉ)2​⋅i=0∑m−1​j=0∑n−1​(T(i,j)−Tˉ)2​

  • 这是目标图像和模板图像的偏差平方和的平方根的乘积。

  • 目的是对分子进行归一化,使得相关系数的值范围在 [−1,1] 之间。

为什么使用归一化相关系数

  1. 光照和对比度变化:归一化相关系数对光照和对比度变化具有一定的鲁棒性。通过减去均值并除以标准差,可以消除这些变化的影响。
  2. 标准化:归一化相关系数的值范围在 [−1,1] 之间,便于比较不同位置的相似度。
  3. 统计意义:归一化相关系数基于统计学的相关性概念,能够有效地衡量两个数据集之间的线性关系。

示例代码中的实现

在前面的代码中,calculateNormalizedCorrelation 函数实现了归一化相关系数的计算。以下是该函数的详细解释:

double calculateNormalizedCorrelation(Image* img, Image* template, int x, int y) {
    double sumImg = 0.0, sumTemplate = 0.0;
    double sumImg2 = 0.0, sumTemplate2 = 0.0;
    double sumProduct = 0.0;

    // 计算目标图像和模板图像的均值、平方和、乘积和
    for (int i = 0; i < template->height; i++) {
        for (int j = 0; j < template->width; j++) {
            int imgX = x + j;
            int imgY = y + i;
            if (imgX < img->width && imgY < img->height) {
                int imgPixel = img->data[imgY][imgX];
                int templatePixel = template->data[i][j];

                sumImg += imgPixel;
                sumTemplate += templatePixel;
                sumImg2 += imgPixel * imgPixel;
                sumTemplate2 += templatePixel * templatePixel;
                sumProduct += imgPixel * templatePixel;
            }
        }
    }

    int n = template->width * template->height;
    double numerator = n * sumProduct - sumImg * sumTemplate;
    double denominator = sqrt((n * sumImg2 - sumImg * sumImg) * (n * sumTemplate2 - sumTemplate * sumTemplate));

    return (denominator == 0) ? 0 : numerator / denominator;
}

代码解释

计算均值、平方和、乘积和

  • sumImgsumTemplate 分别是目标图像和模板图像的像素值之和。

  • sumImg2sumTemplate2 分别是目标图像和模板图像的像素值平方之和。

  • sumProduct 是目标图像和模板图像的像素值乘积之和。

计算分子和分母

  • numerator 是分子部分的计算结果。

  • denominator 是分母部分的计算结果。

返回归一化相关系数

  • 如果分母为零,返回 0(表示没有相关性)。

  • 否则,返回归一化相关系数。

通过这种方式,归一化相关系数能够有效地衡量目标图像和模板图像在每个位置的相似度,从而帮助我们找到模板的最佳匹配位置。

视频讲解

BiliBili: 视睿网络-哔哩哔哩视频 (bilibili.com)