目录
大津阈值法(OTSU)
最大类间方差法(otsu)的公式推导:
记t为前景与背景的分割阈值,前景点数占图像比例为w0,平均灰度为u0;背景点数占图像比例为w1,平均灰度为u1。
则图像的总平均灰度为:u=w0*u0+w1*u1。
前景和背景图象的方差:g=w0*(u0-u)*(u0-u)+w1*(u1-u)*(u1-u)=w0*w1*(u0-u1)*(u0-u1),此公式为方差公式。
查找所有灰度级中的最大类方差,并返回最大类方差所对应的的灰度级,就是前景与背景的分割阈值。
#include <iostream>
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/opencv.hpp"
int OTSU(cv::Mat srcImage);
const char* WINDOW = "主窗口";
int main()
{
cv::Mat srcImage, srcGray;
cv::namedWindow(WINDOW,cv::WINDOW_AUTOSIZE);
//加载图像
srcImage = cv::imread("Qt.png",1);
if (!srcImage.data)
{
std::cout << "加载图像失败"<<std::endl;
return -1;
}
resize(srcImage, srcImage,cv::Size(srcImage.cols/3, srcImage.rows/3));
imshow(WINDOW, srcImage);
//灰度转换
cvtColor(srcImage, srcGray, CV_RGB2GRAY);
cv::imshow("srcGray", srcGray);
//调用OTSU阈值算法得到阈值
int ostuThreshold = OTSU(srcGray);
std::cout << ostuThreshold << std::endl;
// 定义输出结果图像
cv::Mat otsuResultImage =cv::Mat::zeros(srcGray.rows, srcGray.cols, CV_8UC1);
// 利用得到的阈值实现二值化操作
for (int i = 0; i < srcGray.rows; i++)
{
for (int j = 0; j < srcGray.cols; j++)
{
//满足大于阈值ostuThreshold置255
if (srcGray.at<uchar>(i, j) > ostuThreshold)
{
otsuResultImage.at<uchar>(i, j) = 255;
}
else
{
otsuResultImage.at<uchar>(i, j) = 0;
}
}
}
imshow("otsuResultImage", otsuResultImage);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
/*
@ 大津阈值法函数实现
@ srcImage:输入图像,灰度图
@ return int 阈值
*/
int OTSU(cv::Mat srcImage)
{
int nCols = srcImage.cols;
int nRows = srcImage.rows;
int threshold = 0;
// 初始化统计参数
int nSumPix[256];
float nProDis[256];
for (int i = 0; i < 256; i++)
{
nSumPix[i] = 0;
nProDis[i] = 0;
}
//统计灰度级中每个像素在整幅图像中的个数
for (int i = 0; i < nRows; i++)
{
for (int j = 0; j < nCols; j++)
{
nSumPix[(int)srcImage.at<uchar>(i, j)]++;
}
}
//计算每个灰度级占图像中的概率分布,平均数
for (int i = 0; i < 256; i++)
{
nProDis[i] = (float)nSumPix[i] / (nCols * nRows);
}
// 遍历灰度级[0,255],计算出最大类间方差下的阈值
float w0, w1, u0_temp, u1_temp, u0, u1, delta_temp;
double delta_max = 0.0;
for (int i = 0; i < 256; i++)
{
// 初始化相关参数
w0 = w1 = u0_temp = u1_temp = u0 = u1 = delta_temp = 0;
for (int j = 0; j < 256; j++)
{
//背景部分
if (j <= i)
{
//当前i为分割阈值,第一类总的概率
w0 += nProDis[j];
u0_temp += j * nProDis[j];
}
//前景部分
else
{
// 当前i为分割阈值,第一类总的概率
w1 += nProDis[j];
u1_temp += j * nProDis[j];
}
}
// 分别计算各类的平均灰度
u0 = u0_temp / w0;
u1 = u1_temp / w1;
delta_temp = (float)(w0 *w1* pow((u0 - u1), 2));
// 依次找到最大类间方差下的阈值
if (delta_temp > delta_max)
{
delta_max = delta_temp;
threshold = i;
}
}
return threshold;
}
固定阈值法
图像二值化其实就是阈值操作,先设定一个阈值,然后将灰度图的单通道像素值与固定阈值进行比较,根据比较的结果(大于或者小于),划分为两种的不同的颜色或者灰度,对于二值图像可以更好更好进行边缘检测,然后寻找轮廓。
流程图
固定阈值操作:Threshold()函数
函数Threshold()对单通道数组应用固定阈值操作,阈值可以设定为初始值或者加上一个轨迹条进行控制。该函数的典型应用是对灰度图像进行阈值操作得到二值图像,中间可以进行一下滤波操作,滤除掉很小或者很大像素值的图像点。(注意标红的地方)
函数原型
double threshold( InputArray src, OutputArray dst,
double thresh, double maxval, int type );
src:输入图像或输入数组,单通道灰度图,8或32位浮点类型的Mat。
dst:输出图像或输出数组,经过阈值操作后得到的二值图。
thresh:设定的阈值。
maxval:最大范围值,一般为255,根据最后一个参数type的类型来确定。
type:阈值类型,通常使用的有5种类型,分别是CV_THRESH_BINARY、CV_THRESH_BINARY_INV、CV_THRESH_TRUNC、CV_THRESH_TOZERO、CV_THRESH_TOZERO_INV,这5中类型可以分别用数字0、1、2、3、4来代替使用,不要超过4,不然会报错(还有一个大津阈值法是8)。
5种类型分别对应的阈值操作方法:
CV_THRESH_BINARY(可以用标识符0代替,意思是大于阈值为maxval(白),否则为0(黑))
CV_THRESH_BINARY_INV(可以用标识符1代替,意思是大于阈值为0(黑),否则为maxval(白))
CV_THRESH_TRUNC(可以用标识符2代替,意思是大于阈值为threshold(阈值),否则为src(x,y)(本色或原色))
CV_THRESH_TOZERO(可以用标识符3代替,意思是大于阈值为src(x,y)(本色或原色),否则为0(黑))
CV_THRESH_TOZERO_INV(可以用标识符4代替,意思是大于阈值为0(黑),否则为src(x,y)(本色或原色))
代码示例:
/************************************************************************
<*@创建者:OYXL
<*@时间:2018/6/20
<*@实现功能:图片或者视频进行二值化,可选择
<*@备注:图片二值化,显示图像必须和二值响应函数放在一块,不然不会执行
<*@备注:on_Threshold( int, void* );不可更改参数
************************************************************************/
#include "opencv2/opencv.hpp"
#include "iostream"
using namespace std;
using namespace cv;
int Picture();
int Video();
void on_Threshold( int, void* );//回调函数
void ChooseMode();
Mat img,gray,binary;
int thr=100;
int num=0;
int BinaryType=2;
int main()
{
ChooseMode();
if (num==1)
{
Picture();
}
else if (num==2)
{
Video();
}
return 0;
}
//<图片二值化函数
int Picture()
{
namedWindow("TrackBar", CV_WINDOW_AUTOSIZE); //create a window called "Control"
createTrackbar("THR", "TrackBar", &thr, 255,on_Threshold); //Hue (0 - 180)
img=imread("F:\\磊神图片\\9527.png");
if (!img.data)
{
cout << "picture failed to load !" << endl;
return -1;
}
cvtColor(img, gray, CV_BGR2GRAY);//<必须先进行二值化操作,不然得到的二值图是彩色的
GaussianBlur(gray, gray, Size(15, 15), 0, 0);
on_Threshold(0,0);
//threshold(gray, binary, thr, 255, 4);
imshow("src", img);
waitKey(0);
}
//<视频二值化函数
int Video()
{
namedWindow("TrackBar", CV_WINDOW_AUTOSIZE); //create a window called "Control"
createTrackbar("THR", "TrackBar", &thr, 255,0); //Hue (0 - 180)
VideoCapture capture("F:\\磊神视频\\noglasses.avi");
while (true)
{
capture >> img;
if (img.empty()) //判断摄像头是否打开
{
cout << "Video failed to load !" << endl;
return -1;
}
cvtColor(img, gray, CV_BGR2GRAY);
GaussianBlur(gray, gray, Size(15, 15), 0, 0);
threshold(gray, binary, thr, 255, BinaryType);
imshow("src", img);
imshow("binary image", binary);
waitKey(1);
}
}
/************************************************************************
<*@阈值回调函数
<*@显示二值图片需要放在这里,响应一次,显示一次
************************************************************************/
void on_Threshold( int, void* )
{
//<调用阈值函数
threshold(gray,binary,thr,255,BinaryType);
imshow("binary image", binary);
}
void ChooseMode()
{
cout<<"Please enter the type you want to select:"<<endl;
cout<<"0、THRESH_BINARY"<<endl; //<大于阈值为白,小于为黑
cout<<"1、THRESH_BINARY_INV"<<endl; //<大于阈值为黑,小于为白
cout<<"2、THRESH_TRUNC"<<endl; //<大于阈值为阈值,小于为原值
cout<<"3、THRESH_TOZERO"<<endl; //<大于阈值为原值,小于为黑
cout<<"4、THRESH_TOZERO_INV"<<endl; //<小于阈值为原值,于为黑
cin>>BinaryType;
cout<<"Please enter the mode you want to select:"<<endl;
cout<<"1、Picture Mode"<<endl;
cout<<"2、Video Mode"<<endl;
cin>>num;
}
代码分析:
示例代码中可以分别对图片和视频进行阈值操作,不过有些地方需要注意,对于图片来说,需要添加轨迹条响应函数(跟鼠标响应函数一样),而视频则不需要,可以直接使用Threshold()函数,为了适应轨迹条的阈值,需要将显示二值图放在轨迹条响应函数里面,每滑动一次轨迹条,就显示一次图片,轨迹条响应函数参数不可更改。另外,注意图片和视频的路径是否正确。
效果图:
自适应阈值
adaptiveThreshold函数:通过计算每个像素位置周围的b*b区域的加权平均值然后减去常数C得到阈值,b为blockSize。相对于一般的阈值化操作,当图像中出现较大的明暗差异时,自适应阈值法非常有效。
void adaptiveThreshold( InputArray src, OutputArray dst,
double maxValue, int adaptiveMethod,
int thresholdType, int blockSize, double C );
src | 8位单通道图像。 |
dst | 与src具有相同大小和相同类型的目标图像 |
maxValue | 非0的最大像素值 |
adaptiveMethod | 要使用的自适应阈值算法,0:ADAPTIVE_THRESH_MEAN_C,1: ADAPTIVE_THRESH_GAUSSIAN_C |
thresholdType | 阈值类型必须是THRESH_BINARY或THRESH_BINARY_INV |
blockSize | 用于计算像素阈值的像素邻域的大小:3,5,7等。 |
C | 从平均值或加权平均值中减去常数。通常情况下,它是正数,但也可能为零或负数。 |
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
Mat srcImage, srcGray;
// 图像读取及判断
srcImage = cv::imread("Qt.png");
if (!srcImage.data)
{
cout << "-------- 加载图像失败 ---------"<<endl;
return -1;
}
resize(srcImage, srcImage,Size(srcImage.cols/3, srcImage.rows/3));
cv::imshow("主窗口", srcImage);
//灰度转换
cvtColor(srcImage, srcGray, CV_RGB2GRAY);
imshow("srcGray", srcGray);
Mat dstImage;
//初始化自适应阈值参数
int blockSize = 5;
int constValue = 10;
const int maxVal = 255;
//初始化自适应方法和阈值类型
int adaptiveMethod = 0;
int thresholdType = 1;
// 图像自适应阈值操作
adaptiveThreshold(srcGray, dstImage, maxVal, adaptiveMethod, thresholdType, blockSize,constValue);
imshow("dstImage", dstImage);
waitKey(0);
destroyAllWindows();
return 0;
}
双阈值法
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat srcImage;
//图像读取及判断
srcImage = cv::imread("Qt.png");
if (!srcImage.data)
{
cout << "---------- 加载图像失败 ----------" << endl;
return -1;
}
resize(srcImage, srcImage,Size(srcImage.cols/3, srcImage.rows / 3));
imshow("主窗口", srcImage);
//灰度转换
Mat srcGray;
cvtColor(srcImage, srcGray, CV_RGB2GRAY);
imshow("srcGray", srcGray);
//初始化阈值参数
const int maxVal = 255;
int low_threshold = 150;
int high_threshold = 210;
Mat dstTempImage1, dstTempImage2, dstImage;
//小阈值对源灰度图像进行阈值化操作
threshold(srcGray, dstTempImage1,low_threshold, maxVal, cv::THRESH_BINARY);
//大阈值对源灰度图像进行阈值化操作
threshold(srcGray, dstTempImage2,high_threshold, maxVal, cv::THRESH_BINARY_INV);
//矩阵与运算得到二值化结果
bitwise_and(dstTempImage1,dstTempImage2, dstImage);
imshow("dstImage", dstImage);
waitKey(0);
destroyAllWindows();
return 0;
}
半阈值法
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
// 读取源图像及判断
Mat srcImage = imread("Qt.png");
if (!srcImage.data)
{
cout << "" << endl;
return -1;
}
resize(srcImage, srcImage,Size(srcImage.cols/3, srcImage.rows/3));
imshow("主窗口", srcImage);
// 转化为灰度图像
Mat srcGray;
cvtColor(srcImage, srcGray, CV_RGB2GRAY);
imshow("srcGray", srcGray);
// 初始化阈值参数
const int maxVal = 255;
int thresholdVal = 200;
Mat dstTempImage, dstImage;
// 阈值对源灰度图像进行阈值化操作
threshold(srcGray, dstTempImage,thresholdVal, 255, THRESH_BINARY);
// 矩阵与运算得到二值化结果
bitwise_and(srcGray, dstTempImage, dstImage);
imshow("dstImage", dstImage);
waitKey(0);
destroyAllWindows();
return 0;
}