1. 指纹图像采集与预处理
任务说明
本案例实现从指纹传感器读取原始图像数据,并完成灰度化、去噪与增强三项预处理操作。输入为传感器输出的RAW格式图像(8位灰度或Bayer模式),输出为可直接用于后续特征提取的增强灰度图。代码采用模块化设计,核心处理逻辑与传感器硬件解耦,便于在不同平台上验证。
传感器数据读取与格式转换
指纹传感器通常输出8位灰度RAW数据或Bayer模式数据。本案例以8位灰度RAW为输入格式,通过标准文件读取模拟传感器数据流。若传感器输出Bayer格式,则需先进行去马赛克处理,本案例代码中已包含Bayer到RGB的转换函数,但默认路径为直接灰度读取。
// sensor_capture.h
#ifndef SENSOR_CAPTURE_H
#define SENSOR_CAPTURE_H
#include <stdint.h>
#include <stddef.h>
#define IMG_WIDTH 256
#define IMG_HEIGHT 256
#define RAW_SIZE (IMG_WIDTH * IMG_HEIGHT)
// 读取传感器RAW数据(模拟:从文件读取)
int sensor_read_raw(const char* filepath, uint8_t* buffer, size_t size);
// Bayer模式转灰度(若传感器输出Bayer)
void bayer_to_gray(const uint8_t* bayer, uint8_t* gray, int w, int h);
#endif
// sensor_capture.c
#include "sensor_capture.h"
#include <stdio.h>
#include <string.h>
int sensor_read_raw(const char* filepath, uint8_t* buffer, size_t size) {
FILE* fp = fopen(filepath, "rb");
if (!fp) return -1;
size_t rd = fread(buffer, 1, size, fp);
fclose(fp);
return (rd == size) ? 0 : -1;
}
void bayer_to_gray(const uint8_t* bayer, uint8_t* gray, int w, int h) {
// 简化双线性插值去马赛克后转灰度
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
int r = bayer[(y * w + x) * 3];
int g = bayer[(y * w + x) * 3 + 1];
int b = bayer[(y * w + x) * 3 + 2];
gray[y * w + x] = (uint8_t)((r * 77 + g * 150 + b * 29) >> 8);
}
}
}
图像预处理核心实现
预处理流程包含三个步骤:灰度化(若输入为Bayer则转换,否则直接使用)、去噪(采用3×3中值滤波,有效去除椒盐噪声同时保留脊线边缘)、增强(使用CLAHE对比度受限自适应直方图均衡化,提升脊谷对比度)。中值滤波窗口大小和CLAHE的clip限制均设为经验值,可根据传感器噪声特性调整。
// preprocess.h
#ifndef PREPROCESS_H
#define PREPROCESS_H
#include <stdint.h>
void median_filter_3x3(const uint8_t* src, uint8_t* dst, int w, int h);
void clahe_enhance(const uint8_t* src, uint8_t* dst, int w, int h,
int tile_size, float clip_limit);
#endif
// preprocess.c
#include "preprocess.h"
#include <stdlib.h>
#include <string.h>
// 3x3中值滤波
void median_filter_3x3(const uint8_t* src, uint8_t* dst, int w, int h) {
int window[9];
for (int y = 1; y < h - 1; y++) {
for (int x = 1; x < w - 1; x++) {
int idx = 0;
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
window[idx++] = src[(y + dy) * w + (x + dx)];
}
}
// 简单排序取中值(插入排序)
for (int i = 1; i < 9; i++) {
int key = window[i];
int j = i - 1;
while (j >= 0 && window[j] > key) {
window[j + 1] = window[j];
j--;
}
window[j + 1] = key;
}
dst[y * w + x] = window[4];
}
}
// 边界复制
memcpy(dst, src, w);
memcpy(dst + (h - 1) * w, src + (h - 1) * w, w);
for (int y = 0; y < h; y++) {
dst[y * w] = src[y * w];
dst[y * w + w - 1] = src[y * w + w - 1];
}
}
// CLAHE增强(简化实现:8x8 tile,双线性插值)
void clahe_enhance(const uint8_t* src, uint8_t* dst, int w, int h,
int tile_size, float clip_limit) {
int tiles_x = w / tile_size;
int tiles_y = h / tile_size;
if (tiles_x < 2 || tiles_y < 2) return;
// 计算每个tile的直方图并裁剪
int hist[256];
int* cdf = (int*)malloc(tiles_x * tiles_y * 256 * sizeof(int));
if (!cdf) return;
for (int ty = 0; ty < tiles_y; ty++) {
for (int tx = 0; tx < tiles_x; tx++) {
memset(hist, 0, sizeof(hist));
for (int y = ty * tile_size; y < (ty + 1) * tile_size; y++) {
for (int x = tx * tile_size; x < (tx + 1) * tile_size; x++) {
hist[src[y * w + x]]++;
}
}
// 裁剪
int clip = (int)(tile_size * tile_size * clip_limit / 256.0f);
int excess = 0;
for (int i = 0; i < 256; i++) {
if (hist[i] > clip) {
excess += hist[i] - clip;
hist[i] = clip;
}
}
int redist = excess / 256;
for (int i = 0; i < 256; i++) hist[i] += redist;
// CDF
int sum = 0;
int* tile_cdf = cdf + (ty * tiles_x + tx) * 256;
for (int i = 0; i < 256; i++) {
sum += hist[i];
tile_cdf[i] = sum;
}
// 归一化到0-255
int min_cdf = tile_cdf[0];
int max_cdf = tile_cdf[255];
if (max_cdf > min_cdf) {
for (int i = 0; i < 256; i++) {
tile_cdf[i] = (int)((tile_cdf[i] - min_cdf) * 255.0f / (max_cdf - min_cdf));
}
}
}
}
// 双线性插值映射
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
float fx = (float)x / tile_size - 0.5f;
float fy = (float)y / tile_size - 0.5f;
int tx0 = (int)fx;
int ty0 = (int)fy;
if (tx0 < 0) tx0 = 0;
if (ty0 < 0) ty0 = 0;
if (tx0 >= tiles_x - 1) tx0 = tiles_x - 2;
if (ty0 >= tiles_y - 1) ty0 = tiles_y - 2;
float dx = fx - tx0;
float dy = fy - ty0;
int val = src[y * w + x];
int* cdf00 = cdf + (ty0 * tiles_x + tx0) * 256;
int* cdf10 = cdf + (ty0 * tiles_x + tx0 + 1) * 256;
int* cdf01 = cdf + ((ty0 + 1) * tiles_x + tx0) * 256;
int* cdf11 = cdf + ((ty0 + 1) * tiles_x + tx0 + 1) * 256;
float v = (1 - dy) * ((1 - dx) * cdf00[val] + dx * cdf10[val]) +
dy * ((1 - dx) * cdf01[val] + dx * cdf11[val]);
dst[y * w + x] = (uint8_t)v;
}
}
free(cdf);
}
主流程与验证
主程序读取RAW文件,依次执行去噪和增强,输出处理后的图像文件。验证方法为:将输出图像与原始图像进行视觉对比,观察脊线是否更清晰、噪声是否减少。判定标准为:增强后图像脊谷对比度明显提升,且无块状伪影。
// main.c
#include <stdio.h>
#include <stdlib.h>
#include "sensor_capture.h"
#include "preprocess.h"
int main(int argc, char* argv[]) {
if (argc < 3) {
printf("Usage: %s <input.raw> <output.raw>\n", argv[0]);
return 1;
}
uint8_t* raw = (uint8_t*)malloc(RAW_SIZE);
uint8_t* denoised = (uint8_t*)malloc(RAW_SIZE);
uint8_t* enhanced = (uint8_t*)malloc(RAW_SIZE);
if (!raw || !denoised || !enhanced) {
printf("Memory allocation failed\n");
return 1;
}
if (sensor_read_raw(argv[1], raw, RAW_SIZE) != 0) {
printf("Failed to read input file\n");
return 1;
}
// 预处理流程
median_filter_3x3(raw, denoised, IMG_WIDTH, IMG_HEIGHT);
clahe_enhance(denoised, enhanced, IMG_WIDTH, IMG_HEIGHT, 32, 2.0f);
// 保存结果
FILE* fp = fopen(argv[2], "wb");
if (!fp) {
printf("Failed to open output file\n");
return 1;
}
fwrite(enhanced, 1, RAW_SIZE, fp);
fclose(fp);
printf("Preprocessing complete. Output saved to %s\n", argv[2]);
free(raw);
free(denoised);
free(enhanced);
return 0;
}
编译与运行
gcc -O2 -o fingerprint_preprocess main.c sensor_capture.c preprocess.c -lm
./fingerprint_preprocess input.raw output.raw
输入文件为256×256的8位灰度RAW图像。运行后输出文件为预处理结果。可使用图像查看工具(如ImageJ)将RAW文件按256×256、8位灰度格式打开,对比输入输出图像的视觉效果。
验证判定标准:
- 输出图像中指纹脊线连续清晰,无明显断点
- 背景噪声(颗粒感)显著减少
- 脊谷边界锐利,对比度高于原始图像
- 图像边缘无异常伪影
预期输出示例
对于一张包含清晰脊线的指纹RAW图像,处理后预期得到:脊线灰度值更接近255(白色),谷线更接近0(黑色),且图像中无孤立噪点。具体像素值取决于输入图像内容,但整体对比度应显著提升。