十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Matlab的Hough变换人眼虹膜定位方法详解

基于Matlab的Hough变换人眼虹膜定位方法详解 简介基于Matlab的Hough变换人眼虹膜定位实现配套源码与示例图像面向计算机、电子信息工程、数学等专业学生适用于课程设计、期末大作业或毕业设计中的图像处理模块参考。资源包内共2个文件含1个.m主程序源码和1个.bmp人眼示例图像整体仅72KB轻量易用便于直接查看算法流程与调试运行。Hough变换作为核心方法可用于检测虹膜圆形边界代码结构清晰适合有一定Matlab基础的学习者理解参数空间映射、边缘检测与圆拟合的完整思路也可借此掌握霍夫变换在特征提取中的典型应用。目前已有241人浏览学习可作为人眼定位、生物特征识别等课题的起步参考。读者可基于源码自行修改检测半径范围、阈值或边缘算子扩展至不同场景下的瞳孔定位或其他圆形目标检测任务或进一步结合形态学处理提升鲁棒性从而完成更复杂的视觉识别实验。1. 基于Matlab实现Hough变换的人眼虹膜定位方法为什么这个经典方案仍值得上手在人人都谈深度学习的图像算法语境下用 Hough 变换做虹膜定位反而成了工业落地上被反复拉出来用的老方案。它不依赖标注数据集、参数全部可解释、Matlab 里几十行就能跑通一套完整的“人眼图像 → 边缘图 → 参数投票 → 内圆外圆输出”管线。很多人一上来就想用 CNN 拟合瞳孔和虹膜边界最后却发现标注成本、光照鲁棒性和部署环境带来的问题比算法本身更棘手。而这个基于 Matlab 实现 Hough 变换的虹膜定位方法解决的正是“没有 GPU、没有训练集、但要稳定输出圆心坐标与半径”这类务实需求。无论你是正在做毕业设计的学生、刚入行图像算法的工程师还是想把快速虹膜检测塞进实时图像链路里的开发者这套方法都能在半小时内落地第一版可用结果而且每一步都能看到中间图像排错路径清晰、可控、可复现。2. Hough变换检测圆形的原理与选型从参数空间投票到虹膜内外圆模型2.1 圆检测Hough变换把“找圆”变成“参数空间投票”Hough 变换的核心思想并不复杂图像空间里的一个点变换到参数空间里会对应一条曲线多个共线的点在参数空间里会交于同一点这个交点就是图像空间里那条直线的参数。检测圆时圆的方程为(x - a)^2 (y - b)^2 r^2其中 (a, b) 是圆心坐标r 是半径。图像空间中每个边缘点 (x, y) 在参数空间 (a, b, r) 里对应一个圆锥面。真实圆周上的所有边缘点共享同一组 (a, b, r)它们投票出来的累加器单元会形成峰值。找峰值就找到了圆。Matlab 中不一定要手动实现三维累加器但有必要理解这个投票过程因为它决定后续调参的方向。传统 CHT 的计算量大是因为对每个边缘点都要遍历所有可能的半径和所有可能的圆心复杂度接近 O(N * R * A)其中 N 是边缘像素数R 是半径候选数A 是圆心候选数。这也是很多经验丰富的工程师在接手这类项目时会先用imfindcircles这类封装好的函数快速验证可行性、再考虑是否改写成限定半径范围的加速版本的原因。2.1.1 一个最小化的圆参数投票示意下面这段伪代码展示的是“已知半径范围统计圆心累加器”的核心逻辑真实的 Matlab 代码在下一章演示% 边缘点集合: edges_x, edges_y % 半径搜索范围: r_min 到 r_max accumulator zeros(size(img)); for k 1:length(edges_x) x edges_x(k); y edges_y(k); for r r_min:r_max for theta 0:359 a round(x - r * cosd(theta)); b round(y - r * sind(theta)); if a 0 b 0 a size(img,2) b size(img,1) accumulator(b, a, r - r_min 1) accumulator(b, a, r - r_min 1) 1; end end end end % 三维累加器中最大值对应的 (a, b, r) 即检测出的圆参数这里对每个边缘点、每个半径、每个角度都投一票累加器维度是图像高 × 图像宽 × 半径候选个数。实际使用时这个三重循环在 Matlab 里跑得很慢所以通常会用向量化方式或只对候选圆心区域投票这个优化手法在第五章展开。理解这个投票模型后再看imfindcircles的封装参数就会觉得处处可解释。2.2 对比积分微分算子与深度学习回归Hough变换的边界在哪虹膜定位不是只有 Hough 变换一种做法。Daugman 提出的积分微分算子Integro-Differential Operator是生物识别领域最经典的方案它的思路是沿着圆周方向做积分再对半径求导找到灰度变化最剧烈的圆边界。这个方法的优势是计算效率高但它对初始圆心位置敏感通常需要先在瞳孔区域做一个粗略定位否则容易收敛到眉毛、眼睑这些梯度也很强的位置。相比之下Hough 变换对圆心初始位置没有强依赖只要边缘图里圆周轮廓是完整的它就能通过投票找到圆心和半径这是它适合作为通用图像算法被反复使用的最重要原因。和深度学习方案比Hough 变换不依赖训练数据和 GPU解释性和可控性都是明显的优势。它的问题在于当人眼图像存在严重遮挡、反光或运动模糊时边缘图上的圆弧是断裂的投票峰值会被噪声摊薄这时就需要结合瞳孔先验和局部搜索来约束累加器空间。本章的主线就是把这个先验条件和 Matlab 图像处理工具箱结合起来先把内外圆模型建立清楚。3. Matlab中Hough变换虹膜定位的最小可行流程从人眼图像到内外圆输出3.1 读入人眼图像后按“灰度化→增强→边缘检测”分步预处理虹膜图像通常是 RGB 彩色图第一步转灰度是必须的因为 Hough 变换只关心亮度梯度颜色信息会干扰边缘的一致性。第二步是增强这里我一般先看直方图分布如果图像整体偏暗、瞳孔和虹膜边界对比度不够就用直方图均衡化把对比度拉开如果图像有轻微模糊考虑先做一次高斯去噪而不是直接上锐化因为锐化会把睫毛、眼睑纹理也变成强边缘。% 读取人眼图像 img imread(eye.png); % 转灰度图 gray rgb2gray(img); % 高斯滤波去噪sigma不宜过大否则圆边界会被磨平 gauss imgaussfilt(gray, 1.2); % 直方图均衡化增强对比度 enhanced adapthisteq(gauss, NumTiles, [8 8], ClipLimit, 0.02); % Canny边缘检测阈值先给默认后续按边缘密度调整 edges edge(enhanced, canny, [0.1 0.25]); imshow(edges); title(Canny Edge Map);sigma1.2的去噪强度适合大多数普通摄像头采集到的人眼图过大会让虹膜和巩膜边界变得太宽。ClipLimit0.02是 CLAHE 增强的关键参数它限制的是对比度放大幅度调大会让瞳孔区域的反光点也被放大成强边缘后续 Hough 检测时容易在反光点周围额外检出一个假圆。3.2 用imfindcircles做第一轮内圆检测的Matlab命令Matlab 图像处理工具箱里的imfindcircles就是基于 Hough 变换的圆检测封装它对边缘图内部做基于梯度的投票而不是标准 CHT 的逐点三维累加速度上做了大幅优化。第一次跑的时候不必追求完美先给一个宽松的半径范围和合适的极性把候选圆找出来再说。% 检测暗背景上的亮圆瞳孔是暗的反向亦然 % 半径范围按图像尺寸估算假设人眼区域占图像宽度的三分之一 [centers, radii, metric] imfindcircles(enhanced, [15 60], ... ObjectPolarity, dark, ... % 瞳孔比虹膜暗 Sensitivity, 0.90, ... % 灵敏度0-1之间越大检出越多 EdgeThreshold, 0.1, ... % 边缘阈值越低越容易接受弱边缘 Method, TwoStage); % 两阶段Hough速度快 viscircles(centers, radii, Color, g, LineWidth, 1.5);这里把ObjectPolarity设为dark是因为瞳孔区域在近红外或普通可见光图像里都比虹膜暗算法会优先在暗区域的圆边界上投票。RadiusRange的 [15 60] 是像素单位需要根据你图像里人眼的实际大小缩放这个参数直接影响搜索空间设得太宽会明显变慢并增加误检几率。3.3 用瞳孔先验约束再定位虹膜外圆内圆瞳孔边界定位成功后外圆虹膜与巩膜边界的搜索范围就应该被限制在瞳孔圆心附近而不是在全图范围内重新做一次 Hough 变换。原因很简单眼睛的图像中瞳孔中心和人眼整体的几何中心偏移很小且虹膜外圆一定包含瞳孔圆它的半径大约在瞳孔半径的 1.8 到 2.5 倍之间。把这两条先验写进代码可以直接把累加器维度降一个量级。% 取瞳孔圆心和半径 pupil_center centers(1, :); pupil_radius radii(1); % 外圆半径范围瞳孔半径的1.8到2.5倍 r_min round(pupil_radius * 1.8); r_max round(pupil_radius * 2.5); % 外圆圆心约束在瞳孔圆心附近 ±10 像素的窗口内 search_mask zeros(size(enhanced)); search_mask(max(1, round(pupil_center(2))-10):... min(size(enhanced,1), round(pupil_center(2))10), ... max(1, round(pupil_center(1))-10):... min(size(enhanced,2), round(pupil_center(1))10)) 1; % 对掩码区域内的边缘做强响应 [iris_centers, iris_radii, iris_metric] imfindcircles(enhanced, ... [r_min r_max], ObjectPolarity, bright, ... Sensitivity, 0.88, EdgeThreshold, 0.12); viscircles(iris_centers, iris_radii, Color, r, LineWidth, 1.5);ObjectPolarity在外圆检测时换成了bright因为虹膜到巩膜的方向通常是亮度的上升沿即虹膜区域比巩膜暗边界本身是一个从暗到亮的过渡。这个细节很多人会忽略结果就是外圆总是偏大或偏小。search_mask的作用不是裁剪图像而是告诉后面的投票过程“只在这个区域内生成候选圆心”相当于一个圆形位置先验。3.4 把定位结果转为结构化输出定位不是画个圈就完事项目如果需要继续做人眼中心估计、视线方向分析或生物识别特征提取通常要把圆心、半径、置信度三个量写到变量或文件里。下面是输出格式化的常见做法result.pupil_center pupil_center; result.pupil_radius pupil_radius; result.iris_center iris_centers; result.iris_radius iris_radii; result.confidence iris_metric; % 保存为结构体到mat文件 save(iris_location_result.mat, result); % 也在命令行打印关键参数 fprintf(Pupil: center(%d, %d), radius%d\n, ... round(pupil_center(1)), round(pupil_center(2)), round(pupil_radius)); fprintf(Iris: center(%d, %d), radius%d\n, ... round(iris_centers(1)), round(iris_centers(2)), round(iris_radii));这一步的工程意义在于后续无论是做眼球运动追踪还是活体检测都只需要调用这个result结构体不需要再重新读图、重新跑 Hough。同时通过把输出结构化也方便批量处理一个文件夹里的多张人眼图像时把结果累积到一个表格变量里。3.5 批量处理一个目录下的多张人眼图像实际项目中很少只跑一张图常见需求是把目录下所有*.png或*.jpg的人眼图批量定位。这里要注意的是每张图的尺寸和眼睛位置可能不同RadiusRange不能写死。我通常先按图像宽度估算一个比例files dir(eye_dataset/*.png); for i 1:length(files) img imread(fullfile(files(i).folder, files(i).name)); img_width size(img, 2); % 人眼瞳孔半径约占图宽的3%~8% r_min round(img_width * 0.03); r_max round(img_width * 0.08); [centers, radii] imfindcircles(img, [r_min r_max], ... ObjectPolarity, dark, Sensitivity, 0.91); % 取置信度最高的那个圆作为瞳孔 if ~isempty(centers) pupil centers(1, :); fprintf(%s: pupil(%d,%d) r%d\n, ... files(i).name, round(pupil(1)), round(pupil(2)), round(radii(1))); else fprintf(%s: no pupil detected\n, files(i).name); end end批量处理时一定要有“检测失败”的分支输出而不是让程序在centers为空时直接崩溃。经验上灵敏度 0.91 在普通清晰图像上会有较高的检出率但也会带来少量假圆所以用metric排序后取最大值是一种常见做法。4. Hough变换虹膜定位的参数调优与排错RadiusRange、Sensitivity、EdgeThreshold怎么设4.1 四个必调参数的联动逻辑与调参顺序很多人在 Matlab 里跑imfindcircles时只调Sensitivity结果发现一会误检一会漏检原因是他们没有理解这四个参数是联动关系参数作用调大后效果调小后效果建议起始值RadiusRange限制半径搜索区间搜索空间变大、变慢、误检变多可能漏掉真实圆按瞳孔/虹膜实际尺寸估算Sensitivity控制投票峰值的取舍阈值检出更多弱圆、假圆变多只检强边缘圆、可能漏检0.85 ~ 0.92EdgeThreshold边缘像素入选梯度阈值边缘点变稀疏圆可能不完整边缘点变密集噪声增多0.1 ~ 0.2ObjectPolarity指定目标是亮圆还是暗圆设反时检测不到正确边界同左瞳孔用 dark虹膜外圆用 bright调参顺序上我一般先固定RadiusRange和ObjectPolarity为什么因为它们定义的是“你要找什么东西”属于先验知识而Sensitivity和EdgeThreshold属于“在多大噪声下还坚持找”属于后验调节。两者混着调很难判断当前结果是哪个参数引起的。% 调试辅助代码显示检测到的候选圆个数 [centers, radii, metric] imfindcircles(enhanced, [15 60], ... ObjectPolarity, dark, Sensitivity, 0.90); fprintf(Candidate circles: %d\n, length(radii)); % 打印所有候选圆的置信度观察分数分布 disp([centers radii metric]);如果一个候选圆都没有先确认RadiusRange是否覆盖真实半径再把Sensitivity从 0.85 起步每次加 0.02 往上调每次只看metric的分布而不是直接看图。metric大于 0.2 的圆通常还有意义低于 0.1 的基本是噪声投票。4.2 EdgeThreshold过低导致边缘图过密的修正EdgeThreshold是 Canny 边缘检测的滞后阈值在imfindcircles内部的映射它控制着多少梯度幅值能进入投票环节。这个参数调到 0.05 以下时眼睑纹理、睫毛阴影、皮肤毛孔都会变成边缘点投票累加器里全是虚假峰值表现为检测出的圆半径不连续跳动或者在眼睑边缘上形成一个超大圆。反过来阈值太高圆边界上只有零星几个点投票峰值撑不起来直接漏检。判断当前边缘密度是否合适一个简单有效的办法是直接统计边缘点占图像总像素的比例edges edge(enhanced, canny); edge_density sum(edges(:)) / numel(edges); fprintf(Edge density: %.3f\n, edge_density);在虹膜定位场景下边缘密度在 0.05 到 0.15之间是相对正常的。低于 0.03 说明圆边界大概率没有完整进入投票场高于 0.2 则说明噪声点过多。看到边缘密度异常时先不要急着改 Hough 参数回去调增强参数比如把adapthisteq的ClipLimit降低或者高斯滤波的sigma增大到 1.5从源头减少细纹理。4.3 眼睛闭合、睫毛遮挡和反光导致的丢圆与错圆这三类干扰在真实场景里几乎躲不开。反光点的尺度小、亮度高在边缘图上形成一个致密的亮点簇投票时经常会在这个簇周围形成一个半径很小的假圆它的metric甚至比真正的瞳孔圆还高。睫毛遮挡会让圆形边缘出现一段长缺口Hough 投票的峰值被明显削弱表现为检测半径比实际瞳孔偏大或圆心飘向睫毛一侧。处理这类问题我常用的手段是在投票后增加一个几何合理性校验% 假设图像宽度为W如果检测出圆占图像比例太小或太大丢弃 img_w size(enhanced, 2); if radius img_w * 0.02 || radius img_w * 0.15 % 半径不符合人眼几何比例视为误检 fprintf(Rejected candidate with r%d\n, radius); end % 也可以检查圆心是否落在图像中上部眼睛通常位于人脸图像上半区域 % 这个先验在纯眼睛图像中不适用但适用于人脸/上半身图像 if center_y size(enhanced, 1) * 0.7 fprintf(Rejected candidate: center too low\n); end这种先验过滤不解决图像本质问题但能显著减少错圆对后续流程的污染。一个稳健的定位流程应当允许“检测失败”并返回空结果而不是强行输出一个错误圆。对实时图像场景这个设计的价值远高于任何调参技巧因为后续的注视估计或身份比对模块拿到错圆比拿到空结果更危险。5. 进阶用局部CHT替代全局搜索把虹膜定位做成可实时验证的模块5.1 限定圆心邻域的高度受限CHT实现imfindcircles的封装虽然方便但在需要把定位逻辑嵌入到自定义图像处理流程、或需要对每一帧实时图像做快速响应时自写一个限定圆心邻域的 CHT 反而更好用。核心技巧就是前面提过的把圆的搜索从“全图找任意圆”收紧为“在一小块区域内找半径在给定范围的圆”。这样三重循环的累加器直接被切片成小块速度可以提升一个数量级。function [best_center, best_radius] local_cht(edge_map, cx, cy, r_min, r_max, win) % edge_map: 边缘二值图 % cx, cy: 瞳孔/粗定位圆心 % r_min, r_max: 半径搜索范围 % win: 圆心搜索窗口半径 % 圆心搜索范围 x_range max(1, cx-win):min(size(edge_map,2), cxwin); y_range max(1, cy-win):min(size(edge_map,1), cywin); max_votes 0; best_center [cx cy]; best_radius r_min; % 遍历圆心候选点 for a x_range for b y_range % 对固定圆心统计不同半径上有多少边缘点 for r r_min:r_max votes sum_edge_points_on_circle(edge_map, a, b, r); if votes max_votes max_votes votes; best_center [a b]; best_radius r; end end end end end这个写法的优势在于圆心候选被限制在win * 2 1的方形窗口内如果win10圆心搜索就是 21×21441 个候选点相比全图动辄几十万像素的累计空间计算量差异是数量级的。sum_edge_points_on_circle是把圆周采样的边缘点数量统计出来的辅助函数可以每 3 度采一个点避免重复访问同一个边缘像素。5.2 用瞳孔圆心作为外圆搜索种子闭环验证定位稳定性在已经确定瞳孔圆的前提下外圆的搜索可以直接复用local_cht用瞳孔圆心作为cx, cy把窗口设为瞳孔半径的 0.8 倍左右半径范围按瞳孔半径的 1.8 到 2.5 倍。这样一个完整的内外圆定位流程就变成了两次“给定种子点的小范围 Hough 投票”。为了验证算法在连续帧上的稳定性常见的做法是读取一段录制好的眼睛视频逐帧定位瞳孔圆心并输出其轨迹video VideoReader(eye_video.avi); positions []; frame_idx 0; while hasFrame(video) frame readFrame(video); gray rgb2gray(frame); [centers, radii] imfindcircles(gray, [15 60], ... ObjectPolarity, dark, Sensitivity, 0.90); if ~isempty(centers) positions(end1, :) [centers(1), radii(1)]; else positions(end1, :) [NaN NaN]; end frame_idx frame_idx 1; end % 检查瞳孔中心在连续帧间的位移是否平滑 jump diff(positions(:, 1)); fprintf(Max inter-frame jump: %.2f pixels\n, max(abs(jump)));如果发生超过瞳孔半径 50% 的跳动那一帧的检测结果基本可以判定为误检。这个验证的工程价值是直白的单张图的定位效果再漂亮也要经得起连续帧的考验。稳定的虹膜定位系统从来不是把参数调到一组完美值而是要在检测结果上叠加合理的时空一致性判断把偶然的错圆从输出流中剔除。这也是 Hough 变换在虹膜定位里依然被一线工程师保留的核心理由——它的输出可以被人完全理解任何一层都可以插进人工校验逻辑而这在大规模深度学习方案里是为数不多的奢求。本文还有配套的精品资源点击获取
返回列表