10.1 引言
图像中的阈值处理是指剔除图像内像素高于阈值或者低于阈值的像素点,从而分离得到需要的特征部分。图像阈值化是图像分割中国的一种常用方法。
滤波利用原有图像的某个像素的周围像素来确定该像素的新像素值,可用来消去噪声以及图像中的不合理的像素点。滤波器主要包括线性滤波器和非线性滤波器,其中线性滤波器包括均值滤波,方框滤波和高斯滤波,非线性的主要是中值滤波。
10.2 阈值类型
下图红色表示一系列值,蓝色为阈值。将以该图介绍一下几种阈值的方式
THRESH_BINARY 阈值二值化 | ||
THRESH_BINARY_INV 阈值反二值化 | ||
THRESH_TRUNC 截断 | ||
THRESH_TOZERO 阈值取零 | ||
THRESH_TOZERO_INV 阈值反取零 | ||
THRESH_MASK | ||
THRESH_OTSU 大津阈值法 | 最大类间方差,对直方图有明显波谷的图像效果较好 | 仅支持8位单通道图,阈值寻找 |
THRESH_TRIANGLE | 对直方图只有单峰的效果好 | 仅支持8位单通道图,阈值寻找 |
10.3 阈值处理cv::threshold()
double cv::threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)
参数 | 含义 | |
作用 | 将一种颜色空间的图像转换为另一种颜色空间 | |
输入 | src | 原图,只能是CV_8UHE CV_32F类型 |
dst | 二值化后的图像 | |
thresh | 二值化的阈值 | |
maxval | 二值化过程的最大值,在THRESH_BINARY和THRESH_BINARY_INV两种方法类使用 | |
type | 二值化方法类型(可集合自动获取阈值) | |
返回值 | double |
代码示例:
示例1,五种基本的阈值处理。打开一个窗口显示多个图像:
void imshowMany(string windowName, vector<Mat>images, int imgCols, Size imgSize, int space)
{
//初始坐标
Point2i location(0, 0);
//构造窗口大小
Mat imgWindow((imgSize.height + space) * ((images.size() - 1) / imgCols + 1) - space,
(imgSize.width + space) * imgCols - space, images[0].type());
//贴图
for (int i = 0; i < images.size(); i++)
{
location.x = (i%imgCols)*imgSize.width + (i%imgCols)*space;
location.y = (i / imgCols)*imgSize.height + (i / imgCols)*space;
images[i].copyTo(imgWindow(Rect(location,imgSize)));
}
//显示图像集合
imshow(windowName, imgWindow);
}
Mat src, gray, binaryImg, binaryInvImg,truncImg,tozeroImg,tozeroInvImg;
int thresholdValue = 127;
int thresholdMax = 255;
const char* outputWin = "output";
//拉动跟踪条改变阈值
void thresholdBack(int, void*)
{
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binaryImg, thresholdValue, thresholdMax,THRESH_BINARY);
threshold(gray, binaryInvImg, thresholdValue, thresholdMax, THRESH_BINARY_INV);
threshold(gray, truncImg, thresholdValue, thresholdMax, THRESH_TRUNC);
threshold(gray, tozeroImg, thresholdValue, thresholdMax, THRESH_TOZERO);
threshold(gray, tozeroInvImg, thresholdValue, thresholdMax, THRESH_TOZERO_INV);
vector<Mat> images{ gray, binaryImg, binaryInvImg,truncImg,tozeroImg,tozeroInvImg};
Size imgSize = gray.size();
imshowMany(outputWin, images, 3, imgSize, 10);
}
int main(int argc, char** argv)
{
src = imread("D:\\testimg\\CSet12\\lena.png");
if (src.empty())
{
printf("Could not load the image...\n");
return -1;
}
else;
char input_win[] = "srcImg";
namedWindow(input_win, WINDOW_AUTOSIZE);
imshow(input_win, src);
namedWindow(outputWin, WINDOW_AUTOSIZE);
createTrackbar("thresholdValue:",outputWin,&thresholdValue,thresholdMax, thresholdBack);
thresholdBack(0,0);
waitKey(0);
return 0;
}
示例2:通过THRESH_OTSU和THRESH_TRIANGLE自动查找阈值
int main(int argc, char** argv)
{
src = imread("D:\\testimg\\CSet12\\lena.png");
if (src.empty())
{
printf("Could not load the image...\n");
return -1;
}
else;
char input_win[] = "srcImg";
namedWindow(input_win, WINDOW_AUTOSIZE);
imshow(input_win, src);
cvtColor(src, gray, COLOR_BGR2GRAY);
Mat OtsuImg, TriangleImg;
threshold(gray, OtsuImg,0, 255, THRESH_BINARY|THRESH_OTSU);
threshold(gray, TriangleImg, 0, 255, THRESH_BINARY | THRESH_TRIANGLE);
imshow("OtsuImg", OtsuImg);
imshow("TriangleImg", TriangleImg);
waitKey(0);
return 0;
}
10.4 自定义线性滤波
10.4.1 卷积概念
卷积是卷积核(kernel)在图像中的每个像素的操作;kernel本质上是一个固定大小的矩阵数组,通常将其中心称为锚点(anchor point)。计算与卷积核重叠区域一一对应的乘积和重新赋值到与锚点重叠的像素。
Sum = 8x1+6x1+6x1+2x1+8x1+6x1+2x1+2x1+8x1 各乘积相加
New pixel = sum / (m*n) 取平均值
卷积的作用:模糊图像,提取边缘,图像增锐化等;
10.4.2 常见算子
Robert算子
Mat robertX, robertY;
//x向
Mat kernelx = (Mat_<int>(2, 2) << 1, 0, 0, -1);
filter2D(src, robertX, -1, kernelx, Point(-1, -1),0.0);
//y向
Mat kernely = (Mat_<int>(2, 2) << 0, 1, -1, 0);
filter2D(src, robertY, -1, kernely, Point(-1, -1),0.0);
Sobel算子
Mat sobelX, sobelY;
//x向
Mat kernelx = (Mat_<int>(3, 3) << -1, 0,1,-2,0,2,-1, 0, 1);
filter2D(src, sobelX, -1, kernelx, Point(-1, -1),0.0);
//y向
Mat kernely = (Mat_<int>(3, 3) <<-1, -2, -1, 0,0,0,1,2,1);
filter2D(src, sobelY, -1, kernely, Point(-1, -1),0.0);
拉普拉斯(Laplace)算子
Mat Laplace;
//x向
Mat kernel = (Mat_<int>(3, 3) << 0, -1,0,-1,4,-1,0, -1, 0);
filter2D(src, Laplace, -1, kernelx, Point(-1, -1),0.0);
10.4.3 自定义渐进模糊
int main(int argc, char** argv)
{
Mat src = imread("D:\\testimg\\CSet12\\lena.png");
if (src.empty())
{
printf("Could not load the image...\n");
return -1;
}
else;
char input_win[] = "srcImg";
namedWindow(input_win, WINDOW_AUTOSIZE);
imshow(input_win, src);
Mat dst;
/// 初始化滤波器参数
Point anchor = Point(-1, -1);
double delta = 0;
/// 循环 - 每隔0.5秒,用一个不同的核来对图像进行滤波
int c;
int ind = 0;
while (true)
{
c = waitKey(500);
/// 按'ESC'可退出程序
if ((char)c == 27)
{
break;
}
/// 更新归一化块滤波器的核大小
int kernelSize = 3 + 2 * (ind % 5);
Mat kernel = Mat::ones(kernelSize, kernelSize, CV_32F) / (float)(kernelSize*kernelSize);
/// 使用滤波器
filter2D(src, dst, -1, kernel, anchor, delta, BORDER_DEFAULT);
imshow("auto blur", dst);
ind++;
}
waitKey(0);
return 0;
}
10.5 卷积边缘处理
图像通过卷积后,由于卷积核是有大小的,原图的边界像素无法被卷积操作,如3*3滤波时边缘有一个像素无法被处理,5*5滤波时有2个像素没有被处理。
10.5.1 处理卷积边缘
在卷积操作之前对原图进行边界扩充,填充的像素值为0或RGB黑色,比如需进行3*3滤波时,给原图四周各增加1个像素的边缘,确保图像边缘可以被卷积核覆盖。opencv中默认的处理方式是:BORDER_DEFAULT(在滤波的API中已有该参数)。常用的有以下几种:
- BORDER_DEFAULT ——将最近的像素进行映射
- BORDER_CONSTANT——用指定像素值填充边缘
- BORDER_REPLICATE——用已知的边缘像素值填充边缘
- BORDER_WRAP——用另外一边的像素来补偿填充
此外,除了在卷积API中进行定义,还可以自主对图像进行边缘扩充。opencv提供了cv::copyMakeBorder给图像增加边缘。
double cv::copyMakeBorder(Mat src, Mat dst, int top,int bottom, int left, int right, int borderType, Scalar value)
参数 | 含义 | |
作用 | 给图像增加边缘 | |
输入 | src | 原图 |
dst | 添加边缘后的图像 | |
top | 边缘上宽度,一般上下左右一样 | |
bottom | 边缘下宽度 | |
left | 边缘左宽度 | |
right | 边缘右宽度 | |
borderType | 边缘类型: BORDER_DEFAULT; BORDER_CONSTANT=0;常量 BORDER_REPLICATE=1;也就是复制最边缘像素 BORDER_REFLECT=2;对称法,以最边缘像素为轴,对称复制 BORDER_WRAP=3;用另外一边像素填充 | |
value | 当使用BORDER_CONSTANT时,设置的要填充的像素值 | |
type | 二值化方法类型(可集合自动获取阈值) | |
返回值 | double |
10.5.2 代码示例
int main(int argc, char** argv)
{
Mat src = imread("D:\\testimg\\CSet12\\lena.png");
if (src.empty())
{
printf("Could not load the image...\n");
return -1;
}
else;
char input_win[] = "srcImg";
namedWindow(input_win, WINDOW_AUTOSIZE);
imshow(input_win, src);
int top = (int)(0.05*src.rows);
int bottom = (int)(0.05*src.rows);
int left = (int)(0.05*src.cols);
int right = (int)(0.05*src.cols);
Mat DefaultImg, ConstantImg, RepalcateImg, ReflectImg, WrapImg;
copyMakeBorder(src, DefaultImg,top,bottom,left,right,BORDER_DEFAULT,Scalar(0,0,255));
copyMakeBorder(src, ConstantImg, top, bottom, left, right, BORDER_CONSTANT, Scalar(0, 0, 255));
copyMakeBorder(src, RepalcateImg, top, bottom, left, right, BORDER_REPLICATE, Scalar(0, 0, 255));
copyMakeBorder(src, ReflectImg, top, bottom, left, right, BORDER_REFLECT, Scalar(0, 0, 255));
copyMakeBorder(src, WrapImg, top, bottom, left, right, BORDER_WRAP, Scalar(0, 0, 255));
imshow("DefaultImg", DefaultImg);
imshow("ConstantImg", ConstantImg);
imshow("RepalcateImg", RepalcateImg);
imshow("ReflectImg", ReflectImg);
imshow("WrapImg", WrapImg);
waitKey(0);
return 0;
}