
简介这份资源聚焦虹膜分割与虹膜提取的工程实现面向具备一定图像处理基础、希望用C与OpenCV落地生物特征识别项目的开发者与学习者。包内提供可直接编译运行的Visual Studio工程核心代码以cpp源文件形式给出并配有测试图片与一份方法讲解PPT便于对照理解分割流程与算法思路。资源共39个文件包含10张bmp与10张jpg测试图像、2个cpp实现文件以及sln、vcproj、exe、pdb等工程与调试文件另有pdf说明文档压缩包约3.26MB结构完整、开箱即用。目前已有1124人学习下载适合作为课程设计、毕业设计或虹膜识别入门阶段的参考实现读者可借此快速搭建实验环境观察分割效果并在此基础上做算法改进与二次开发。1. 虹膜分割到底在分什么从一张人眼图到可用的掩膜很多人第一次拿到虹膜分割任务以为就是把瞳孔那一圈抠出来。真上手才发现一张人眼图里要分的东西至少有三层瞳孔、虹膜环带、以及把这两者包住的眼睑和睫毛干扰区。虹膜分割代码要输出的不是一张好看的图而是一张二值掩膜告诉后续特征提取模块「哪些像素属于虹膜、哪些是噪声」。这份 C 和 OpenCV 实现的资源核心价值就在于把这条链路用原生代码跑通不依赖深度学习框架编译完就能在本地对单张图或批量图做虹膜提取。它适合两类人一类是做生物特征识别、想理解传统图像处理流程的从业者另一类是需要把虹膜定位嵌进 C 工程、又不想引入庞大推理依赖的开发者。整份代码围绕灰度化、边缘检测、霍夫圆变换、掩膜生成几个环节展开参数都暴露在源码里改起来直观。下面按「先跑通、再调参、最后避坑」的顺序拆。2. 环境搭建与第一张掩膜把 C 工程跑起来2.1 依赖选型为什么是 OpenCV 而不是别的虹膜分割的经典路线是「边缘 圆拟合」这条路线上 OpenCV 提供的HoughCircles、Canny、GaussianBlur几乎覆盖了全部需求。选 OpenCV 而不是自己写卷积理由很实际霍夫圆变换内部涉及累加器空间和半径搜索手写一版能跑但调参成本极高而 OpenCV 的实现已经做了非极大值抑制和梯度筛选参数语义清晰。C 版本相比 Python 的优势在于批量处理上千张图时没有解释器开销且能直接编进已有的桌面或嵌入式工程。常见做法是用 CMake 管理工程把 OpenCV 作为外部依赖链接进来。我一般会先确认本机 OpenCV 版本因为 4.x 和 3.x 在HoughCircles的参数默认值上有细微差别尤其是param2的敏感度。# 查看本机 OpenCV 版本决定后续链接的库名 pkg-config --modversion opencv4 # 若输出 4.x则 CMake 里用 find_package(OpenCV 4 REQUIRED)这段命令的作用是确认版本号。如果输出为空说明pkg-config路径没配好需要手动指定OpenCV_DIR。参数上没什么可调的纯粹是环境探测。2.2 编译与运行从源码到可执行文件工程结构通常是src/放源码、include/放头文件、CMakeLists.txt在根目录。下面是一个最小可用的 CMake 配置链接 OpenCV 的核心和图像处理模块。cmake_minimum_required(VERSION 3.10) project(IrisSegmentation) # 只链接需要的模块减少编译体积 find_package(OpenCV REQUIRED COMPONENTS core imgproc highgui) add_executable(iris_seg src/main.cpp src/iris_segment.cpp) target_include_directories(iris_seg PRIVATE ${OpenCV_INCLUDE_DIRS} include) target_link_libraries(iris_seg PRIVATE ${OpenCV_LIBS})逻辑说明COMPONENTS里只写core、imgproc、highgui是因为虹膜分割用不到dnn、videoio这些模块少链接能加快编译。target_include_directories把自定义头文件目录加进去避免源码里写一长串相对路径。参数上REQUIRED保证找不到 OpenCV 时直接报错而不是静默跳过。编译命令就三步mkdir build cd build cmake .. make -j4-j4是按四核并行编译机器核多可以改大。编译完成后build/下会生成iris_seg可执行文件运行时传入图片路径即可。2.3 第一张掩膜的生成流程主流程分四步读图转灰度、高斯模糊降噪、霍夫圆检测定位瞳孔和虹膜外边界、根据两个圆生成环带掩膜。下面这段是核心函数的骨架。// 输入原始 BGR 图输出虹膜环带掩膜 cv::Mat segmentIris(const cv::Mat src) { cv::Mat gray, blurred; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); // 高斯核 5x5sigma 由 OpenCV 自动推算先压掉睫毛高频噪声 cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0); std::vectorcv::Vec3f circles; // param1 是 Canny 高阈值param2 是累加器阈值越小越容易检出假圆 cv::HoughCircles(blurred, circles, cv::HOUGH_GRADIENT, 1, gray.rows / 8, 200, 50, 20, 200); cv::Mat mask cv::Mat::zeros(gray.size(), CV_8UC1); if (circles.size() 2) { // 半径大的当虹膜外边界小的当瞳孔边界 cv::Vec3f pupil circles[0], iris circles[1]; if (pupil[2] iris[2]) std::swap(pupil, iris); cv::circle(mask, cv::Point(pupil[0], pupil[1]), pupil[2], 0, -1); // 挖掉瞳孔 cv::circle(mask, cv::Point(iris[0], iris[1]), iris[2], 255, -1); // 填充虹膜 } return mask; }逻辑说明先转灰度是因为霍夫圆只吃单通道。高斯模糊的核大小 5x5 是经验值太大虹膜边缘会糊太小睫毛噪声压不住。HoughCircles里dp1表示累加器分辨率与原图一致minDist设为行高的八分之一防止同一个圆被检出多次。param1200是 Canny 高阈值param250是累加器阈值这两个是调参重点。最后用「先填白再挖黑」的方式生成环带比逐像素判断半径更省事。参数怎么改如果检不出圆先把param2降到 30 左右如果检出太多假圆把param2提到 70 以上。minDist太小会导致瞳孔和虹膜被当成同一个圆太大则可能漏掉半径接近的双圆。3. 参数调优与鲁棒性让掩膜在脏图上也能用3.1 霍夫圆参数的实际影响HoughCircles的五个关键参数里dp、minDist、param1、param2、minRadius/maxRadius每一个都会显著改变结果。下面这张表是我在几十张不同质量人眼图上试出来的经验区间不是理论值是踩坑踩出来的。参数作用保守取值激进取值翻车表现dp累加器分辨率12取 2 时小瞳孔直接漏检minDist圆心最小间距rows/8rows/4太小双圆合并太大漏检param1Canny 高阈值200100太低边缘碎圆拟合发散param2累加器阈值5030太低满屏假圆minRadius最小半径2010太小把噪点当瞳孔调参顺序建议先固定dp1、minDistrows/8然后调param2让圆的数量稳定在 2 到 4 个最后用minRadius/maxRadius把明显不合理的圆筛掉。param1一般不动除非图像对比度特别低。3.2 预处理对分割结果的影响原始人眼图里睫毛、反光、眼睑阴影都是干扰源。直接上霍夫圆十张里能有三张检出正确双圆就不错了。常见做法是在灰度化之后加一步形态学开运算把细长的睫毛去掉同时保留虹膜边缘。cv::Mat denoise(const cv::Mat gray) { cv::Mat blurred, opened; cv::GaussianBlur(gray, blurred, cv::Size(7, 7), 0); // 3x3 矩形核做开运算先腐蚀掉睫毛再膨胀回来 cv::Mat kernel cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); cv::morphologyEx(blurred, opened, cv::MORPH_OPEN, kernel); return opened; }逻辑说明核从 5x5 提到 7x7 是为了让睫毛这种细结构在模糊阶段就衰减掉。开运算的核不能大3x3 足够再大虹膜外边缘会被啃掉一圈导致半径偏小。这一步的代价是瞳孔边缘也会略微变钝所以后续param2要相应降低 5 到 10。另一个常见干扰是红外图里的高光斑。高光在灰度图上表现为一块高亮区域霍夫圆会把它的边缘当成圆弧。处理办法是在开运算之后加一步阈值截断把超过 240 的像素压到 240削弱高光梯度。3.3 双圆匹配逻辑的边界处理HoughCircles返回的圆是无序的不能默认第一个是瞳孔、第二个是虹膜。除了按半径排序还要加两个约束圆心距离不能太远、半径比要在合理范围。正常正视人眼里瞳孔半径约为虹膜半径的 0.3 到 0.6圆心偏移不超过虹膜半径的 0.2。bool isValidPair(const cv::Vec3f a, const cv::Vec3f b) { float rMin std::min(a[2], b[2]); float rMax std::max(a[2], b[2]); float ratio rMin / rMax; float dx a[0] - b[0], dy a[1] - b[1]; float dist std::sqrt(dx * dx dy * dy); // 半径比 0.3~0.6圆心偏移不超过大圆半径的 0.2 return ratio 0.3f ratio 0.6f dist rMax * 0.2f; }逻辑说明这个函数在候选圆列表里两两配对只保留满足几何约束的组合。如果没有任何一对通过说明这张图质量太差应该跳过而不是硬分割。参数 0.3 和 0.6 是统计值遇到侧视或斜视人眼可以放宽到 0.25 和 0.7但放宽后假阳性会上升需要配合后续掩膜面积过滤。4. 避坑与排查五个真实翻车记录4.1 现象编译通过但运行时报缺少 opencv_world 库原因CMake 链接的是模块化库但运行时动态链接器找不到.so路径。解决在~/.bashrc里把 OpenCV 的lib目录加进LD_LIBRARY_PATH或者编译时加-Wl,-rpath把路径写死进可执行文件。我一般用后者省得换终端就失效。4.2 现象同一张图两次运行结果不一样原因HoughCircles内部有并行累加多线程下浮点累加顺序不同会导致结果微抖。解决在调用前设cv::setNumThreads(1)牺牲一点速度换确定性。批量处理时这个设置尤其重要否则日志对不上。4.3 现象掩膜边缘有一圈锯齿原因cv::circle画圆时默认用 8 邻域连接半径大时边缘呈阶梯状。解决把cv::circle的最后一个参数lineType设为cv::LINE_AA开启抗锯齿。代价是掩膜边缘会有灰度过渡值后续如果要做严格二值判断需要再阈值化一次。4.4 现象瞳孔被挖掉后虹膜环带断裂原因瞳孔圆和虹膜圆圆心不重合时先填白再挖黑会在偏移方向留下一块空白。解决不要用两次cv::circle改成逐像素判断「到瞳孔圆心距离大于瞳孔半径且到虹膜圆心距离小于虹膜半径」这样即使圆心偏移也能得到连续环带。4.5 现象批量处理到某张图直接崩溃原因circles为空时访问circles[0]越界。解决在取圆之前加if (circles.size() 2) return emptyMask;返回全黑掩膜并记录文件名。血泪经验是批量脚本一定要把失败文件名写进日志否则几千张图里崩一张回头找都找不到。5. 进阶技巧用掩膜面积和圆度做自动质检跑通分割只是第一步真正上线要解决的是「怎么知道这张掩膜是对的」。我的习惯是在生成掩膜后加一道自动质检用两个指标筛掉明显错误的结果掩膜面积占比和环带圆度。掩膜面积占比指虹膜环带像素数除以整图像素数。正视人眼里这个值通常在 0.02 到 0.15 之间太小说明圆检小了太大说明把眼睑也框进去了。圆度用环带外接矩形的宽高比衡量正常虹膜接近正圆宽高比应在 0.8 到 1.25 之间超出说明检出的圆被拉扁了。// 返回 true 表示掩膜通过质检 bool qualityCheck(const cv::Mat mask) { int area cv::countNonZero(mask); double ratio static_castdouble(area) / (mask.rows * mask.cols); if (ratio 0.02 || ratio 0.15) return false; std::vectorstd::vectorcv::Point contours; cv::findContours(mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); if (contours.empty()) return false; cv::Rect box cv::boundingRect(contours[0]); double aspect static_castdouble(box.width) / box.height; return aspect 0.8 aspect 1.25; }逻辑说明countNonZero统计掩膜里白色像素数除以总像素得到占比。findContours取最外层轮廓boundingRect拿到外接矩形宽高比就是圆度的粗略度量。参数 0.02、0.15、0.8、1.25 都是经验阈值换数据集要重新统计。这套质检不能保证 100% 准确但能把明显翻车的图挡在后续流程之外省下大量人工复核时间。从那以后我每次做虹膜分割都强制先跑一遍质检再进特征提取宁可多花几毫秒也不想让一张烂掩膜污染整个批次的统计结果。希望帮到你。本文还有配套的精品资源点击获取