ARTICLE DETAIL

资讯详情

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

DIP大作业实战:Canny、OTSU与Haar人脸检测流水线

DIP大作业实战:Canny、OTSU与Haar人脸检测流水线 简介这份数字图像处理大作业资源包面向计算机视觉入门学习者与高校课程实践者围绕图像分割、人脸检测与边缘检测三大经典任务展开。包内共11个文件以7个Python脚本为核心配合2个Markdown说明文档、1个压缩包及1个gitignore配置整体约97.64MB脚本覆盖预处理、分类、训练与评估等模块便于直接运行与二次修改。内容涉及Canny边缘检测的高斯滤波、梯度计算、非极大值抑制与双阈值流程OTSU自适应二值化的类间方差最大化思路以及基于OpenCV Haar级联分类器的人脸检测实现并借助matplotlib完成结果可视化。目前已有8348人学习下载适合希望系统掌握OpenCV与Python图像处理基础、完成课程大作业或夯实计算机视觉入门技能的读者参考。1. 拆开这份 DIP 大作业三个算法怎么串成一条能跑的流水线数字图像处理大作业里图像分割、人脸检测、边缘检测这三件事经常被拆成三道独立题交完报告就扔。但真到工程里它们往往是同一条流水线上的不同工位先做预处理压噪再上边缘或阈值把前景抠出来最后在候选区域里跑检测。这份 DIP-Project-master 的价值就在这——它不是三个孤立的 demo而是一个带 Gradle 构建、带 classification 训练子模块、带 Preliminaries 说明的完整工程骨架。你拿到手能直接跑 Canny、OTSU、Haar 人脸检测也能顺着 classification 目录往下摸到 ResNet 和 3D 训练脚本。适合谁正在交大作业但不想只交一份 notebook 的学生以及想用 OpenCV Python 快速验证传统算子效果的从业者。它不解决 SOTA 精度问题但能让你把「预处理→分割→检测」这条链路亲手跑通一遍知道每个参数动了之后图像到底变成什么样。2. 环境与工程结构Gradle 6.5 和 Python 依赖怎么摆平2.1 先看清压缩包里到底有什么解压 DIP-Project-master.zip 之后根目录下大致是这么几类东西.gitignore管版本控制忽略项README.md和Preliminaries.md是说明文档gradle-6.5-bin.zip是构建工具本体classification目录是分类训练相关的 Python 代码根目录还散着classification.py、divide_trdev.py、preprocess.py、dataset.py、metric.py、resnet.py、train_3d.py这些脚本。注意gradle 的 zip 直接放在工程里说明作者希望你在没网或网络受限的环境下也能把构建跑起来不用再去下载。文件/目录作用你什么时候会碰它Preliminaries.md预备知识说明第一次跑之前读一遍gradle-6.5-bin.zipGradle 构建工具需要编译 Java/Kotlin 部分时classification/分类训练代码想跑 ResNet 或 3D 训练时preprocess.py预处理脚本做数据清洗和增强时dataset.py数据集加载换自己的数据时metric.py评估指标看分类结果时resnet.pyResNet 模型定义改网络结构时train_3d.py3D 训练入口跑三维数据训练时这张表不是让你背是让你在报错时能快速定位该翻哪个文件。常见做法是先把Preliminaries.md过一遍里面通常会写清楚 Python 版本和依赖别跳过。2.2 Python 侧依赖装法传统算子这部分核心依赖就是 OpenCV、NumPy、Matplotlib。我一般会建一个干净虚拟环境避免和系统里的包打架python -m venv dip-env source dip-env/bin/activate # Windows 用 dip-env\Scripts\activate pip install opencv-python numpy matplotlibopencv-python是主包带cv2.Canny、cv2.threshold、cv2.CascadeClassifier这些函数numpy负责数组运算OpenCV 读进来的图像本身就是 ndarraymatplotlib用来把原图和结果并排画出来对比。如果你还要跑classification里的训练脚本那还得补 PyTorch 或 TensorFlow具体看resnet.py的 import 头。这里有个坑opencv-python和opencv-contrib-python不要同时装会互相覆盖导致某些函数找不到。2.3 Gradle 部分要不要动gradle-6.5-bin.zip的存在说明工程里有需要 Gradle 构建的模块可能是 Java 写的图像处理工具也可能是 Android 侧的调用封装。如果你只关心 Python 侧的三个算法这个 zip 可以先放着不动。但如果你要跑完整工程就得把它解压到某个目录然后把GRADLE_HOME指过去或者直接用工程里的 wrapper。常见做法是unzip gradle-6.5-bin.zip -d /opt/gradle export PATH/opt/gradle/gradle-6.5/bin:$PATH gradle -v # 验证版本gradle -v能打印出 6.5 版本号就说明环境通了。这一步不急先把 Python 侧的三个算法跑出图来再回头处理构建的事。3. Canny 边缘检测高斯核、双阈值到底怎么调3.1 算法四步拆解Canny 不是一步出结果的它内部走了四步高斯滤波去噪、Sobel 算子算梯度幅值和方向、非极大值抑制细化边缘、双阈值连接强弱边缘。很多人调cv2.Canny只改两个阈值结果边缘要么断断续续要么糊成一片就是因为没意识到前面还有高斯核在起作用。cv2.Canny的签名里其实有apertureSize和L2gradient两个参数前者控制 Sobel 核大小后者决定梯度幅值用 L1 还是 L2 范数算。3.2 可抄作业的调用代码import cv2 import numpy as np import matplotlib.pyplot as plt # 读图并转灰度Canny 只接受单通道 img cv2.imread(test.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 先手动高斯模糊让核大小可控 blurred cv2.GaussianBlur(gray, (5, 5), 1.4) # 双阈值低阈值管弱边缘高阈值管强边缘 edges cv2.Canny(blurred, threshold150, threshold2150, apertureSize3, L2gradientTrue) # 并排显示 plt.subplot(1, 2, 1); plt.imshow(gray, cmapgray); plt.title(Gray) plt.subplot(1, 2, 2); plt.imshow(edges, cmapgray); plt.title(Canny) plt.show()逻辑说明先cvtColor转灰度是因为 Canny 内部只处理单通道GaussianBlur的核大小(5,5)和 sigma1.4是经验起点噪声大就加大核但核太大会把细边缘一起抹掉。threshold150、threshold2150的比例常见做法是 1:2 到 1:3高阈值定强边缘低阈值把和强边缘相连的弱边缘接上。apertureSize3是 Sobel 核尺寸默认就是 3改成 5 或 7 会更抗噪但定位变粗。L2gradientTrue用平方和开根算幅值比默认的 L1 更准代价是稍慢。3.3 参数怎么根据图像改如果边缘断得多先把threshold1往下调比如从 50 降到 30如果噪点被当成边缘先把threshold2往上提或者把高斯核从(5,5)加到(7,7)。光照不均匀的图建议先做一次直方图均衡化再进 Canny否则暗部边缘全丢。这里有个反直觉的点L2gradientTrue并不总是更好在某些低对比度图上它会把噪声幅值也放大反而多出假边缘这时候切回False试试。4. OTSU 图像分割阈值自动选取和光照对抗4.1 类间方差最大化的直觉OTSU 的核心思想很朴素遍历所有可能的灰度阈值把像素分成前景和背景两类算这两类的类间方差取方差最大的那个阈值。类间方差越大说明前景和背景分得越开。它不需要你手动给阈值适合光照不均匀或对比度低的图。但它有个前提图像直方图最好呈双峰分布如果前景背景面积悬殊或者直方图是单峰OTSU 会翻车。4.2 代码实现与参数import cv2 import numpy as np import matplotlib.pyplot as plt img cv2.imread(test.jpg, 0) # 直接以灰度读入 # 先做一次高斯滤波压噪OTSU 对噪声敏感 blur cv2.GaussianBlur(img, (5, 5), 0) # THRESH_OTSU 让 OpenCV 自动算阈值返回值里 ret 就是它算出来的 ret, otsu_bin cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) print(fOTSU 自动选取的阈值: {ret}) plt.subplot(1, 2, 1); plt.imshow(blur, cmapgray); plt.title(Blurred) plt.subplot(1, 2, 2); plt.imshow(otsu_bin, cmapgray); plt.title(fOTSU th{ret}) plt.show()逻辑说明cv2.threshold的第二个参数在用了THRESH_OTSU之后会被忽略写 0 就行真正生效的是 OTSU 自己算出来的ret。THRESH_BINARY表示大于阈值置 255小于置 0。先高斯模糊是因为 OTSU 只看灰度分布噪声会把直方图抹平导致阈值偏移。print(ret)这一步别省你能直观看到它选了多少方便和手动阈值对比。4.3 什么时候 OTSU 会失效如果图像里前景只占很小一块比如医学图像里的病灶OTSU 算出来的阈值会偏向背景把前景吞掉。这时候常见做法是先做形态学开运算去掉小噪点或者改用自适应阈值cv2.adaptiveThreshold。另外彩色图不能直接丢给 OTSU必须先转灰度否则它只按第一个通道算结果完全不对。如果你要做的是广告牌图像分割这类前景背景面积接近的场景OTSU 通常表现不错但如果是颜色变化不大的边缘检测场景OTSU 的二值化可能把渐变区域整块吞掉得配合边缘算子一起用。5. Haar 级联人脸检测加载 XML 和 detectMultiScale 参数5.1 级联分类器怎么工作OpenCV 自带的haarcascade_frontalface_default.xml是一个预训练好的级联分类器内部由多个阶段串联每个阶段是一组 Haar 特征的弱分类器。检测时滑动窗口在图像上逐块扫描只有通过所有阶段的窗口才被判为人脸。这种设计的好处是前几个阶段就能刷掉大量非人脸区域速度快代价是对侧脸、遮挡、光照极端的情况召回率下降。5.2 完整检测代码import cv2 # 加载预训练模型路径按你实际解压位置改 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml) img cv2.imread(group.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 直方图均衡化提升暗部对比度对人脸检测帮助明显 gray cv2.equalizeHist(gray) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, # 每次缩放比例越小越慢但越全 minNeighbors5, # 一个候选框要被多少个邻居确认 minSize(30, 30) # 最小人脸尺寸过滤小噪点 ) for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x w, y h), (0, 255, 0), 2) cv2.imshow(Faces, img) cv2.waitKey(0) cv2.destroyAllWindows()逻辑说明cv2.data.haarcascades是 OpenCV 安装后自带的模型目录直接用这个前缀能避免路径写错。equalizeHist不是必须的但在背光或暗光图上效果提升明显。scaleFactor1.1表示每次把图像缩小 10% 再扫值越接近 1 检测越细但越慢minNeighbors5控制误报调大能减少假脸但可能漏掉真脸minSize用来过滤比人脸还小的误检区域。这三个参数是调优的主战场建议先固定scaleFactor1.1再动minNeighbors。5.3 和边缘、分割怎么串起来一个实用的串法是先 OTSU 或 Canny 把感兴趣区域抠出来缩小检测范围再在 ROI 上跑 Haar 检测。这样比全图扫描快也能减少背景误检。比如做实时摄制视频的人脸检测与标注时可以每隔几帧做一次全图检测中间帧只在上一帧的人脸框附近做局部检测。这份工程里preprocess.py和dataset.py就是干类似的事把数据先过一遍再送进模型。6. 避坑与排查五个真实翻车记录6.1 现象Canny 输出全黑或全白原因图像路径读错cv2.imread返回None后续cvtColor直接报错或产生空数组或者阈值设得极端比如threshold10、threshold20。解决读图后立刻print(img.shape)确认不是None阈值从50/150起步别一上来就拉满。6.2 现象OTSU 阈值算出来是 0 或 255原因图像本身就是纯色或接近纯色直方图只有一个峰OTSU 找不到有效分割点。解决先检查图像方差print(img.std())如果接近 0 说明图没内容换一张有前景背景对比的图再试。6.3 现象Haar 检测报错!empty() in function detectMultiScale原因CascadeClassifier加载 XML 失败路径不对或文件损坏。解决用cv2.data.haarcascades拼路径别手写绝对路径加载后加一句print(face_cascade.empty())返回True就是没加载上。6.4 现象Gradle 构建卡在下载依赖原因工程里虽然放了gradle-6.5-bin.zip但构建脚本里可能还配了远程仓库网络不通就卡住。解决先确认gradle -v能跑再把构建脚本里的仓库地址换成本地缓存或离线模式gradle --offline build试试。6.5 现象classification训练脚本 import 报错原因resnet.py、train_3d.py依赖的深度学习框架没装或者版本不匹配。解决先看脚本头部的 import确认是 PyTorch 还是 TensorFlow再按对应版本装别混装两个框架的 GPU 版本CUDA 版本冲突会让 import 直接失败。7. 进阶技巧把三个算子串成一条可验证的流水线单跑 Canny、OTSU、Haar 都不难难的是让它们互相配合还不互相拖累。我一般会写一个统一的入口脚本把预处理、分割、边缘、检测按顺序串起来每一步的输出都存成中间图方便回看是哪一步把结果搞砸了。下面这个骨架你可以直接改成自己的import cv2 import numpy as np def pipeline(path): img cv2.imread(path) assert img is not None, 读图失败检查路径 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 第一步高斯模糊同时服务 Canny 和 OTSU blur cv2.GaussianBlur(gray, (5, 5), 1.4) # 第二步OTSU 分割拿到前景掩膜 _, mask cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) # 第三步Canny 边缘只在掩膜内保留 edges cv2.Canny(blur, 50, 150, L2gradientTrue) edges cv2.bitwise_and(edges, edges, maskmask) # 第四步Haar 人脸检测在灰度图上跑 cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml) faces cascade.detectMultiScale(gray, 1.1, 5, minSize(30, 30)) # 把结果画回原图 vis img.copy() vis[edges 0] [0, 0, 255] # 边缘标红 for (x, y, w, h) in faces: cv2.rectangle(vis, (x, y), (x w, y h), (0, 255, 0), 2) return vis, mask, edges, faces vis, mask, edges, faces pipeline(test.jpg) cv2.imwrite(mask.png, mask) cv2.imwrite(edges.png, edges) cv2.imwrite(result.png, vis) print(f检测到 {len(faces)} 张人脸)这段代码的关键在于顺序先模糊再分割和边缘共用同一份预处理结果避免重复计算OTSU 的掩膜用来限制 Canny 的搜索范围减少背景边缘干扰Haar 检测放在灰度图上跑因为级联分类器只认灰度。vis[edges 0] [0, 0, 255]这行是把边缘像素直接染红比addWeighted更直观。跑完之后mask.png、edges.png、result.png三张图一对比你就知道是哪一步出了问题。验证方法上我习惯用一张已知人脸位置的图做基准手动改scaleFactor从 1.05 到 1.3看len(faces)怎么变再固定scaleFactor改minNeighbors从 3 到 8记录误检和漏检的平衡点。这套流程走下来你对三个算子的边界就有手感了。从那以后我每次拿到新的图像处理工程都强制先把中间结果落盘再调参不再凭感觉改数字。希望帮到你。本文还有配套的精品资源点击获取
返回列表