两帧差分的原理是基于比较连续两帧图像的像素差异,以检测图像中发生的变化。这个方法常用于运动检测、物体跟踪或其他图像变化分析。
两帧图像都由一系列像素构成,每个像素通常有三种颜色通道(红色、绿色、蓝色,RGB)。 通常对于每一个像素,分别比较前一帧和当前帧中对应位置的像素值,使用绝对值差分计算两个像素的颜色通道差异,对红色、绿色、蓝色三个通道分别进行计算。
当两个帧之间的像素值有明显差异时,表明图像内容在这一区域发生了变化。
差分后的图像可以通过视觉上呈现变化的区域。没有变化的区域,像素差值为0;有变化的区域,像素差值较大。
两帧差分广泛应用于:
运动检测:通过连续帧差分,可以检测视频中移动的物体。
物体跟踪:分析连续帧中物体的运动路径。
背景减除:可以通过差分帧来消除静态背景,保留运动物体。
以2048*2048大小24位的BMP图像为例,实现两帧图像的差分处理:
1. 直接相减
以用时最短为目标,不进行灰度化等其他处理步骤。
// 只进行两帧差分操作 不进行灰度化处理
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#define WIDTH 2048
#define HEIGHT 2048
typedef struct {
unsigned char b;
unsigned char g;
unsigned char r;
} RGBPixel;
#pragma pack(push, 1)
typedef struct {
unsigned short bfType;
unsigned int bfSize;
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned int bfOffBits;
} BMPFileHeader;
typedef struct {
unsigned int biSize;
int biWidth;
int biHeight;
unsigned short biPlanes;
unsigned short biBitCount;
unsigned int biCompression;
unsigned int biSizeImage;
int biXPelsPerMeter;
int biYPelsPerMeter;
unsigned int biClrUsed;
unsigned int biClrImportant;
} BMPInfoHeader;
#pragma pack(pop)
// 读取 BMP 图像
void load_bmp_image(const char* file_name, RGBPixel(*image)[WIDTH]) {
FILE* file = fopen(file_name, "rb");
if (!file) {
printf("无法打开文件 %s\n", file_name);
exit(1);
}
BMPFileHeader fileHeader;
BMPInfoHeader infoHeader;
fread(&fileHeader, sizeof(BMPFileHeader), 1, file);
if (fileHeader.bfType != 0x4D42) {
printf("文件 %s 不是有效的 BMP 文件\n", file_name);
fclose(file);
exit(1);
}
fread(&infoHeader, sizeof(BMPInfoHeader), 1, file);
if (infoHeader.biWidth != WIDTH || infoHeader.biHeight != HEIGHT || infoHeader.biBitCount != 24) {
printf("图像尺寸或格式不匹配\n");
fclose(file);
exit(1);
}
fseek(file, fileHeader.bfOffBits, SEEK_SET);
fread(image, sizeof(RGBPixel), WIDTH * HEIGHT, file);
fclose(file);
}
// 保存 BMP 图像
void save_bmp_image(const char* file_name, RGBPixel(*image)[WIDTH]) {
FILE* file = fopen(file_name, "wb");
if (!file) {
printf("无法保存文件 %s\n", file_name);
exit(1);
}
BMPFileHeader fileHeader;
BMPInfoHeader infoHeader;
fileHeader.bfType = 0x4D42;
fileHeader.bfSize = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader) + WIDTH * HEIGHT * sizeof(RGBPixel);
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
fileHeader.bfOffBits = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader);
infoHeader.biSize = sizeof(BMPInfoHeader);
infoHeader.biWidth = WIDTH;
infoHeader.biHeight = HEIGHT;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 24;
infoHeader.biCompression = 0;
infoHeader.biSizeImage = WIDTH * HEIGHT * sizeof(RGBPixel);
infoHeader.biXPelsPerMeter = 0;
infoHeader.biYPelsPerMeter = 0;
infoHeader.biClrUsed = 0;
infoHeader.biClrImportant = 0;
fwrite(&fileHeader, sizeof(BMPFileHeader), 1, file);
fwrite(&infoHeader, sizeof(BMPInfoHeader), 1, file);
fwrite(image, sizeof(RGBPixel), WIDTH * HEIGHT, file);
fclose(file);
}
// 计算两帧的差分
void compute_frame_difference(RGBPixel(*prev_frame)[WIDTH], RGBPixel(*current_frame)[WIDTH], RGBPixel(*diff_frame)[WIDTH], double* duration) {
clock_t start_time = clock(); // 开始时间
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
// 计算每个像素的差异 (绝对值)
diff_frame[i][j].r = abs(current_frame[i][j].r - prev_frame[i][j].r);
diff_frame[i][j].g = abs(current_frame[i][j].g - prev_frame[i][j].g);
diff_frame[i][j].b = abs(current_frame[i][j].b - prev_frame[i][j].b);
}
}
clock_t end_time = clock(); // 结束时间
*duration = ((double)(end_time - start_time)) / CLOCKS_PER_SEC; // 计算时间差并存储到指针指向的变量中
}
// 主函数
int main() {
RGBPixel(*prev_frame)[WIDTH] = malloc(HEIGHT * sizeof(*prev_frame));
RGBPixel(*current_frame)[WIDTH] = malloc(HEIGHT * sizeof(*current_frame));
RGBPixel(*diff_frame)[WIDTH] = malloc(HEIGHT * sizeof(*diff_frame));
double duration; // 用于存储计算时间
// 读取前一帧图像
//load_bmp_image("target_image.bmp", prev_frame);
load_bmp_image("target_image2048.bmp", prev_frame);
// 读取当前帧图像
//load_bmp_image("black_image.bmp", current_frame);
load_bmp_image("black_image2048.bmp", current_frame);
// 计算差分图像并记录时间
compute_frame_difference(prev_frame, current_frame, diff_frame, &duration);
// 输出所用时间
printf("帧差计算所用时间: %f 秒\n", duration);
// 保存差分图像
save_bmp_image("frame_difference.bmp", diff_frame);
// 释放内存
free(prev_frame);
free(current_frame);
free(diff_frame);
return 0;
}
2. 差分后进行二值化操作
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#define WIDTH 2048
#define HEIGHT 2048
typedef struct {
unsigned char b;
unsigned char g;
unsigned char r;
} RGBPixel;
typedef struct {
int x;
int y;
} Point2D;
#pragma pack(1)
typedef struct {
unsigned short bfType;
unsigned int bfSize;
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned int bfOffBits;
} BMPFileHeader;
typedef struct {
unsigned int biSize;
int biWidth;
int biHeight;
unsigned short biPlanes;
unsigned short biBitCount;
unsigned int biCompression;
unsigned int biSizeImage;
int biXPelsPerMeter;
int biYPelsPerMeter;
unsigned int biClrUsed;
unsigned int biClrImportant;
} BMPInfoHeader;
#pragma pack()
// 读取 BMP 图像
void load_bmp_image(const char* file_name, RGBPixel(*image)[WIDTH]) {
FILE* file = fopen(file_name, "rb");
if (!file) {
printf("无法打开文件 %s\n", file_name);
exit(1);
}
BMPFileHeader fileHeader;
BMPInfoHeader infoHeader;
fread(&fileHeader, sizeof(BMPFileHeader), 1, file);
if (fileHeader.bfType != 0x4D42) {
printf("文件 %s 不是有效的 BMP 文件\n", file_name);
fclose(file);
exit(1);
}
fread(&infoHeader, sizeof(BMPInfoHeader), 1, file);
if (infoHeader.biWidth != WIDTH || infoHeader.biHeight != HEIGHT || infoHeader.biBitCount != 24) {
printf("图像尺寸或格式不匹配\n");
fclose(file);
exit(1);
}
fseek(file, fileHeader.bfOffBits, SEEK_SET);
fread(image, sizeof(RGBPixel), WIDTH * HEIGHT, file);
fclose(file);
}
// 保存 BMP 图像
void save_bmp_image(const char* file_name, RGBPixel(*image)[WIDTH]) {
FILE* file = fopen(file_name, "wb");
if (!file) {
printf("无法保存文件 %s\n", file_name);
exit(1);
}
BMPFileHeader fileHeader;
BMPInfoHeader infoHeader;
fileHeader.bfType = 0x4D42;
fileHeader.bfSize = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader) + WIDTH * HEIGHT * sizeof(RGBPixel);
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
fileHeader.bfOffBits = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader);
infoHeader.biSize = sizeof(BMPInfoHeader);
infoHeader.biWidth = WIDTH;
infoHeader.biHeight = HEIGHT;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 24;
infoHeader.biCompression = 0;
infoHeader.biSizeImage = WIDTH * HEIGHT * sizeof(RGBPixel);
infoHeader.biXPelsPerMeter = 0;
infoHeader.biYPelsPerMeter = 0;
infoHeader.biClrUsed = 0;
infoHeader.biClrImportant = 0;
fwrite(&fileHeader, sizeof(BMPFileHeader), 1, file);
fwrite(&infoHeader, sizeof(BMPInfoHeader), 1, file);
fwrite(image, sizeof(RGBPixel), WIDTH * HEIGHT, file);
fclose(file);
}
// 图像相减并进行二值化
void image_subtract(RGBPixel(*img1)[WIDTH], RGBPixel(*img2)[WIDTH], RGBPixel(*result)[WIDTH], double* duration) {
clock_t start_time = clock();
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
int diff_r = abs(img1[i][j].r - img2[i][j].r);
int diff_g = abs(img1[i][j].g - img2[i][j].g);
int diff_b = abs(img1[i][j].b - img2[i][j].b);
// 根据图像实际情况调整二值化阈值
int avg_diff = (diff_r + diff_g + diff_b) / 3;
if (avg_diff > THRESHOLD) {
result[i][j].r = 255; // 白色
result[i][j].g = 255;
result[i][j].b = 255;
}
else {
result[i][j].r = 0; // 黑色
result[i][j].g = 0;
result[i][j].b = 0;
}
}
}
clock_t end_time = clock();
*duration = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
}
int main() {
clock_t start_time, end_time;
double cpu_time_used, subtract_duration, target_detection_duration;
RGBPixel(*img1)[WIDTH] = malloc(HEIGHT * sizeof(*img1));
RGBPixel(*img2)[WIDTH] = malloc(HEIGHT * sizeof(*img2));
RGBPixel(*img_subtracted)[WIDTH] = malloc(HEIGHT * sizeof(*img_subtracted));
Point2D centers[MAX_POINTS];
int center_count;
if (!img1 || !img2 || !img_subtracted) {
printf("内存分配失败\n");
exit(1);
}
// 读取图像
load_bmp_image("target_image2048.bmp", img1);
load_bmp_image("black_image2048.bmp", img2);
// 开始总时间测量
start_time = clock();
// 进行图像相减并测量时间
image_subtract(img1, img2, img_subtracted, &subtract_duration);
// 检测靶标中心并测量时间
//detect_target_centers(img_subtracted, centers, ¢er_count, &target_detection_duration);
// 结束总时间测量
end_time = clock();
cpu_time_used = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
// 保存差分后的图像
save_bmp_image("target_subtracted2048.bmp", img_subtracted);
// 输出时间信息和检测到的靶标中心
printf("图像相减时间: %.6f 秒\n", subtract_duration);
// 释放内存
free(img1);
free(img2);
free(img_subtracted);
return 0;
}
3. 自适应阈值处理差分操作
第2中方法是根据既定的阈值进行二值化处理,下面这种方法是自适应阈值处理。
// 自适应阈值处理方法进行两帧图像差分
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#define WIDTH 2048
#define HEIGHT 2048
#define THRESHOLD 100 // 二值化阈值
#define ADAPTIVE_THRESHOLD_FACTOR 0.5 //自适应阈值处理
typedef struct {
unsigned char b;
unsigned char g;
unsigned char r;
} RGBPixel;
typedef struct {
int x;
int y;
} Point2D;
#pragma pack(1)
typedef struct {
unsigned short bfType;
unsigned int bfSize;
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned int bfOffBits;
} BMPFileHeader;
typedef struct {
unsigned int biSize;
int biWidth;
int biHeight;
unsigned short biPlanes;
unsigned short biBitCount;
unsigned int biCompression;
unsigned int biSizeImage;
int biXPelsPerMeter;
int biYPelsPerMeter;
unsigned int biClrUsed;
unsigned int biClrImportant;
} BMPInfoHeader;
#pragma pack()
// 读取 BMP 图像
void load_bmp_image(const char* file_name, RGBPixel(*image)[WIDTH]) {
FILE* file = fopen(file_name, "rb");
if (!file) {
printf("无法打开文件 %s\n", file_name);
exit(1);
}
BMPFileHeader fileHeader;
BMPInfoHeader infoHeader;
fread(&fileHeader, sizeof(BMPFileHeader), 1, file);
if (fileHeader.bfType != 0x4D42) {
printf("文件 %s 不是有效的 BMP 文件\n", file_name);
fclose(file);
exit(1);
}
fread(&infoHeader, sizeof(BMPInfoHeader), 1, file);
if (infoHeader.biWidth != WIDTH || infoHeader.biHeight != HEIGHT || infoHeader.biBitCount != 24) {
printf("图像尺寸或格式不匹配\n");
fclose(file);
exit(1);
}
fseek(file, fileHeader.bfOffBits, SEEK_SET);
fread(image, sizeof(RGBPixel), WIDTH * HEIGHT, file);
fclose(file);
}
// 保存 BMP 图像
void save_bmp_image(const char* file_name, RGBPixel(*image)[WIDTH]) {
FILE* file = fopen(file_name, "wb");
if (!file) {
printf("无法保存文件 %s\n", file_name);
exit(1);
}
BMPFileHeader fileHeader;
BMPInfoHeader infoHeader;
fileHeader.bfType = 0x4D42;
fileHeader.bfSize = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader) + WIDTH * HEIGHT * sizeof(RGBPixel);
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
fileHeader.bfOffBits = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader);
infoHeader.biSize = sizeof(BMPInfoHeader);
infoHeader.biWidth = WIDTH;
infoHeader.biHeight = HEIGHT;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 24;
infoHeader.biCompression = 0;
infoHeader.biSizeImage = WIDTH * HEIGHT * sizeof(RGBPixel);
infoHeader.biXPelsPerMeter = 0;
infoHeader.biYPelsPerMeter = 0;
infoHeader.biClrUsed = 0;
infoHeader.biClrImportant = 0;
fwrite(&fileHeader, sizeof(BMPFileHeader), 1, file);
fwrite(&infoHeader, sizeof(BMPInfoHeader), 1, file);
fwrite(image, sizeof(RGBPixel), WIDTH * HEIGHT, file);
fclose(file);
}
// 自适应阈值处理
void image_subtract(RGBPixel(*img1)[WIDTH], RGBPixel(*img2)[WIDTH], RGBPixel(*result)[WIDTH], double* duration) {
clock_t start_time = clock();
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
// 获取两个像素的亮度值,使用加权平均
int brightness1 = (img1[i][j].r + img1[i][j].g + img1[i][j].b) / 3;
int brightness2 = (img2[i][j].r + img2[i][j].g + img2[i][j].b) / 3;
// 根据亮度的差异动态调整阈值
int local_threshold = (int)(ADAPTIVE_THRESHOLD_FACTOR * (brightness1 + brightness2) / 2);
int diff_r = abs(img1[i][j].r - img2[i][j].r);
int diff_g = abs(img1[i][j].g - img2[i][j].g);
int diff_b = abs(img1[i][j].b - img2[i][j].b);
// 使用局部阈值进行二值化
if (diff_r > local_threshold || diff_g > local_threshold || diff_b > local_threshold) {
result[i][j].r = 255; // 填充为白色
result[i][j].g = 255;
result[i][j].b = 255;
}
else {
result[i][j].r = 0; // 填充为黑色
result[i][j].g = 0;
result[i][j].b = 0;
}
}
}
clock_t end_time = clock();
*duration = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
}
int main() {
clock_t start_time, end_time;
double cpu_time_used, subtract_duration, target_detection_duration;
/*RGBPixel(*img1)[WIDTH] = malloc(HEIGHT * sizeof(*img1));
RGBPixel(*img2)[WIDTH] = malloc(HEIGHT * sizeof(*img2));
RGBPixel(*img_subtracted)[WIDTH] = malloc(HEIGHT * sizeof(*img_subtracted));*/
RGBPixel(*img1)[WIDTH] = malloc(HEIGHT * WIDTH * sizeof(RGBPixel));
RGBPixel(*img2)[WIDTH] = malloc(HEIGHT * WIDTH * sizeof(RGBPixel));
RGBPixel(*img_subtracted)[WIDTH] = malloc(HEIGHT * WIDTH * sizeof(RGBPixel));
Point2D centers[MAX_POINTS];
int center_count;
if (!img1 || !img2 || !img_subtracted) {
printf("内存分配失败\n");
exit(1);
}
// 读取图像
load_bmp_image("img1.bmp", img1);
load_bmp_image("img2.bmp", img2);
// 开始总时间测量
start_time = clock();
// 进行图像相减并测量时间
image_subtract(img1, img2, img_subtracted, &subtract_duration);
// 结束总时间测量
end_time = clock();
cpu_time_used = ((double)(end_time - start_time)) / CLOCKS_PER_SEC;
// 保存差分后的图像
save_bmp_image("subtracted_img.bmp", img_subtracted);
printf("图像相减时间: %.6f 秒\n", subtract_duration);
// 释放内存
free(img1);
free(img2);
free(img_subtracted);
return 0;
}