
简介基于Matlab实现Hough变换的人眼虹膜定位方法附带完整源码与测试图像主要面向计算机、电子信息工程、数学等专业学生适用于课程设计、期末大作业或毕业设计中的图像处理部分。压缩包共2个文件包含1个M脚本与1张BMP人眼图像M脚本覆盖图像读取、灰度化、边缘检测到Hough圆检测的完整流程BMP图像便于直接运行验证整体仅72KB轻量精简。目前已有241人学习浏览说明该实现方案对同类课题具有不错的参考价值。通过研读源码可重点理解Hough变换在虹膜定位中的参数设置与实现技巧掌握圆形目标检测的基本原理与调试思路为后续功能扩展或算法改进打下基础。适合具备一定Matlab基础、希望快速上手图像处理项目或复现虹膜定位流程的读者参考使用。1. Hough变换虹膜定位为什么课程设计都选这条路处理过生物特征识别相关课程设计的人大概率都遇到过同一个痛点给定一张人眼正面图像要求把虹膜区域圈出来。模板匹配在光照变化、眼睑遮挡面前非常脆弱灰度相关性一塌糊涂而Hough变换走的是另一条路——利用边缘点的几何投票机制把找圆转化成找参数空间峰值天然抗局部遮挡和光照扰动。这也是为什么绝大多数Matlab版的虹膜定位参考代码都会落在Hough变换上。这个资源包里包含ex1.m主脚本和一张yanjing.bmp人眼测试图像代码量不大但把图像读取、灰度化、边缘提取、Hough圆检测到结果标记的完整链路串起来了。适合数字图像处理课程设计、生物特征识别方向的大作业也适合刚接触圆的检测原理、想看看真实图像上效果到底如何的工程师。接下来从原理到参数一步步拆开这套方法。2. 从投票到参数空间Hough圆检测的数学机制与Matlab实现2.1 直线Hough到圆Hough累加器维度是怎么涨的Hough变换的核心思想是把图像空间里的形状检测问题映射到参数空间的峰值搜索问题。检测直线时一条直线用斜率k和截距b两个参数或极坐标下的rho和theta两个参数描述于是图像中的每个边缘点都在参数空间投票给所有经过它的直线形成两个维度的累加器。检测圆时圆方程(x-a)^2(y-b)^2r^2需要三个参数圆心坐标(a,b)和半径r累加器随之变成三维。维度上涨带来的第一个问题就是计算量陡增。以一张600×400的图像为例如果半径搜索范围是20到80像素、步长1像素那么累加器数组的理论规模就是600×400×61约1464万个单元。如果对每个边缘点都遍历全部(a,b,r)组合去投票耗时很难接受。这正是Matlab中imfindcircles不直接暴露三维累加器、而是采用Hough梯度法的原因。Hough梯度法的技巧在于分两步走第一步先用边缘点的梯度方向估计圆心因为圆上任意一点的梯度方向都指向圆心或背离圆心第二步再对候选圆心做半径方向的一维直方图投票。这样三维投票被拆成一个二维累加器加一个一维直方图复杂度大幅下降。理解了这个机制后面调参数时就知道边缘梯度质量直接决定了定位精度。2.2 imfindcirclesMatlab封装背后的两步法Matlab图像处理工具箱中imfindcircles函数是Hough梯度法的高层实现也是最常用的圆检测入口。它的返回值是检测到的圆心坐标矩阵centers和半径向量radii。一个最基本的调用方式如下img imread(yanjing.bmp); gray rgb2gray(img); [centers, radii] imfindcircles(gray, [20 80], ... ObjectPolarity, bright, ... Sensitivity, 0.85, ... EdgeThreshold, 0.2);这里[20 80]是半径搜索范围的上下界单位是像素。ObjectPolarity参数指定目标是亮圆还是暗圆虹膜相对巩膜通常偏暗但具体取决于图像的光照条件和拍摄方向如果检测不到就把它切换成bright再试。Sensitivity是累加器峰值判定的灵敏度取值0到1之间值越大越容易把弱边缘也当作圆但误检率也会上升。EdgeThreshold是边缘梯度幅值的阈值低于该阈值的像素不参与投票用来滤除睫毛、皮肤纹理这类弱边缘干扰。函数背后的实际执行流程是先对灰度图计算梯度幅值和方向用EdgeThreshold筛掉弱边缘再用梯度方向信息估计圆心并投票最后在半径方向上统计每个候选圆心对应的最显著半径。理解这一点就不难明白为什么imfindcircles对边缘清晰度如此敏感——边缘都没有投票就无从谈起。2.3 Canny边缘预处理为什么不能跳步有一类常见的误用是拿原始灰度图直接丢给imfindcircles不进行任何预处理就指望它精确圈出虹膜。虹膜边界在图像中往往不是一条锐利的闭合弧线眼睑遮挡、睫毛投影、瞳孔边界都会产生额外的边缘响应原始灰度图上直接跑Hough很容易把瞳孔当成虹膜或者把上下眼睑的弧线误判成圆的一部分。我一般会在Hough检测前做一次Canny边缘提取。Canny算子的滞后双阈值机制对这类问题很有效高阈值筛出确定性强的主边缘低阈值保留与主边缘连通的弱边缘能天然抑制孤立的碎边缘。配合Matlab的edge函数核心步骤就两行edges edge(gray, canny, [0.05 0.15]); [centers, radii] imfindcircles(edges, [20 80]);注意边缘图本身是二值图像imfindcircles对二值图仍会计算梯度并投票此时EdgeThreshold对二值图已经意义不大重心转移到Canny两个阈值的选择上。通常低阈值取0.05左右、高阈值取低值的2到3倍。低阈值太低会把睫毛、瞳孔反光全部引入高阈值太高可能切断虹膜边缘的弱连接段导致圆检测失败。后面第4章的参数联动分析还会继续讨论这个问题。3. ex1.m源码走读从yanjing.bmp到虹膜标记的完整管线3.1 资源包结构与输入图像解析解压这个资源包后核心文件就两个ex1.m和yanjing.bmp。yanjing.bmp是人眼正面的样本图像作为检测算法的输入。ex1.m是主脚本典型的课程设计风格把从图像读取到结果可视化的整个流程写在同一个脚本里方便一步步运行观察中间变量。需要说明的是这类源码基本是给看得懂、能自己改的人准备的直接复制运行可能因为Matlab版本差异或工具箱缺失报错需要自己动手调。如果你的环境里没有图像处理工具箱imfindcircles和viscircles都会直接报未定义函数这是最常见的运行失败原因。可以先执行ver命令检查是否安装了Image Processing Toolbox没有的话需要先补装。另外文件路径中如果包含中文或空格imread偶尔会读到空矩阵建议把解压目录改成纯英文路径再运行。3.2 完整可运行的定位脚本下面给出一个经过整理、可独立运行的版本补齐了原脚本中可能省略的注释和异常处理便于逐段理解%% 基于Hough变换的人眼虹膜定位 clear; close all; clc; % 1. 读取图像并转为灰度 img imread(yanjing.bmp); if size(img, 3) 3 gray rgb2gray(img); else gray img; end % 2. Canny边缘提取突出虹膜边界 edges edge(gray, canny, [0.04 0.12]); % 3. Hough梯度法检测圆形区域 % 半径范围需要根据图像中虹膜的像素尺寸预估 [centers, radii] imfindcircles(edges, [30 100], ... ObjectPolarity, dark, ... Sensitivity, 0.9, ... EdgeThreshold, 0.1); % 4. 可视化原图上叠加检测到的圆 figure; imshow(img); hold on; if ~isempty(centers) viscircles(centers, radii, Color, g, LineWidth, 2); else disp(未检测到虹膜请调整半径范围或灵敏度); end hold off;这段代码的核心逻辑分四步读图转灰度、Canny提取边缘、Hough圆检测、可视化标记。第3步中的[30 100]是半径搜索范围需要根据yanjing.bmp中虹膜的实际像素尺寸手动估计——先用imtool打开图像量一下虹膜直径大约多少个像素再除以2作为半径上下界。范围给得太宽不仅计算变慢还容易把眼皮弧线误判成圆范围太窄则直接漏检。3.3 代码逐段说明与输出验证第一段里用rgb2gray而不是直接取某个颜色通道是因为虹膜与巩膜的对比度主要体现在亮度差异上灰度化能综合三个通道的亮度信息降低单通道噪声。第二段Canny的阈值[0.04 0.12]是经验值低阈值保留梯度幅值大于0.04的边缘高阈值大于0.12才算强边缘。如果图像对比度低两端的下限和上限都往下调如果边缘碎点太多则统一往上提。第三段是整条流水线的关键。imfindcircles返回的centers是N×2矩阵每一行是一个候选圆心的(x,y)坐标radii是N×1向量对应每个候选圆的半径。注意它可能返回多个圆按累加器峰值从高到低排列一般情况下第一个就是虹膜。如果检测出多个结果可以用像素面积或圆度来筛选这个后面第5章会展开。第四段的viscircles负责把检测圆绘制到原图上用于视觉验证。验证标准有两个层面一是圆心是否落在虹膜中心附近二是绘制圆是否贴合虹膜与巩膜的边界。如果检测结果偏移明显优先检查EdgeThreshold和Sensitivity这两个参数对圆心精度的直接影响最大。4. 参数调优实战敏感度、边缘阈值与半径范围如何决定定位成败4.1 三个核心参数的联动关系Hough圆检测在Matlab里能调的参数不少但真正决定成败的就是RadiusRange、Sensitivity和EdgeThreshold三个而且它们彼此耦合孤立地调某一个往往收效甚微。下面的表格是我在实际图像上反复试出来的规律参数取值范围调大后果调小后果调整优先级RadiusRange根据图像自适应引入误检圆计算变慢虹膜半径超范围则漏检第一优先先定范围Sensitivity0 ~ 1弱边缘也被接受误检增加漏检真实虹膜第二优先EdgeThreshold0 ~ 1边缘保留过少圆不完整睫毛、纹理干扰增多第三优先调整顺序上我一般先把RadiusRange卡准。方法是用imdistline在图像上手动量一下虹膜直径比如量出来是90像素半径就设[40 55]留10%的余量。范围一旦偏移后面的Sensitivity再怎么调都白费。RadiusRange确定后再去动Sensitivity从0.85起步检测不到再逐步加每加0.02跑一次。EdgeThreshold放在最后只有当误检圆来自睫毛或眼睑时才去动它它的作用是提前过滤边缘而不是修正圆判定。4.2 批量参数扫描脚本人工一次次改参数效率太低而且肉眼判断误差大。更可靠的方法是写一个循环遍历多组参数组合同时算一个量化误差来评估每组参数的好坏。下面的脚本用梯度下降的思路做了一次最简单的网格搜索img imread(yanjing.bmp); gray rgb2gray(img); % 手工标注的虹膜圆心和半径用于误差评估 true_center [300 250]; true_radius 45; results []; sensList 0.80:0.02:0.94; edgeList 0.05:0.02:0.15; for s sensList for e edgeList [centers, radii] imfindcircles(gray, [40 55], ... Sensitivity, s, EdgeThreshold, e); if isempty(centers) continue; end center_err norm(centers(1,:) - true_center); radius_err abs(radii(1) - true_radius); results(end1, :) [s e center_err radius_err]; end end % 按圆心误差排序取最优参数 results sortrows(results, 3); fprintf(最佳参数: Sensitivity%.2f, EdgeThreshold%.2f\n, ... results(1,1), results(1,2));这段脚本把Sensitivity和EdgeThreshold各自扫描7组值共49种组合每一组用圆心欧氏距离和半径绝对差共同衡量定位质量。norm(centers(1,:) - true_center)计算检测圆心与手工标注圆心之间的欧氏距离误差越小说明定位越准。实际使用时true_center和true_radius需要在第一张图上手工标注或者用已知数据替代。扫描结果通常能发现一组明显的甜点区Sensitivity在0.86到0.90附近、EdgeThreshold在0.08到0.12附近时圆心误差和半径误差同时最小。远离这个区域时要么检测不到圆要么圆心被其他边缘拉到一侧误差急剧上升。这种批量验证的方式比肉眼调参靠谱得多也能直观看到参数之间的相互制约。4.3 常见失败模式与修正第一类失败是检测到的圆落在睫毛或眼睑上。特征非常明显圆心偏向图像上方或眼角、半径偏大或偏小、绘制圆与虹膜边界明显分离。修正手段是把EdgeThreshold往上抬滤除睫毛造成的弱边缘投票。如果EdgeThreshold调到0.2还不够就先对图像做一次中值滤波去掉细纹理再重新提取边缘。第二类失败是检测到瞳孔而不是虹膜。瞳孔比虹膜更暗、边界梯度更清晰如果RadiusRange的下界刚好小于瞳孔半径Hough投票会优先命中瞳孔。修正方法是直接收紧RadiusRange的下界比如虹膜半径45左右、瞳孔半径15左右那就把范围设成[35 55]从参数上隔离掉瞳孔。第三类失败是虹膜边缘被眼睑大面积遮挡导致Hough投票得不到足够多的边缘点支撑累加器峰值不够高、检测不到圆。这种情况先降低Sensitivity到0.9以上如果还不行就得接受局部遮挡的现实改用第5章的分级检测思路——先粗定位再局部精修。5. 内外圆分级检测Hough定位从能用走向好用5.1 ROI裁剪与内圆二次检测全图范围做一次Hough检测能定位到虹膜但瞳孔边界往往被虹膜边缘的投票干扰精度不够理想。一个更稳健的做法是内外圆分级第一轮检测先找到虹膜外圆锁定圆心和半径第二轮在圆心周围裁剪一个ROI区域在这个局部范围内重新做Hough检测目标是瞳孔内圆。% 第一轮全图范围检测虹膜外圆 [centers, radii] imfindcircles(gray, [35 55], ... Sensitivity, 0.88, EdgeThreshold, 0.10); cx round(centers(1,1)); cy round(centers(1,2)); r_iris round(radii(1)); % 第二轮围绕虹膜中心裁剪ROI检测瞳孔 roi_size round(r_iris * 1.4); xmin max(cx - roi_size, 1); xmax min(cx roi_size, size(gray, 2)); ymin max(cy - roi_size, 1); ymax min(cy roi_size, size(gray, 1)); roi gray(ymin:ymax, xmin:xmax, :); [centers_pupil, radii_pupil] imfindcircles(roi, [10 25], ... ObjectPolarity, dark, Sensitivity, 0.92);ROI宽度取虹膜半径的1.4倍是因为瞳孔一定落在虹膜范围内留出20%的余量就能确保瞳孔完整包含在ROI中同时排除大部分眼睑。第二轮的RadiusRange缩小到[10 25]计算量大幅下降Sensitivity提高到0.92可以在局部范围内捕获瞳孔的弱边缘。注意第二轮检测用的是ROI局部坐标系得到的结果需要加上偏移量(xmin-1, ymin-1)才是全图坐标。5.2 面积约束与圆度校验无论是第一轮还是第二轮检测imfindcircles都可能返回多个候选圆。实际场景里经常出现圆心对了但半径偏大或半径对了但圆心在眼角的情况单纯看检测顺序不可靠。我习惯在Hough之后加一道几何校验计算每个候选圆的边缘重合率只有与真实边缘贴合度足够高的圆才被接受。edges edge(roi, canny, [0.05 0.15]); score zeros(size(centers_pupil, 1), 1); for k 1:size(centers_pupil, 1) % 在边缘图上取圆上的等间距采样点 theta linspace(0, 2*pi, 72); xs round(centers_pupil(k,1) radii_pupil(k) * cos(theta)); ys round(centers_pupil(k,2) radii_pupil(k) * sin(theta)); valid xs 1 xs size(edges,2) ... ys 1 ys size(edges,1); score(k) sum(edges(sub2ind(size(edges), ys(valid), xs(valid)))) / sum(valid); end [best, idx] max(score);这段代码的思想很简单在检测圆上均匀采样72个点逐一检查这些点在Canny边缘图上是否落在边缘像素上落在边缘上的采样点占比score就是该圆的边缘重合率。重合率越高说明这个候选圆与图像中的真实边界越吻合。正常虹膜或瞳孔的score能到0.6以上而误检圆的score通常低于0.3拿这个阈值做筛选就能把绝大多数误检直接滤掉。面积约束可以再加一道圆面积pi*r^2与边缘内部像素面积极差超过20%的直接丢弃避免把大面积暗区当圆形目标。经过这两道校验之后Hough定位的结果在大多数课程设计和毕业设计场景下都足够稳。本文还有配套的精品资源点击获取