otsu(大津法二值化),Kitttle算法相关实现

图像二值化

二值化(Thresholding)

二值化是将图像使用黑和白两种值表示的方法,就是将图像上的像素点的灰度值设置为0或255,也就是将整个图像呈现出明显的黑白效果的过程。
(其中0是黑,255是白)。


#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>

int main(int argc, const char* argv[]){
    cv::Mat img = cv::imread("imagepath", cv::IMREAD_COLOR);
    int height = img.rows;
    int width  = img.cols;

    int th = 128;//阈值,Threshold value

    cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
    //灰度化
    for (int j=0; j<height; j++){
    for (int i=0; i<width; i++){
      uchar val = (int)((float)img.at<cv::Vec3b>(j,i)[0] * 0.0722 +	\
			(float)img.at<cv::Vec3b>(j,i)[1] * 0.7152 +	\
			(float)img.at<cv::Vec3b>(j,i)[2] * 0.2126);
      if (val < th)
	  {
		  val = 0;
      } 
	  else 
	  {
		  val = 255;
      }
      out.at<uchar>(j,i) = val;//通过跟阈值比较进行二值化
    }
  }
  
  //cv::imwrite("out.jpg", out);
  cv::imshow("answer", out);
  cv::waitKey(0);
  cv::destroyAllWindows();

  return 0;



}

::: tip 阈值

RGB三彩图需要先将图片灰度化,再对图像像素设置一个阈值进行二值化操作。

阈值,可以看作一个分界指,两边的像素(大于阈值或者小于阈值)赋值是0或者255.

:::
但是图片二值化过程中人工确定阈值往往效果不好。于是有了otsu,大津法二值化提出。

大津二值化算法(Otsu’s Method)

大津法(OTSU)是一种图像灰度自适应的阈值分割算法,是1979年由日本学者大津提出,并由他的名字命名的。大津法按照图像上灰度值的分布,将图像分成背景和前景两部分看待,前景就是我们要按照阈值分割出来的部分。背景和前景的分界值就是我们要求出的阈值。遍历不同的阈值,计算不同阈值下对应的背景和前景之间的类内方差,当类内方差取得极大值时,此时对应的阈值就是大津法(OTSU算法)所求的阈值。
来源

::: tip 算法描述

对于图像I(x,y),前景(即目标,白色部分)和背景的分割阈值记作T,属于前景(大于阈值)的像素点数占整幅图像的比例记为ω0,其平均灰度μ0;

背景像素(小于阈值)点数占整幅图像的比例为ω1,其平均灰度为μ1。图像的总平均灰度记为μ1,类间方差记为g。

假设图像的背景较暗,并且图像的大小为M×N,图像中像素的灰度值大于阈值T的像素个数记作N0,像素灰度小于阈值T的像素个数记作N1,

则有:

ω0=N0/ M×N (1)

ω1=N1/ M×N (2)

N0+N1=M×N (3)

ω0+ω1=1    (4)

μ=ω0μ0+ω1μ1 (5)

g=ω0(μ0-μ)2+ω1(μ1-μ)2 (6)

将式(5)代入式(6),得到等价公式:

g=ω0ω1(μ0-μ1)^2 (7) 

这个就是类间方差的公式表述,采用遍历的方法得到使类间方差g最大的阈值T,即为所求。

:::

实现代码

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>

int main(int argc, const char* argv[]) {
	cv::Mat img = cv::imread("imagepath", cv::IMREAD_COLOR);

	int height = img.rows;
	int width = img.cols;

	cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);

	// gray灰度化
	int val = 0;
	for (int j = 0; j < height; j++) {
		for (int i = 0; i < width; i++) {
			val = (int)((float)img.at<cv::Vec3b>(j, i)[0] * 0.0722 + \
				(float)img.at<cv::Vec3b>(j, i)[1] * 0.7152 + \
				(float)img.at<cv::Vec3b>(j, i)[2] * 0.2126);
			out.at<uchar>(j, i) = (uchar)val;
		}
	}

	// determine threshold
	double w0 = 0, w1 = 0;
	double N = 0, N1 = 0;
	double max_g = 0, g = 0;
	int th = 0;

	for (int T = 0; T < 255; T++)//遍历求阈值
	{
		w0 = 0;
		w1 = 0;
		N = 0;
		N1 = 0;
		for (int j = 0; j < height; j++)
		{
			for (int i = 0; i < width; i++)
			{
				val = (int)(out.at<uchar>(j, i));
				if (val > T) {
					w0++;
					N += val;
				}
				else {
					w1++;
					N1 += val;
				}
			}
		}

		N /= w0;//前景平均灰度
		N1 /= w1;//背景平均灰度
		w0 /= (height * width);//比例
		w1 /= (height * width);
		g = w0 * w1 * pow((N - N1), 2);//类间方差

		if (g > max_g) { //判断最大类间方差
			max_g = g;
			th = T;
		}

	}

	// binalization二值化
	for (int j = 0; j < height; j++)
	{
		for (int i = 0; i < width; i++)
		{
			val = (int)(out.at<uchar>(j, i));
			if (val < th)
			{
				val = 0;
			}
			else {
				val = 255;
			}
			out.at<uchar>(j, i) = (uchar)val;
		}
	}

	std::cout << "threshold >> " << th << std::endl;

	//cv::imwrite("out.jpg", out);
	cv::imshow("answer", out);
	cv::waitKey(0);
	cv::destroyAllWindows();

	return 0;

}

