OpenCV图像分割实战:阈值分割与边缘检测算法详解

凌晨两点半
凌晨两点半
发布时间:2026-10-14| 4人浏览过
原创

本文详细解析基于OpenCV的图像分割技术,涵盖基本全局阈值法与Otsu最大类间方差法的原理及C++实现。同时深入探讨Roberts、Sobel、Laplacian及Canny等边缘检测算子,通过对比先阈值分割后边缘检测与直接边缘检测的效果,结合完整代码示例,为图像预处理提供实用参考。

本文分享基于OpenCV实现图像分割的具体代码与实现细节,供开发者参考。

一、实验目的

深入理解图像阈值分割与边缘检测的基本原理。掌握基本全局阈值方法及最大类间方差法(Otsu法)的原理,并完成编程实现。同时,通过编程实践掌握图像边缘检测技术。

二、实验内容与要求

通过编程实现图像的阈值分割(包括基本全局阈值方法和Otsu法)以及图像的边缘检测。

三、实验主要仪器设备和材料

计算机硬件环境,软件配置为Visual Studio 2017集成OpenCV库。

四、实验原理与方法

图像阈值分割的基本原理

图像二值化是图像分割的核心环节,旨在将图像像素灰度值设定为0或255,从而呈现明显的黑白对比效果。设I为原图像,R为二值化后的图像,二值化过程可用公式表示:

其中thr为选定的阈值。二值化逻辑为:若原图像素灰度值大于阈值,则置为白色(255),否则置为黑色(0)。通过选取合适的阈值,将256级灰度图像转换为仅含两个灰度级的图像,这在图像处理分析中至关重要,尤其在实用场景中。根据阈值应用范围,可分为全局阈值方法(Global Thresholding)和局部阈值方法(Local/Adaptive Thresholding)。后者也称为动态阈值,具体实现可参考相关图像处理专著。

1. 基本全局阈值方法

该方法假设全图像素共享同一阈值thr,具体步骤如下: (1)选取一个初始估计值T; (2)利用T分割图像,生成两组像素集合:G1包含所有灰度值大于T的像素,G2包含所有灰度值小于或等于T的像素; (3)分别计算G1和G2中所有像素的平均灰度值u1和u2; (4)计算新阈值:T = (u1 + u2) / 2; (5)重复步骤(2)至(4),直至新旧T值之差小于预设参数T0。

2. Otsu方法的算法步骤

(1)计算图像的归一化直方图; (2)设定分类阈值i(即灰度级),从0开始迭代; (3)基于归一化直方图,统计0至i灰度级像素(背景)占整幅图像的比例w0,并计算背景像素平均灰度u0;同时统计i至255灰度级像素(前景)占整幅图像的比例w1,并计算前景像素平均灰度u1; (4)计算前景与背景之间的方差 g = w0 * w1 * (u0 - u1)^2; (5)i递增,直至i达到256时结束迭代; (6)选取使g最大的i值作为图像的全局阈值。

边缘检测

图像边缘检测可借助一阶或二阶微分实现。常见的一阶边缘检测算子包括Roberts算子、Prewitt算子和Sobel算子;二阶算子主要为Laplacian算子。由于二阶算子对噪声敏感,通常需先进行图像平滑处理,例如LOG算子先进行高斯平滑,再进行拉普拉斯变换并求零交叉点。Canny算子则被公认为最优的边缘检测算子。

五、实验内容

1. 图像的阈值分割

以车牌图像为例,编写代码分别实现基本全局阈值法和Otsu法,并对比两种方法的分割结果。

2. 边缘检测

使用边缘检测算子对车牌图像进行处理,可选用梯度算子、Laplacian算子或Canny算子(OpenCV内置Canny函数)。需对比分析“先进行阈值分割再进行边缘检测”与“直接对原图像进行边缘检测”两种情况的结果差异。注意:此处仅需提取灰度边缘。

代码实现


#include "pch.h"
#include <iostream> 

#include <opencv2/opencv.hpp>  
using namespace std;
using namespace cv;

// 拉普拉斯锐化函数
void LaplacianSharpDeal(const Mat &src, Mat &dst) {
 if (!src.data)return;
 for (int i = 0; i < src.rows; ++i)
  for (int j = 0; j < src.cols; ++j) {
   float a;
   if (i > 1 && i < src.rows - 1 && j > 1 && j < src.cols - 1) {
    a = 5 * (float)src.at<uchar>(i, j) - (float)src.at<uchar>(i - 1, j) - (float)src.at<uchar>(i, j - 1) -
     (float)src.at<uchar>(i, j + 1) - (float)src.at<uchar>(i + 1, j);
   }
   else {//边缘赋值
    a = src.at<uchar>(i, j);
   }
   if (a > 255 || a < 0) {
    dst.at<uchar>(i, j) = src.at<uchar>(i, j);
   }
   else {
    dst.at<uchar>(i, j) = a;
   }
  }
}

