简介:这份资源面向生物特征识别方向的初学者与工程实践者,聚焦虹膜识别中基于2D-Gabor滤波器的特征提取环节,帮助读者理解如何将归一化虹膜图像滤波后转化为可比较的二进制特征编码。压缩包共10个文件,约939KB,以m脚本为主,辅以txt说明、mat数据与bmp示例图像,覆盖滤波器构造、编码生成与匹配评估等环节,便于直接运行与调试。资源中gabor_encode、gabor_all、mygabor等脚本展示了多方向多频率滤波与特征编码流程,gabor_eer与result2.mat可用于分析识别性能,说明文档则梳理了算法思路与参数含义。目前已有965人学习下载,适合希望动手复现虹膜识别特征提取、理解Gabor滤波与汉明距离匹配的读者参考。
1. 从一张眼球照片到身份ID:matlab虹膜识别资源能跑通什么
你手头可能有一张近红外的人眼照片,也可能是一段公开的虹膜视频,想把它变成一串可比对的特征码,但卡在“预处理该先做哪步、归一化用极坐标还是别的方法、匹配阈值怎么定”这些具体环节上。这份 matlab 虹膜识别资源就是冲着这条链路来的:它把虹膜区域定位、展开成矩形、提取特征、比对打分这几个环节串成了一个能直接运行的工程。适合两类人:一类是刚接触生物特征识别、需要一份能跑出中间结果图的代码来理解流程的;另一类是做课程设计或原型验证,想拿它当基线,再替换自己采集的数据或改特征提取算法。它不承诺工业级精度,但胜在每一步都有可视化输出,你能看到虹膜到底被圈在哪、展开后长什么样,而不是只拿到一个黑匣子分数。
2. 虹膜定位与归一化:从圆环到矩形的那几步
虹膜识别的第一步不是提特征,而是把虹膜这个圆环区域从整张眼球图里抠出来,再拉成一个固定尺寸的矩形。这一步做不准,后面再花哨的算法都是白搭。这份资源里定位和归一化是分开的两个函数,方便你单独调参看效果。
2.1 定位:先找瞳孔,再找虹膜外边界
常见做法是先二值化瞳孔区域,用形态学去掉睫毛和反光造成的噪点,再对瞳孔边缘做圆拟合。资源里用的是基于边缘检测加圆霍夫变换的思路,但做了简化:先粗定位瞳孔圆心,再在固定半径范围内搜虹膜外圆。这样比全图霍夫快很多,代价是要求输入图里眼睛大致居中。
% 读取图像并转灰度 img = imread('eye_sample.png'); if size(img,3) == 3 gray = rgb2gray(img); else gray = img; end % 高斯滤波抑制睫毛和噪点 gray_f = imgaussfilt(gray, 2); % 自适应二值化提取瞳孔候选区域 bw = imbinarize(gray_f, 'adaptive', 'Sensitivity', 0.5); % 形态学开运算去掉细小噪点,闭运算填补瞳孔内部空洞 se = strel('disk', 3); bw = imopen(bw, se); bw = imclose(bw, se); % 取最大连通域作为瞳孔 bw = bwareafilt(bw, 1); % 提取边界并做圆拟合 [B, L] = bwboundaries(bw, 'noholes'); boundary = B{1}; % 最小二乘拟合圆,得到瞳孔圆心和半径 [xc, yc, r_pupil] = fit_circle(boundary(:,2), boundary(:,1));这段代码里imgaussfilt的 sigma 取 2 是个经验值,太小去不掉睫毛,太大瞳孔边缘会糊。imbinarize的 Sensitivity 调到 0.5 左右能兼顾深色和浅色瞳孔,如果你的数据集瞳孔偏亮,可以降到 0.4。bwareafilt只保留最大连通域,这一步能干掉大部分反光造成的假区域。fit_circle是资源里自带的最小二乘拟合函数,输入边界点坐标,输出圆心和半径。
瞳孔定位完,虹膜外边界搜索范围一般设在瞳孔半径的 2 到 3 倍之间。资源里用了一个径向边缘强度累加的方法:从瞳孔圆心向外发射若干条射线,找梯度最大的位置作为外边界点,再对这些点做圆拟合。这样做比直接霍夫变换稳,因为虹膜外边界经常被上眼睑遮挡,霍夫容易拟合到眼睑弧线上。
2.2 归一化:极坐标展开与尺寸选择
定位拿到内外圆参数后,要把圆环展开成矩形。标准做法是极坐标变换:对每个角度 theta,从内圆半径到外圆半径均匀采样 N 个点,映射到矩形的列上。资源里用的就是这套,展开后矩形宽 360(对应 360 度),高 64。
% 归一化参数 angle_steps = 360; % 角度方向采样数 radius_steps = 64; % 半径方向采样数 % 生成极坐标网格 theta = linspace(0, 2*pi, angle_steps); rho = linspace(0, 1, radius_steps); % 初始化展开图 unwrapped = zeros(radius_steps, angle_steps); for i = 1:angle_steps for j = 1:radius_steps % 当前采样点对应的实际坐标 r = r_pupil + rho(j) * (r_iris - r_pupil); x = round(xc + r * cos(theta(i))); y = round(yc + r * sin(theta(i))); % 边界保护 x = min(max(x,1), size(gray,2)); y = min(max(y,1), size(gray,1)); unwrapped(j,i) = gray_f(y,x); end endangle_steps和radius_steps直接决定特征向量长度。360×64 是公开库里的常见配置,再大对精度提升有限但计算量翻倍。rho从 0 到 1 对应从瞳孔边缘到虹膜外边缘,注意这里没有把瞳孔区域算进去,因为瞳孔本身不含虹膜纹理。展开后一般还会做一次直方图均衡化增强对比度,资源里放在归一化之后、特征提取之前。
提示:如果你的图像里虹膜外边界被眼睑遮挡超过三分之一,展开图上半部分会是模糊或全黑,这时候要么换图,要么在匹配时对遮挡区域做掩膜加权,资源里默认没有做掩膜,这是后面匹配分数波动的一个来源。
3. 特征提取与匹配:Gabor滤波、相位编码和距离度量
归一化之后,虹膜纹理变成了一张 64×360 的灰度图。接下来要把它变成一串固定长度的二进制码,才能做快速比对。这份资源走的是经典 Daugman 路线:Gabor 滤波加相位量化,再用汉明距离匹配。
3.1 Gabor 滤波器组与相位编码
Gabor 滤波的本质是在不同频率和方向上提取局部纹理。资源里用了 4 个频率、6 个方向共 24 个滤波器,对归一化图做卷积,然后取每个位置的实部和虚部符号,编码成两位二进制。
% Gabor 滤波器参数 frequencies = [0.1 0.2 0.3 0.4]; % 径向频率 orientations = 0:pi/6:pi-pi/6; % 6 个方向 % 预分配特征码矩阵 feature_code = zeros(radius_steps, angle_steps, 2); for f = 1:length(frequencies) for o = 1:length(orientations) % 构造 Gabor 核 lambda = 1 / frequencies(f); theta_g = orientations(o); sigma = 0.5 * lambda; gamma = 0.5; % 调用资源自带的 gabor_kernel 函数 kernel = gabor_kernel(lambda, theta_g, sigma, gamma, 15); % 卷积 filtered = conv2(unwrapped, kernel, 'same'); % 取实部虚部符号,累加到特征码 feature_code(:,:,1) = feature_code(:,:,1) + (real(filtered) >= 0); feature_code(:,:,2) = feature_code(:,:,2) + (imag(filtered) >= 0); end end % 二值化,得到最终虹膜码 iris_code = feature_code(:,:,1) > (length(frequencies)*length(orientations)/2);frequencies和orientations的数量决定了特征维度。24 个滤波器是精度和速度的折中,如果你只做演示,可以砍到 2 个频率 4 个方向,跑得快很多。gabor_kernel里的sigma和gamma控制滤波器的带宽和椭圆率,资源里给的默认值对近红外图效果还行,换成可见光图可能需要调。最后二值化阈值取滤波器数量的一半,意思是“超过半数滤波器响应为正就置 1”,这是一种投票机制,比单个滤波器硬阈值稳。
3.2 匹配:汉明距离与旋转补偿
两个虹膜码比对时,不能直接逐位比,因为归一化时角度起点可能不一致,导致整体错位。常见做法是把其中一个码在角度方向循环移位若干次,取最小汉明距离作为最终分数。
function dist = hamming_distance(code1, code2, max_shift) % code1, code2: 二值特征码,尺寸 [radius_steps, angle_steps] % max_shift: 最大旋转补偿角度步数 min_dist = inf; [h, w] = size(code1); for s = -max_shift:max_shift shifted = circshift(code2, s, 2); % 只比较有效区域,这里简化处理全图比较 d = sum(xor(code1(:), shifted(:))) / numel(code1); if d < min_dist min_dist = d; end end dist = min_dist; endmax_shift一般取 8 到 12,对应 8 到 12 度的旋转容忍。再大就会引入误匹配,因为不同人的虹膜码在随机移位下也可能碰巧相似。汉明距离小于 0.32 通常认为是同一只眼,大于 0.45 认为是不同眼,中间是灰区需要二次验证。这个阈值不是绝对的,取决于你的编码长度和滤波器组,资源里给的是 0.35,你可以用自己的数据画类内类间分布图来重新定。
注意:汉明距离对光照变化和遮挡很敏感。如果两张图一张瞳孔大一张瞳孔小,归一化后纹理会被拉伸程度不同,类内距离可能飙到 0.4 以上。资源里没有做瞳孔缩放补偿,这是实际使用中最容易翻车的地方。
4. 避坑与排查:跑不通、分数乱、图不对时先看这几条
这份资源在理想图上能跑出漂亮结果,但换一批数据就容易出各种幺蛾子。下面几条是我自己踩过的,按现象、原因、解决来写。
现象一:瞳孔定位跑到眼角或反光点上。原因通常是二值化阈值不合适,或者图像里有比瞳孔更亮的反光区域。解决方法是先看bw中间结果图,如果瞳孔区域不完整,把imbinarize的 Sensitivity 调低到 0.35 左右,或者在二值化前加一步顶帽变换抑制大面积亮区。
现象二:展开图上下颠倒或左右镜像。原因是极坐标变换时 theta 的起始角度和图像坐标系不一致。Matlab 图像坐标 y 轴向下,sin和cos的方向和你直觉可能相反。解决办法是在theta生成后加一个偏移量,或者直接把展开图旋转 180 度看哪个方向纹理连续。
现象三:同一只眼两次匹配距离大于 0.4。除了旋转和瞳孔缩放,还有一个常见原因是两次采集时眼睛位置不同导致定位到的内外圆半径比例变化。资源里内外圆半径是独立拟合的,如果外圆被眼睑遮挡,拟合半径会偏小,展开时纹理被压缩。解决方法是加一个半径比例约束,比如强制外圆半径在瞳孔半径的 2.2 到 2.8 倍之间,超出就重新搜。
现象四:不同眼睛的匹配距离小于 0.3。这通常是特征码维度太低或者滤波器组太单一导致的。360×64 的码长在公开库上类间距离均值大概 0.45 左右,如果你砍到 180×32,类间距离会明显下降,误识率上升。解决办法是至少保持 360×64,或者增加滤波器方向数到 8 个。
现象五:运行报错“矩阵维度不一致”。多半是输入图像不是灰度图或者尺寸太小。资源里默认输入是 320×280 左右的近红外图,如果你塞一张 4K 彩色图进去,bwboundaries返回的边界点数量会爆炸,后面拟合圆时内存不够。解决办法是读图后先imresize到长边 400 以内,再转灰度。
5. 进阶用法:用自己的数据跑一遍完整评估
把资源跑通只是第一步,真正要判断它能不能用,得用自己的数据做一次类内类间分布评估。我一般会按下面的流程走一遍,大概半小时能出结果。
5.1 批量处理与分数矩阵
假设你有一个文件夹,里面每人左右眼各若干张图,命名规则是personID_eyeID_index.png。先批量提取特征码存成 mat` 文件,再两两算距离。
% 批量提取特征码 image_files = dir('data/*.png'); codes = {}; labels = {}; for k = 1:length(image_files) fname = image_files(k).name; img = imread(fullfile('data', fname)); % 调用资源里的主流程函数 code = iris_pipeline(img); codes{end+1} = code; % 从文件名解析标签,假设格式为 P01_L_1.png parts = split(fname, '_'); labels{end+1} = [parts{1} '_' parts{2}]; end % 计算分数矩阵 n = length(codes); dist_matrix = zeros(n, n); for i = 1:n for j = i+1:n d = hamming_distance(codes{i}, codes{j}, 10); dist_matrix(i,j) = d; dist_matrix(j,i) = d; end end % 分两类:同标签为类内,不同标签为类间 genuine = []; imposter = []; for i = 1:n for j = i+1:n if strcmp(labels{i}, labels{j}) genuine(end+1) = dist_matrix(i,j); else imposter(end+1) = dist_matrix(i,j); end end end % 画分布图 figure; histogram(genuine, 0:0.02:1, 'Normalization', 'probability', 'FaceColor', 'b'); hold on; histogram(imposter, 0:0.02:1, 'Normalization', 'probability', 'FaceColor', 'r'); legend('类内', '类间'); xlabel('汉明距离'); ylabel('概率');这段代码里iris_pipeline是你需要把前面定位、归一化、特征提取串起来的封装函数,资源里给的是分步脚本,自己包一层就行。hamming_distance的max_shift设 10,对应 10 度旋转补偿。histogram的 bin 宽 0.02 足够看出两个分布的重叠区域。
5.2 看什么指标、怎么定阈值
跑完分布图,重点看三个数:类内距离的均值加两倍标准差、类间距离的均值减两倍标准差、以及两个分布的交叠面积。如果类内上界小于类间下界,说明阈值可以取中间任意值,系统可用。如果交叠严重,要么数据质量太差,要么特征提取环节需要改。
| 指标 | 含义 | 理想范围 |
|---|---|---|
| 类内均值 | 同一只眼多次采集的平均距离 | 0.15 到 0.25 |
| 类内标准差 | 同一只眼距离的波动 | 小于 0.08 |
| 类间均值 | 不同眼之间的平均距离 | 0.45 到 0.55 |
| 等误率 | 类内类间分布交点处的错误率 | 越低越好,低于 5% 算可用 |
阈值一般取等误率对应的距离值。如果类内均值 0.2、标准差 0.06,类间均值 0.48、标准差 0.07,那阈值大概在 0.34 左右。资源里默认的 0.35 在这个场景下是合理的,但换数据必须重算。
提示:评估时一定要保证同一只眼的图片来自不同采集时段或不同光照条件,如果全是同一秒连拍的,类内距离会低得不真实,上线后必然翻车。
从那以后我每次拿到新的虹膜数据,都强制先跑一遍类内类间分布,不看到两个峰分开绝不动匹配阈值。这份 matlab 虹膜识别资源给了一条能走通的基线链路,定位、归一化、编码、匹配四个环节都有中间输出可以检查,剩下的就是拿你自己的数据去磨参数。希望帮到你。
本文还有配套的精品资源,点击获取