Kittle算法

Kittler算法与Otsu方法效果接近,但速度更快(看应用场景),更适宜应用于像素质量较高的图像中。

它的中心思想是,计算整幅图像的梯度灰度的平均值,以此平均值做为阈值。

图像梯度

在微积分中,一维函数的一阶微分的基本定义是这样的:


而图像是一个二维函数f(x,y),其微分当然就是偏微分。因此有:

图像是一个离散的二维函数,ϵ不能无限小,我们的图像是按照像素来离散的,最小的ϵ就是0像素。因此,上面的图像微分又变成了如下的形式(ϵ=1):

这分别是图像在(x, y)点处x方向和y方向上的梯度,从上面的表达式可以看出来,图像的梯度相当于2个相邻像素之间的差值.

我们先考虑下x方向,选取某个像素,假设其像素值是100,沿x方向的相邻像素分别是90,90,90,则根据上面的计算其x方向梯度分别是10,0,0,如图:

我们看到,相加后的新图像,原图像像素点100与90亮度只相差10,现在是110与90,亮度相差20了,对比度显然增强了,尤其是图像中物体的轮廓和边缘,与背景大大加强了区别,这就是用图像梯度来增强图像的原理。y方向同理,而x和y结合可以用如下式子表示在一起:

由于计算量比较大,于是一般用绝对值来近似平方和平方根的操作,来降低计算量:

代码:

/*该代码未必合理,主要是在梯度灰度平均值的计算原理上有偏差*/
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>

int main(int argc, const char* argv[]) {
	cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);

	int height = img.rows;
	int width = img.cols;

	cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
	int tmp = 0;
	// gray
	int val = 0;
	for (int j = 0; j < height; j++) {
		for (int i = 0; i < width; i++) {
			val = (int)((float)img.at<cv::Vec3b>(j, i)[0] * 0.0722 + \
				(float)img.at<cv::Vec3b>(j, i)[1] * 0.7152 + \
				(float)img.at<cv::Vec3b>(j, i)[2] * 0.2126);
			out.at<uchar>(j, i) = (uchar)val;
			tmp += val;
		}
	}
	double grad = 0.0;
//梯度灰度计算
	for (int ii = 0; ii < height-1; ii++)
	{
		for (int jj = 0; jj < width - 1; jj++) 
		{
			
			double dx = out.at<uchar>(ii, jj + 1) - out.at<uchar>(ii, jj);
			double dy = out.at<uchar>(ii+ 1, jj) - out.at<uchar>(ii, jj);
			//double ds = std::sqrt((dx*dx + dy * dy) / 2);//求法有异议,看到部分资料没有除以二,有的部分有除以二
			double ds = abs(dx) + abs(dy);
			grad += ds;
		}

	}
	double imageAvG = grad + tmp;
	int th = imageAvG / (height*width);
	//二值化
	for (int j = 0; j < height; j++)
	{
		for (int i = 0; i < width; i++)
		{
			val = (int)(out.at<uchar>(j, i));
			if (val < th)
			{
				val = 0;
			}
			else {
				val = 255;
			}
			out.at<uchar>(j, i) = (uchar)val;
		}
	}

	std::cout << "threshold >> " << th << std::endl;
	
	//cv::imwrite("out.jpg", out);
	cv::imshow("answer", out);
	cv::waitKey(0);
	cv::destroyAllWindows();

	return 0;

}

参考链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

J先生x

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值