OpenCV图像分割实战:阈值分割与边缘检测算法详解
本文详细解析基于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();
}
以上为本文全部内容,希望对学习有所帮助,欢迎支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