// 基本全局阈值方法函数
int BasicGlobalThreshold(Mat src, float oldValue)
{ 
 int cols = src.cols;
 int rows = src.rows;
 float G1 = 0;
 float G2 = 0;
 float g1 = 0;
 float g2 = 0;
 float u1 = 0;
 float u2 = 0;
 float T0 = 0;
 // 计算灰度直方图分布,统计像素数和频率
 for (int i = 0; i < rows; i++)
 {
  for (int j = 0; j < cols; j++)
  {
   if (src.at<uchar>(i, j) > oldValue)
   {
    G1 += src.at<uchar>(i, j);
    g1 += 1;
   }
   else
   {
    G2 += src.at<uchar>(i, j);
    g2 += 1;
   }
  }
 }
 u1 = G1 / g1;
 u2 = G2 / g2;
 T0 = (u1 + u2) / 2;
 std::cout << T0 << std::endl;
 if (abs(oldValue - T0) < 0.1) {
  return T0;
 }
 else
 {
  BasicGlobalThreshold(src, T0);
 }
}

// Otsu方法函数
int Otsu(Mat src)
{
 int cols = src.cols;
 int rows = src.rows;
 int nPixelNum = cols * rows;
 // 初始化
 int pixelNum[256];
 double probability[256];
 for (int i = 0; i < 256; i++)
 {
  pixelNum[i] = 0;
  probability[i] = 0.0;
 }
 // 统计像素数和频率
 for (int i = 0; i < rows; i++)
 {
  for (int j = 0; j < cols; j++)
  {
   pixelNum[src.at<uchar>(i, j)]++;
  }
 }
 for (int i = 0; i < 256; i++)
 {
  probability[i] = (double)0.1*pixelNum[i] / nPixelNum;
 }
 // 计算
 int Threshold = 0;          // 最佳阈值
 double MaxDelta = 0.0;      // 最大类间方差
 double Mean_0 = 0.0;        // 左边平均值
 double Mean_1 = 0.0;        // 右边平均值
 double Delta = 0.0;         // 类间方差
 double Mean_0_temp = 0.0;   // 左边平均值中间值
 double Mean_1_temp = 0.0;   // 右边平均值中间值
 double Probability_0 = 0.0;       // 左边频率值
 double Probability_1 = 0.0;       // 右边频率值
 for (int j = 0; j < 256; j++)
 {
  for (int i = 0; i < 256; i++)
  {
   if (i < j)// 前半部分
   {
    Probability_0 += probability[i];
    Mean_0_temp += i * probability[i];
   }
   else      // 后半部分
   {
    Probability_1 += probability[i];
    Mean_1_temp += i * probability[i];
   }
  }
  // 计算平均值
  // Mean_0_teamp计算的是前半部分的灰度值的总和除以总像素数,
  // 所以要除以前半部分的频率才是前半部分的平均值,后半部分同样
  Mean_0 = Mean_0_temp / Probability_0;
  Mean_1 = Mean_1_temp / Probability_1;
  Delta = (double)(Probability_0 * Probability_1 * pow((Mean_0 - Mean_1), 2));
  if (Delta > MaxDelta)
  {
   MaxDelta = Delta;
   Threshold = j;
  }
  // 相关参数归零
  Probability_0 = 0.0;
  Probability_1 = 0.0;
  Mean_0_temp = 0.0;
  Mean_1_temp = 0.0;
  Mean_0 = 0.0;
  Mean_1 = 0.0;
  Delta = 0.0;
 }
 return Threshold;
}

void main() {
 Mat image = imread("A1.bmp", 0);
 Mat image1,image2;
 Mat image3(image.size(), image.type());
 Mat image4(image.size(), image.type());

 std::cout << "基本全局阈值方法" << std::endl;

 int OstuThreshold1 = BasicGlobalThreshold(image, 0.01);
 int OstuThreshold2 = Otsu(image);

 std::cout << "Otsu方法" << std::endl;
 std::cout << OstuThreshold2 << std::endl;
 threshold(image, image1, OstuThreshold1, 255, CV_THRESH_OTSU);
 threshold(image, image2, OstuThreshold2, 255, CV_THRESH_OTSU);

 LaplacianSharpDeal(image2, image3);
 LaplacianSharpDeal(image, image4);
 
 imshow("基本全局阈值方法", image1);
 imshow("Otsu方法", image2);
 imshow("先阈值分割后边缘检测", image3);
 imshow("直接对图像进行边缘检测", image4);
 waitKey();
}

以上为本文全部内容,希望对学习有所帮助,欢迎支持本站。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。