1:大津二值化
二值化:二值化是将图像使用黑和白两种颜色表示的方法,可以凸显图像的轮廓。
我们将灰度的阈值设置为N来进行二值化,即:
一张图片采用不同的阀值进行二值化后得到的图像差距很大,如下图所示:
原图:
以下分别是阀值为50、 70、150时二值化的结果:
可以看到只有合适的阀值才能很好的二值化图像,如何自动的确定阀值呢?
大津二值化算法(OTSU)算法是由日本学者OTSU于1979年提出的一种对图像进行二值化的高效算法,作用是确定将图像分成黑白两个部分的阈值。
当取最佳阈值时,背景应该与前景差别最大,关键在于如何选择衡量差别的标准,而在otsu算法中这个衡量差别的标准就是最大类间方差。
大津算法,也被称作最大类间方差法,是一种可以自动确定二值化中阈值的算法:
g:类间方差(那个灰度的g最大,哪个灰度就是需要的阈值t)
g = w0 * (u0 - u)^2 + w1 * (u1 - u)^2
根据上面的关系,可以推出:
g = w0 * w1 * (u0 - u1) ^ 2
然后,遍历每一个灰度值,找到这个灰度值对应的 g,找到最大的 g 对应的 t。
w0:前景像素数量占总像素数量的比例
w1:背景像素数量占总像素数量的比例
u0:前景平均灰度
u1:背景平均灰度
u:平均灰度
大津二值化算法后的图像:
阀值:103
可以明显感受到该阀值能显著的分离前景和背景。
大津二值化代码实现:
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
using namespace std;
// BGR -> Gray
cv::Mat BGR2GRAY(cv::Mat img){
// get height and width
int width = img.cols;
int height = img.rows;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// BGR -> Gray
out.at<uchar>(y, x) = 0.2126 * (float)img.at<cv::Vec3b>(y, x)[2] \
+ 0.7152 * (float)img.at<cv::Vec3b>(y, x)[1] \
+ 0.0722 * (float)img.at<cv::Vec3b>(y, x)[0];
}
}
return out;
}
// Gray -> Binary
cv::Mat Binarize_Otsu(cv::Mat gray){
int width = gray.cols;
int height = gray.rows;
// determine threshold
double w0 = 0, w1 = 0;
double m0 = 0, m1 = 0;
double max_sb = 0, sb = 0;
int th = 0;
int val;
// Get threshold
for (int t = 0; t < 255; t++){
w0 = 0;
w1 = 0;
m0 = 0;
m1 = 0;
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
val = (int)(gray.at<uchar>(y, x));
if (val < t){
w0++;
m0 += val;
} else {
w1++;
m1 += val;
}
}
}
m0 /= w0;
m1 /= w1;
w0 /= (height * width);
w1 /= (height * width);
sb = w0 * w1 * pow((m0 - m1), 2);
if(sb > max_sb){
max_sb = sb;
th = t;
}
}
std::cout << "threshold:" << th << std::endl;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// Binarize
if (gray.at<uchar>(y, x) > th){
out.at<uchar>(y, x) = 255;
} else {
out.at<uchar>(y, x) = 0;
}
}
}
return out;
}
int main(int argc, const char* argv[])
{
if(argc <2)
{
cout << "input error" << endl;
return 0;
}
// read image
cv::Mat img = cv::imread(argv[1], cv::IMREAD_COLOR);
// BGR -> Gray
cv::Mat gray = BGR2GRAY(img);
// Gray -> Binary
cv::Mat out = Binarize_Otsu(gray);
cv::imwrite("out.jpg", out);
//cv::imshow("sample", out);
//cv::waitKey(0);
//cv::destroyAllWindows();
return 0;
}
2:膨胀
腐蚀和膨胀是数学形态学上的名词,如果用于图像处理上则就称为图像二值形态学。
腐蚀和膨胀是对白色部分(高亮部分)而言的,需要先对图像进行二值化。膨胀就是图像中的高亮部分进行膨胀,“领域扩张”,效果图拥有比原图更大的高亮区域。腐蚀就是原图中的高亮部分被腐蚀,“领域被蚕食”,效果图拥有比原图更小的高亮区域。
形态学处理理中的膨胀算法如下。对于待操作的像素(x,y),如果(x+1, y)、(x-1, y)、(x, y+1)、(x, y-1)中不论哪一个为255.则像素(x,y)为255。
换句话说,如果将上面的操作执行两次,则可以扩大两格。
在实际进行形态学处理的时候,待操作的像素与矩阵
相乘,结果大于255的话,将中心像素设为255.
膨胀的原图、二值化图与膨胀后的图:
代码实现:
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// BGR -> Gray
cv::Mat BGR2GRAY(cv::Mat img){
// get height and width
int width = img.cols;
int height = img.rows;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// BGR -> Gray
out.at<uchar>(y, x) = 0.2126 * (float)img.at<cv::Vec3b>(y, x)[2] \
+ 0.7152 * (float)img.at<cv::Vec3b>(y, x)[1] \
+ 0.0722 * (float)img.at<cv::Vec3b>(y, x)[0];
}
}
return out;
}
// Gray -> Binary
cv::Mat Binarize_Otsu(cv::Mat gray){
int width = gray.cols;
int height = gray.rows;
// determine threshold
double w0 = 0, w1 = 0;
double m0 = 0, m1 = 0;
double max_sb = 0, sb = 0;
int th = 0;
int val;
// Get threshold
for (int t = 0; t < 255; t++){
w0 = 0;
w1 = 0;
m0 = 0;
m1 = 0;
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
val = (int)(gray.at<uchar>(y, x));
if (val < t){
w0++;
m0 += val;
} else {
w1++;
m1 += val;
}
}
}
m0 /= w0;
m1 /= w1;
w0 /= (height * width);
w1 /= (height * width);
sb = w0 * w1 * pow((m0 - m1), 2);
if(sb > max_sb){
max_sb = sb;
th = t;
}
}
std::cout << "threshold:" << th << std::endl;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// Binarize
if (gray.at<uchar>(y, x) > th){
out.at<uchar>(y, x) = 255;
} else {
out.at<uchar>(y, x) = 0;
}
}
}
return out;
}
// Morphology erode
cv::Mat Morphology_Erode(cv::Mat img, int Erode_time){
int height = img.cols;
int width = img.rows;
// output image
cv::Mat tmp_img;
cv::Mat out = img.clone();
// for erode time
for (int i = 0; i < Erode_time; i++){
tmp_img = out.clone();
// each pixel
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// check left pixel
if ((x > 0) && (tmp_img.at<uchar>(y, x - 1) == 255)){
out.at<uchar>(y, x) = 255;
continue;
}
// check up pixel
if ((y > 0) && (tmp_img.at<uchar>(y - 1, x) == 255)){
out.at<uchar>(y, x) = 255;
continue;
}
// check right pixel
if ((x < width - 1) && (tmp_img.at<uchar>(y, x + 1) == 255)){
out.at<uchar>(y, x) = 255;
continue;
}
// check left pixel
if ((y < height - 1) && (tmp_img.at<uchar>(y + 1, x) == 255)){
out.at<uchar>(y, x) = 255;
continue;
}
}
}
}
return out;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("test.jpg", cv::IMREAD_COLOR);
// BGR -> Gray
cv::Mat gray = BGR2GRAY(img);
// Gray -> Binary
cv::Mat bin = Binarize_Otsu(gray);
cv::imwrite("out_bin.jpg", bin);
// Morphology Erode
cv::Mat out = Morphology_Erode(bin, 2);
cv::imwrite("out.jpg", out);
cv::imshow("sample", out);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
3:腐蚀
腐蚀和膨胀相反。
形态学处理理中的膨胀算法如下。对于待操作的像素(x,y),如果(x+1, y)、(x-1, y)、(x, y+1)、(x, y-1)中不论哪一个不为255.则像素(x,y)为0。
在实际进行形态学处理的时候,待操作的像素与矩阵
相乘,结果小于255*4的话,将中心像素设为0.
腐蚀的原图、二值化图与腐蚀后的图:
代码实现:
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// BGR -> Gray
cv::Mat BGR2GRAY(cv::Mat img){
// get height and width
int width = img.cols;
int height = img.rows;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// BGR -> Gray
out.at<uchar>(y, x) = 0.2126 * (float)img.at<cv::Vec3b>(y, x)[2] \
+ 0.7152 * (float)img.at<cv::Vec3b>(y, x)[1] \
+ 0.0722 * (float)img.at<cv::Vec3b>(y, x)[0];
}
}
return out;
}
// Gray -> Binary
cv::Mat Binarize_Otsu(cv::Mat gray){
int width = gray.cols;
int height = gray.rows;
// determine threshold
double w0 = 0, w1 = 0;
double m0 = 0, m1 = 0;
double max_sb = 0, sb = 0;
int th = 0;
int val;
// Get threshold
for (int t = 0; t < 255; t++){
w0 = 0;
w1 = 0;
m0 = 0;
m1 = 0;
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
val = (int)(gray.at<uchar>(y, x));
if (val < t){
w0++;
m0 += val;
} else {
w1++;
m1 += val;
}
}
}
m0 /= w0;
m1 /= w1;
w0 /= (height * width);
w1 /= (height * width);
sb = w0 * w1 * pow((m0 - m1), 2);
if(sb > max_sb){
max_sb = sb;
th = t;
}
}
std::cout << "threshold:" << th << std::endl;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// Binarize
if (gray.at<uchar>(y, x) > th){
out.at<uchar>(y, x) = 255;
} else {
out.at<uchar>(y, x) = 0;
}
}
}
return out;
}
// Morphology Dilate
cv::Mat Morphology_Dilate(cv::Mat img, int Dilate_time){
int height = img.cols;
int width = img.rows;
// output image
cv::Mat tmp_img;
cv::Mat out = img.clone();
// for erode time
for (int i = 0; i < Dilate_time; i++){
tmp_img = out.clone();
// each pixel
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// check left pixel
if ((x > 0) && (tmp_img.at<uchar>(y, x - 1) == 0)){
out.at<uchar>(y, x) = 0;
continue;
}
// check up pixel
if ((y > 0) && (tmp_img.at<uchar>(y - 1, x) == 0)){
out.at<uchar>(y, x) = 0;
continue;
}
// check right pixel
if ((x < width - 1) && (tmp_img.at<uchar>(y, x + 1) == 0)){
out.at<uchar>(y, x) = 0;
continue;
}
// check left pixel
if ((y < height - 1) && (tmp_img.at<uchar>(y + 1, x) == 0)){
out.at<uchar>(y, x) = 0;
continue;
}
}
}
}
return out;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("test.jpg", cv::IMREAD_COLOR);
// BGR -> Gray
cv::Mat gray = BGR2GRAY(img);
// Gray -> Binary
cv::Mat bin = Binarize_Otsu(gray);
// Morphology Dilate
cv::Mat out = Morphology_Dilate(bin, 2);
cv::imwrite("out.jpg", out);
cv::imshow("sample", out);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
开运算:先进行N次腐蚀再进行N次膨胀,开运算可以用来去除仅存的小块像素。
下面进行一次开运算:
二值化图:
一次腐蚀后:
接着一次膨胀后:
闭运算:先进行N次膨胀再进行N次腐蚀,闭运算能够将中断的像素连接起来。
Canny边缘检测之后:
进行一次闭运算(N=1),先膨胀一次:
接着腐蚀一次: