Python-OpenCV边缘检测与阈值分割:Sobel、Scharr、Laplacian、Canny、全局与自适应阈值

目录

一、前置知识回顾

二、Sobel索贝尔算子(基础梯度边缘检测)

三、Scharr沙尔算子(Sobel优化版,捕捉细小边缘)

四、Laplacian拉普拉斯算子(二阶微分,同时捕捉横竖边缘)

五、Canny边缘检测(工业最常用双阈值边缘算法)

[六、全局阈值处理 threshold](#六、全局阈值处理 threshold)

[七、自适应阈值 adaptiveThreshold(光照不均图片专用)](#七、自适应阈值 adaptiveThreshold(光照不均图片专用))

八、算子与阈值工具对比简表

[九、综合案例:标准预处理流水线(降噪→灰度→Canny 边缘)](#九、综合案例:标准预处理流水线(降噪→灰度→Canny 边缘))

十、本章高频踩坑清单

[1. 梯度算子类(Sobel / Scharr / Laplacian)](#1. 梯度算子类(Sobel / Scharr / Laplacian))

[2. 阈值处理类(threshold / adaptiveThreshold)](#2. 阈值处理类(threshold / adaptiveThreshold))

[3. Canny 边缘检测类](#3. Canny 边缘检测类)

[4. 通用细节类](#4. 通用细节类)

十一、本章知识小结


上一章我们学习了图像各类滤波算法,掌握卷积、高斯、中值、双边滤波,主要用于图像降噪预处理。降噪之后,视觉任务里两个非常关键的操作就是提取图像边缘阈值分割

边缘可以理解为图片里面物体轮廓,阈值分割把灰度图划分为前景、背景。物体识别、轮廓查找、目标计数都离不开这两套技术。

本章结合全部示例代码,依次学习 Sobel 索贝尔算子、Scharr 沙尔算子、Laplacian 拉普拉斯算子、Canny 边缘检测,以及全局阈值、自适应阈值处理。

一、前置知识回顾

  1. 边缘:图像像素灰度发生剧烈变化的位置,就是物体轮廓;
  2. 边缘检测前置建议:一般先做滤波降噪,再做边缘提取,减少噪点产生的伪边缘;
  3. 阈值处理只接受单通道灰度图,彩色图需要先转灰度;
  4. 梯度:像素明暗变化程度,算子本质就是计算图像梯度。

二、Sobel索贝尔算子(基础梯度边缘检测)

Sobel 算子分别计算 x 方向、y 方向梯度。

  • dx=1, dy=0:计算 X 方向梯度,提取垂直边缘(图像左右明暗变化)。通俗理解:X 方向梯度看左右像素差异,突出物体竖线轮廓。
  • dx=0, dy=1:计算 Y 方向梯度,提取水平边缘(图像上下明暗变化)。通俗理解:Y 方向梯度看上下像素差异,突出物体横线轮廓。
  • 重要提示:官方建议分开计算 dx、dy,不要直接 dx=1, dy=1 ,直接合并输出效果差。
    ddepth 是输出图像深度,不要填 -1 。Sobel 做微分会产生正负梯度,8 位图像 0-255 存不下负数,填 -1 会直接截断负数,丢掉一半边缘信息(半边边缘消失)。工程标准写法:先用 cv2.CV_64F 保存正负梯度,再用 cv2.convertScaleAbs 取绝对值转回正数。
python 复制代码
# opencv索贝尔算子.py
import cv2
img = cv2.imread(r'D:\PYTHON\your name.jpg')
if img is None:
    print("图片读取失败,请检查路径")
    exit()

#x方向,提取垂直边缘
dx = cv2.Sobel(img, cv2.CV_64F, dx=1, dy=0, ksize=5)
dx = cv2.convertScaleAbs(dx)

#y方向,提取水平边缘
dy = cv2.Sobel(img, cv2.CV_64F, dx=0, dy=1, ksize=5)
dy = cv2.convertScaleAbs(dy)

#不推荐直接同时dx=1 dy=1,效果较差
dxy = cv2.Sobel(img, cv2.CV_64F, dx=1, dy=1, ksize=5)
dxy = cv2.convertScaleAbs(dxy)

#三种方式合并两个方向边缘
res1 = dx + dy
res2 = cv2.add(dx, dy)
res3 = cv2.addWeighted(dx, 0.5, dy, 0.5, 0)
cv2.imshow('原图', img)
cv2.imshow('res1', res1)
cv2.imshow('res2', res2)
cv2.imshow('res3', res3)
while 1:
    key = cv2.waitKey(10)
    if key == ord('q') or key == 27:
        break
cv2.destroyAllWindows()

三种合并方式的差异:

res1 = dx + dy:numpy 数组直接相加,数值溢出会截断(超过 255 归 255);

res2 = cv2.add(dx, dy):OpenCV 饱和加法,超过 255 限制为 255;

res3 = cv2.addWeighted(dx, 0.5, dy, 0.5, 0):加权融合,可以手动控制两个方向权重。

拓展:梯度幅值的工程标准写法

上面用 addWeighted 是简化教学写法。真正数学上的梯度幅值是开平方:grad = sqrt(dx² + dy²),很多竞赛代码会这么写:

python 复制代码
import numpy as np
grad = np.sqrt(dx**2 + dy**2)
grad = cv2.convertScaleAbs(grad)

说明:学习阶段用加权融合已经够用;竞赛或追求更精确梯度幅值的项目,可以用上面这种开平方写法。

适用场景:噪声较多的图片,Sobel自带一定平滑效果;对边缘定位精度要求一般的场合。

缺点:边缘线条偏粗,会产生部分伪边缘。

三、Scharr沙尔算子(Sobel优化版,捕捉细小边缘)

Scharr 本质是一组精度更高的 3×3 Sobel 卷积核,专门用来弥补 ksize=3 时 Sobel 算子精度不足的问题。它并不是独立的新算法,而是 Sobel 的升级版本,卷积核固定 3×3,不需要设置 ksize,对微弱细小边缘更敏感。同样需要分开计算 x、y 方向,再合并结果。

python 复制代码
# opencv沙尔算子.py
import cv2
img = cv2.imread(r'D:\PYTHON\your name.jpg')
if img is None:
    print("图片读取失败,请检查路径")
    exit()
#x方向垂直边缘
dx = cv2.Scharr(img, cv2.CV_64F, dx=1, dy=0)
dx = cv2.convertScaleAbs(dx)
#y方向水平边缘
dy = cv2.Scharr(img, cv2.CV_64F, dx=0, dy=1)
dy = cv2.convertScaleAbs(dy)
#三种合并方式
res1 = dx + dy
res2 = cv2.add(dx, dy)
res3 = cv2.addWeighted(dx, 0.5, dy, 0.5, 0)
cv2.imshow('res1', res1)
cv2.imshow('res2', res2)
cv2.imshow('res3', res3)
while 1:
    key = cv2.waitKey(10)
    if key == ord('q'):
        break
cv2.destroyAllWindows()

适用:希望捕捉图片细微轮廓、纹理边缘。

四、Laplacian拉普拉斯算子(二阶微分,同时捕捉横竖边缘)

Sobel、Scharr属于一阶微分;Laplacian是二阶微分算子,可以同时捕捉水平+垂直两个方向边缘,也可以用来做图像锐化。通俗类比:一阶微分看像素变化快慢,二阶微分看变化的快慢程度,因此能同时抓取两个方向轮廓。

  • ksize为卷积核大小,取奇数;ksize越大轮廓越明显。
python 复制代码
# opencv拉普拉斯算子.py
import cv2
img = cv2.imread(r'D:\PYTHON\your name.jpg')
if img is None:
    print("图片读取失败,请检查路径")
    exit()

#先用CV_64F保存正负梯度,再用convertScaleAbs取绝对值
res1 = cv2.Laplacian(img, cv2.CV_64F, ksize=3)
res1 = cv2.convertScaleAbs(res1)
res2 = cv2.Laplacian(img, cv2.CV_64F, ksize=9)
res2 = cv2.convertScaleAbs(res2)
cv2.imshow("res1", res1)
cv2.imshow("res2", res2)
while 1:
    key = cv2.waitKey(10)
    if key == ord('q'):
        break
cv2.destroyAllWindows()

注意:拉普拉斯算子对噪声比较敏感,建议图片先做降噪滤波,再调用Laplacian

另外 ddepth 同样不要填-1 。Laplacian 是二阶微分,也会产生正负梯度,8 位图像 0-255 存不下负数,填-1会直接截断负数,丢失一半边缘信息。工程标准写法:先用cv2.CV_64F保存正负梯度,再用cv2.convertScaleAbs取绝对值转回正数。

五、Canny边缘检测(工业最常用双阈值边缘算法)

Canny是一套完整边缘检测算法,内部包含降噪、梯度计算、非极大值抑制、双阈值筛选。通俗理解四步:

高斯降噪 :提前平滑图像,抑制噪点;

Sobel 梯度计算 :求取像素梯度大小与方向;

非极大值抑制 :把粗边缘"削细",只保留边缘峰值;

双阈值筛选 :区分强边缘、弱边缘,过滤虚假边缘。

函数:cv2.Canny(图像, 低阈值, 高阈值)

规则:

  1. 像素高于高阈值 → 确定为强边缘,保留;
  2. 像素低于低阈值 → 直接丢弃;
  3. 介于高低阈值之间:只有和强边缘相连才保留,否则丢弃。

阈值调小,检测出来边缘细节变多;阈值调大,只保留明显的大轮廓。

python 复制代码
# opencv Canny边缘检测.py
import cv2
#直接读取为灰度图(工程惯例:Canny 输入灰度图)
img = cv2.imread(r'D:\PYTHON\your name.jpg', cv2.IMREAD_GRAYSCALE)
if img is None:
    print("图片读取失败,请检查路径")
    exit()
#三组不同阈值对比效果
res1 = cv2.Canny(img,60,130)
res2 = cv2.Canny(img,100,200)
res3 = cv2.Canny(img,80,150)
cv2.imshow("res1", res1)
cv2.imshow("res2", res2)
cv2.imshow("res3", res3)
while 1:
    key = cv2.waitKey(10)
    if key == ord('q'):
        break
cv2.destroyAllWindows()

调参经验:高阈值一般取低阈值的2~3倍。例如低阈值 60、高阈值 130,或低阈值 100、高阈值 200,都是比较常用的比例。这样既能保留主要轮廓,又能过滤掉大量弱边缘和噪点。如果边缘断裂严重,可以整体调低两个阈值;如果伪边缘太多,就整体调高。

Canny优势:输出的边缘线条细,干扰少,机器视觉项目高频使用。

实操小技巧:Canny 虽然可以直接接收彩色图,但工程惯例是输入灰度图。建议流程:先转灰度,再传入Canny之前用GaussianBlur做一次高斯模糊,抑制噪点带来的多余边缘。

六、全局阈值处理 threshold

全局阈值:整张图片使用同一个分割阈值,把像素划分为黑白两类,实现前景背景分离。

retval, dst = cv2.threshold(灰度图, thresh, maxval, type)

  • retval:实际使用的阈值;
  • dst:输出二值图像;

type常用模式:

  • THRESH_BINARY:大于阈值取maxval,其余0;
  • THRESH_BINARY_INV:反向二值,小于等于阈值取maxval,其余0。适合物体颜色暗、背景亮的场景,把前景目标变成白色,方便后续轮廓查找;
  • THRESH_TOZERO、THRESH_TRUNC等截断模式。

OTSU 大津阈值(自动计算最优阈值)

上面手动填 thresh 的方式需要自己试值。OTSU 大津法可以自动计算最优全局阈值,不用手动填写 thresh,用法如下:

python 复制代码
retval, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

这里 thresh 填 0 即可,OTSU 会根据灰度直方图自动算出最佳分割阈值并返回给 retval。光照均匀的场景非常好用,是工程里非常高频的写法。

python 复制代码
# OTSU大津阈值自动求阈值示例
import cv2
gray = cv2.imread(r'D:\PYTHON\your name.jpg', cv2.IMREAD_GRAYSCALE)
ret, dst = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
cv2.imshow("otsu", dst)
cv2.waitKey(0)
cv2.destroyAllWindows()
python 复制代码
# opencv阈值处理.py
import cv2
#直接读取为灰度图
img = cv2.imread(r'D:\PYTHON\your name.jpg',cv2.IMREAD_GRAYSCALE)
if img is None:
    print("图片读取失败,请检查路径")
    exit()
threshold_value = 100
max_value = 255
#二进制全局阈值
retval, thresholded_image = cv2.threshold(img, threshold_value, max_value, cv2.THRESH_BINARY)
cv2.imshow("thresholded_image", thresholded_image)
while 1:
    key = cv2.waitKey(10)
    if key == ord('q'):
        break
cv2.destroyAllWindows()

短板:如果图片光照不均匀,整张图用固定一个阈值效果很差,这时就要用自适应阈值。

七、自适应阈值 adaptiveThreshold(光照不均图片专用)

自适应阈值不会整张图片用固定数值;每个像素根据周边一小块区域自动计算本地阈值。适合明暗分布不均、局部光照变化大的照片。

cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)

  • blockSize:局部窗口大小,必须奇数;
  • C:减去的微调常数;
  • 两种算法:ADAPTIVE_THRESH_MEAN_C均值、ADAPTIVE_THRESH_GAUSSIAN_C高斯加权均值。
python 复制代码
# opencv阈值自适应.py
import cv2
img = cv2.imread(r'D:\PYTHON\your name.jpg',cv2.IMREAD_GRAYSCALE)
if img is None:
    print("图片读取失败,请检查路径")
    exit()
max_value = 255
adaptive_method = cv2.ADAPTIVE_THRESH_GAUSSIAN_C
threshold_type = cv2.THRESH_BINARY
block_size = 11
C = 2
new_img = cv2.adaptiveThreshold(img, max_value, adaptive_method, threshold_type, block_size, C)
cv2.imshow("new_img", new_img)
while 1:
    key = cv2.waitKey(10)
    if key == ord('q'):
        break
cv2.destroyAllWindows()

适用场景:书本扫描件、逆光拍摄图片、光照明暗变化大的图像。

快速选型口诀

光线均匀、光照稳定 → 优先全局阈值 + OTSU,速度快;

逆光、扫描文档、局部明暗差异大 → 直接自适应阈值;

自适应阈值速度慢,实时摄像头项目谨慎高频使用。

八、算子与阈值工具对比简表

工具 类型 核心作用 适合场景
Sobel 一阶微分 分别提取水平、垂直边缘 带噪声图像,一般精度边缘
Scharr 一阶微分 捕捉细小微弱边缘 需要提取细微纹理
Laplacian 二阶微分 同时提取双向边缘、锐化 图片提前降噪,追求轮廓强化
Canny 完整边缘算法 精细干净的轮廓边缘 机器视觉、物体轮廓提取(推荐首选)
threshold 全局阈值 阈值分割 整张图固定阈值二值化 光照均匀的图片
adaptiveThreshold 自适应阈值 阈值分割 局部动态计算阈值 光照不均匀、扫描文档类图片

九、综合案例:标准预处理流水线(降噪→灰度→Canny 边缘)

工程中标准流程:原图 → 高斯降噪 → 转灰度 → Canny 边缘检测

python 复制代码
import cv2
img = cv2.imread(r'D:\PYTHON\your name.jpg')
if img is None:
    print("图片读取失败,请检查路径")
    exit()
#1.高斯降噪
blur = cv2.GaussianBlur(img,(3,3),0)
#2.转为灰度图
gray = cv2.cvtColor(blur,cv2.COLOR_BGR2GRAY)
#3.Canny边缘提取
edge = cv2.Canny(gray,70,160)
cv2.imshow('原图',img)
cv2.imshow('Canny输出边缘',edge)
cv2.waitKey(0)
cv2.destroyAllWindows()

十、本章高频踩坑清单

下面把本章常见坑点按「梯度算子」「阈值处理」「Canny 边缘检测」「通用细节」四类整理,并为每一条配上错误写法与正确写法对比,方便对照排查。

1. 梯度算子类(Sobel / Scharr / Laplacian)

坑点 1:Sobel / Scharr 不要同时开启 dx=1, dy=1

错误写法:

python 复制代码
# 直接同时计算两个方向,边缘效果差
dxy = cv2.Sobel(img, cv2.CV_64F, dx=1, dy=1, ksize=5)

正确写法:

python 复制代码
# 分开计算 x、y 方向,再合并
dx = cv2.Sobel(img, cv2.CV_64F, dx=1, dy=0, ksize=5)
dy = cv2.Sobel(img, cv2.CV_64F, dx=0, dy=1, ksize=5)
dx = cv2.convertScaleAbs(dx)
dy = cv2.convertScaleAbs(dy)
res = cv2.addWeighted(dx, 0.5, dy, 0.5, 0)

坑点 2:Sobel / Scharr 的 ddepth 不要填 -1

错误写法:

python 复制代码
# 填 -1 会截断负数梯度,丢失半边边缘
dx = cv2.Sobel(img, -1, dx=1, dy=0, ksize=5)

正确写法:

python 复制代码
# 先用 CV_64F 保存正负梯度,再取绝对值
dx = cv2.Sobel(img, cv2.CV_64F, dx=1, dy=0, ksize=5)
dx = cv2.convertScaleAbs(dx)

坑点 3:Laplacian 的 ddepth 同样不要填 -1

错误写法:

python 复制代码
# 二阶微分也会产生正负梯度,填 -1 会截断负数
res = cv2.Laplacian(img, -1, ksize=3)

正确写法:

python 复制代码
# 同样用 CV_64F + convertScaleAbs
res = cv2.Laplacian(img, cv2.CV_64F, ksize=3)
res = cv2.convertScaleAbs(res)

坑点 4:Laplacian 对噪声敏感,使用前务必先降噪

错误写法:

python 复制代码
# 直接对含噪原图做拉普拉斯,会放大噪点
res = cv2.Laplacian(img, cv2.CV_64F, ksize=3)

正确写法:

python 复制代码
# 先高斯降噪,再做拉普拉斯
blur = cv2.GaussianBlur(img, (3, 3), 0)
res = cv2.Laplacian(blur, cv2.CV_64F, ksize=3)
res = cv2.convertScaleAbs(res)

坑点 5:Scharr 算子没有 ksize 参数

错误写法:

python 复制代码
# Scharr 卷积核固定 3×3,传 ksize 会报错
dx = cv2.Scharr(img, cv2.CV_64F, dx=1, dy=0, ksize=3)

正确写法:

python 复制代码
# 不需要设置 ksize
dx = cv2.Scharr(img, cv2.CV_64F, dx=1, dy=0)

2. 阈值处理类(threshold / adaptiveThreshold)

坑点 6:threshold、adaptiveThreshold 只能接收灰度图

错误写法:

python 复制代码
# 直接对彩色图做阈值处理会报错
ret, dst = cv2.threshold(img, 100, 255, cv2.THRESH_BINARY)

正确写法:

python 复制代码
# 先转灰度,再做阈值处理
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, dst = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)

坑点 7:adaptiveThreshold 的 blockSize 必须是奇数

错误写法:

python 复制代码
# blockSize 为偶数会报错
new_img = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                cv2.THRESH_BINARY, 10, 2)

正确写法:

python 复制代码
# blockSize 取奇数,如 11
new_img = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                cv2.THRESH_BINARY, 11, 2)

3. Canny 边缘检测类

坑点 8:Canny 工程惯例输入灰度图,标准流程是降噪→灰度→Canny

错误写法:

python 复制代码
# 直接对彩色原图做 Canny,不符合工程惯例
edge = cv2.Canny(img, 70, 160)

正确写法:

python 复制代码
# 标准流程:降噪 → 灰度 → Canny
blur = cv2.GaussianBlur(img, (3, 3), 0)
gray = cv2.cvtColor(blur, cv2.COLOR_BGR2GRAY)
edge = cv2.Canny(gray, 70, 160)

坑点 9:Canny 建议前置高斯模糊,否则会检测大量噪点伪边缘

错误写法:

python 复制代码
# 跳过降噪直接 Canny,噪点会被当成边缘
edge = cv2.Canny(gray, 70, 160)

正确写法:

python 复制代码
# 先高斯模糊抑制噪点,再 Canny
blur = cv2.GaussianBlur(gray, (3, 3), 0)
edge = cv2.Canny(blur, 70, 160)

4. 通用细节类

坑点 10:图片路径记得加 r 原始字符串,防止转义字符导致读取返回 None

错误写法:

python 复制代码
# 反斜杠被当成转义字符,路径错误
img = cv2.imread('D:\PYTHON\your name.jpg')

正确写法:

python 复制代码
# 加 r 原始字符串,路径原样解析
img = cv2.imread(r'D:\PYTHON\your name.jpg')

十一、本章知识小结

承接上一章滤波:本章的边缘检测与阈值分割,必须建立在良好降噪预处理之上。噪声不去除,后续边缘、二值结果全部不可用------噪点会被当成伪边缘,二值图也会出现大量黑白斑点。所以工程上始终遵循「先降噪、再提取」的顺序,这也是上一章滤波知识在本章反复被调用的原因。

  • Sobel、Scharr 属于一阶微分算子,分开获取水平、垂直方向边缘再合并;
  • Scharr 对微弱纹理更灵敏;
  • Laplacian 二阶微分算子,同时捕获两个方向轮廓,可用于锐化,但对噪声敏感;
  • Canny 是成熟完整边缘检测方案,双阈值筛选,输出干净细致物体轮廓,项目优先选用;
  • 全局阈值threshold整张图片使用固定分割值,适合光照均匀图像;
  • 自适应阈值adaptiveThreshold局部区域动态算阈值,解决光照不均匀图像二值化难题;
  • 工程通用流程:原图→滤波降噪→灰度转换→边缘检测 / 阈值分割。

下一章开始我们进入形态学操作,学习腐蚀、膨胀、开运算、闭运算,用来清理二值图里面的噪点小毛刺,为后续轮廓查找做准备。

相关推荐
其实防守也摸鱼42 分钟前
红队技能总结导图:从入门到精通的完整知识体系
开发语言·人工智能·学习·安全·web安全
青 春 记 忆1 小时前
LeetCode 121. 买卖股票的最佳时机|Python 解法详解
python·算法·leetcode
满怀冰雪1 小时前
21-图像分类实战:从 MNIST 到 CIFAR-10
人工智能·python·深度学习·分类·数据挖掘·paddlepaddle
Logintern092 小时前
Celery 的底层架构正是进程池、事件循环、epoll 和协程全部组合在了一起
python·架构·消息队列·进程·celery·事件循环
用户0332126663672 小时前
在 Word 文档快速中插入图片【Python 教程】
python
盖伦发发2 小时前
RAG 能跑≠能用:用 EDD 把 Eval 做成基础设施 (附源码)
人工智能·后端·python·功能测试
不会代码的小猴2 小时前
4. 控件学习2
开发语言·c++·笔记·qt
smallswan2 小时前
《Rust七十二变》开源了
开发语言·后端·rust·ai编程
郝学胜_神的一滴2 小时前
Python 高级编程 028:字典dict从入门到精通
python·ai编程