图像的阈值处理
二值化处理
- 将设置的阈值应用于每个通道(阵列)的每个像素,进而过滤太小或太大的像素值。常用于去除噪声。
- 缺点:具有主观性,难以达到理想的分割效果。
#include <opencv2/imgproc.hpp> 函数说明:double cv::threshold( InputArray src, OutputArray dst, double thresh, double maxval, int type ); 输入参数: src 输入阵列(多通道、8位或32位浮点)。 dst 与src具有相同大小和类型以及相同通道数的输出数组。 thresh 阈值。 maxval 与THRESH_BINARY和THRESH_ BINARY_INV阈值类型一起使用的最大值。 type 阈值类型。 cv::THRESH_BINARY = 0 若大于thresh,则设置为maxval,否则设置为0。(常用) cv::THRESH_BINARY_INV = 1 若大于thresh,则设置为0,否则设置为maxval(反操作)。 cv::THRESH_TRUNC = 2 若大于thresh,则设置为thresh,否则保持不变。 cv::THRESH_TOZERO = 3 若大于thresh,则保持不变,否则设置为0。 cv::THRESH_TOZERO_INV = 4 若大于thresh,则设置为0,否则保持不变(反操作)。 cv::THRESH_MASK = 5 cv::THRESH_OTSU = 6 全局自适应阈值化(仅适用于8位单通道图像)。适合于直方图具有双峰的情况,其在双峰之间找到阈值;对于非双峰图像不是很好用。 cv::THRESH_TRIANGLE = 7 全局自适应阈值化(仅适用于8位单通道图像)。在直方图中,在最亮到最暗处连接一条直线,得到的最大直线距离所对应的直方图位置就是阈值thresh。 输出参数:仅当阈值类型为Otsu或Triangle方法时使用,输出自适应的阈值。
自适应二值化处理
-
通过窗口内像素的分布特征自适应计算阈值,并进行阈值化处理。详细步骤如下:
(1)将图像拆分为M x N个区域;
(2)采用自适应阈值算法(窗口均值阈值法、高斯分布阈值法),计算每个区域的(均值、高斯均值),该值即当前区域的二值化阈值;
(3)根据每个窗口计算得到的不同阈值(动态),进行阈值化处理。
#include <opencv2/imgproc.hpp> 函数说明:void cv::adaptiveThreshold( InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C ); 输入参数: src 8位单通道图像。 dst 与src大小和类型相同的目标图像。 maxValue 指定给满足条件的像素的非零值 adaptiveMethod 自适应阈值算法。BORDER_REPLICATE|BORDER_ISOLATED用于处理边界。 cv::ADAPTIVE_THRESH_MEAN_C = 0 窗口均值阈值法。计算出领域的平均值再减去参数double C的值 cv::ADAPTIVE_THRESH_GAUSSIAN_C = 1 高斯分布阈值法。计算出领域的高斯均值再减去参数double C的值 thresholdType 阈值化类型(只有两个取值)。 cv::THRESH_BINARY = 0 若大于thresh,则设置为maxval,否则设置为0。(常用) cv::THRESH_BINARY_INV = 1 若大于thresh,则设置为0,否则设置为maxval(反操作)。 blockSize 像素邻域大小(单位):3、5、7,依此类推。自适应阈值算法的阈值计算时使用。 C 偏移值。自适应阈值算法的阈值计算时使用。
案例
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
string img_path1 = "D:/系统默认/桌面/RM/sha.jpeg";
string img_path2 = "D:/系统默认/桌面/RM/tou.jpeg";
Mat src = imread(img_path1, 1);
Mat src2 = imread(img_path2, 1);
if (src.empty() || src2.empty()) {
cout << "can't read image!!" << endl;
return -1;
}
//转换为灰度图
cv::Mat srcGray;
cv::cvtColor(src, srcGray, cv::COLOR_RGB2GRAY);
//(4)阈值化处理
cv::Mat THRESH_BINARY, THRESH_BINARY_INV, THRESH_TRUNC, THRESH_TOZERO, THRESH_TOZERO_INV, THRESH_MASK, THRESH_OTSU, THRESH_TRIANGLE;
double thresh = 125;
//设定的阈值
double maxval = 255;
//当像素超过我们设定的阈值时赋的值,设为255也就是赋白
cv::threshold(srcGray, THRESH_BINARY, thresh, maxval, 0);
cv::threshold(srcGray, THRESH_BINARY_INV, thresh, maxval, 1);
//以上两个刚好相反,第一个小于阈值置0,第二个相反
cv::threshold(srcGray, THRESH_TRUNC, thresh, maxval, 2);
//小于阈值保持原色,大于置灰色
cv::threshold(srcGray, THRESH_TOZERO, thresh, maxval, 3);
cv::threshold(srcGray, THRESH_TOZERO_INV, thresh, maxval, 4);
//以上两个也相反,第一个小于阈值置0,大于保持原色,第二个相反
//(5)自适应阈值化处理
/*自适应阈值是更趋于局部性的阈值,将像素点的像素值与该点所在区域的像素值的平均值
(也有可能是最大值,中位数)来决定该点是属于0还是1*/
cv::Mat ADAPTIVE_THRESH_MEAN_C0, ADAPTIVE_THRESH_MEAN_C1, ADAPTIVE_THRESH_GAUSSIAN_C0, ADAPTIVE_THRESH_GAUSSIAN_C1;
int blockSize = 5;
//方阵大小
int constValue = 10;
//常数,每个区域计算出阈值基础上减去这个常熟作为这个区域的最终阈值
const int maxVal = 255;
//像素值上限
cv::adaptiveThreshold(srcGray, ADAPTIVE_THRESH_MEAN_C0, maxVal, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, blockSize, constValue);
cv::adaptiveThreshold(srcGray, ADAPTIVE_THRESH_MEAN_C1, maxVal, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, blockSize, constValue);
cv::adaptiveThreshold(srcGray, ADAPTIVE_THRESH_GAUSSIAN_C0, maxVal, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY_INV, blockSize, constValue);
cv::adaptiveThreshold(srcGray, ADAPTIVE_THRESH_GAUSSIAN_C1, maxVal, cv::ADAPTIVE_THRESH_GAUSSIAN_C, cv::THRESH_BINARY_INV, blockSize, constValue);
//不同的赋值方式,加INV的与上面的刚好相反
//(6)显示图像
cv::imshow("srcGray", srcGray);
cv::imshow("img1", THRESH_BINARY);
cv::imshow("img2", THRESH_BINARY_INV);
cv::imshow("img3", THRESH_TRUNC);
cv::imshow("img4", THRESH_TOZERO);
cv::imshow("img5", THRESH_TOZERO_INV);
//cv::imshow("img6", THRESH_MASK);
//cv::imshow("img7", THRESH_OTSU);
//cv::imshow("img8", THRESH_TRIANGLE);
cv::imshow("img11", ADAPTIVE_THRESH_MEAN_C0);
cv::imshow("img22", ADAPTIVE_THRESH_MEAN_C1);
cv::imshow("img33", ADAPTIVE_THRESH_GAUSSIAN_C0);
cv::imshow("img44", ADAPTIVE_THRESH_GAUSSIAN_C1);
cv::waitKey(0);
return 0;
}
很多很乱哈哈哈
图像的形态学转换
腐蚀
关于腐蚀就是将图像的边界腐蚀掉,或者说使得图像整体上看起来变瘦了。它的操作原理就是卷积核沿着图像滑动,如果与卷积核对应的原图像的所有像素值都是1,那么中心元素保持原来的值,否则就变为0。这对于去除白噪声很有用,也可以用于断开两个连载一起的物体。
腐蚀可以应用多次(迭代)。在多通道图像的情况下,每个通道都是独立处理的。若需要将不同的卷积核应用于不同的通道,可使用cv::split。
#include <opencv2/imgproc.hpp>
函数说明:void cv::erode( InputArray src, OutputArray dst, InputArray kernel, Point anchor = Point(-1,-1), int iterations = 1, int borderType = BORDER_CONSTANT, const Scalar &borderValue = morphologyDefaultBorderValue() );
输入参数:
src 输入图像;通道的数量可以是任意的,这些通道是独立处理的,但深度应该是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F中的一个。
dst 输出与src大小和类型相同的图像。
kernel 卷积核大小(单通道浮点矩阵)。如果element=Mat(),则使用一个3x3矩形结构化元素。内核可以使用getStructureElement创建。
anchor = Point(-1,-1) 锚点。位于卷积核内;默认值(-1,-1):表示锚点位于内核中心。
iterations = 1 腐蚀迭代次数(默认1)。迭代N次与连续调用N次的效果是不等同的。
borderType = BORDER_CONSTANT 边界类型(即边界填充方式)。默认BORDER_CONSTANT。不支持BORDER_WRAP。
cv::BORDER_CONSTANT = 0 iiiiii|abcdefgh|iiiiiii 常量法。填充常数值
cv::BORDER_REPLICATE = 1 aaaaaa|abcdefgh|hhhhhhh 复制法。复制最边缘像素
cv::BORDER_REFLECT = 2 fedcba|abcdefgh|hgfedcb 反射法。以两边为轴
cv::BORDER_WRAP = 3 cdefgh|abcdefgh|abcdefg 外包装法。
cv::BORDER_REFLECT_101 = 4 gfedcb|abcdefgh|gfedcba 反射法。以最边缘像素为轴
cv::BORDER_TRANSPARENT = 5 uvwxyz|abcdefgh|ijklmno
cv::BORDER_REFLECT101 = 6 same as BORDER_REFLECT_101
cv::BORDER_DEFAULT = 7 same as BORDER_REFLECT_101
cv::BORDER_ISOLATED = 8 do not look outside of ROI
borderValue = morphologyDefaultBorderValue() 边界值(在边界不变的情况下)
膨胀
膨胀原理与腐蚀相同,只不过膨胀的时候与卷积核对应的原图像的像素值只要有一个为1,那么中心元素就是1。这么做带来的变化就是图像膨胀了,或者说图像变胖了。
膨胀也可以多次迭代,也可以用split
#include <opencv2/imgproc.hpp>
函数说明:void cv::dilate( InputArray src, OutputArray dst, InputArray kernel, Point anchor = Point(-1,-1), int iterations = 1, int borderType = BORDER_CONSTANT, const Scalar &borderValue = morphologyDefaultBorderValue() );
输入参数:
src 输入图像;通道的数量可以是任意的,这些通道是独立处理的,但深度应该是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F中的一个。
dst 输出与src大小和类型相同的图像。
kernel 卷积核大小(单通道浮点矩阵)。如果element=Mat(),则使用一个3x3矩形结构化元素。内核可以使用getStructureElement创建。
anchor = Point(-1,-1) 锚点。位于卷积核内;默认值(-1,-1):表示锚点位于内核中心。
iterations = 1 膨胀迭代次数(默认1)。迭代N次与连续调用N次的效果是不等同的。
borderType = BORDER_CONSTANT 边界类型(即边界填充方式)。默认BORDER_CONSTANT。不支持BORDER_WRAP。
cv::BORDER_CONSTANT = 0 iiiiii|abcdefgh|iiiiiii 常量法。填充常数值
cv::BORDER_REPLICATE = 1 aaaaaa|abcdefgh|hhhhhhh 复制法。复制最边缘像素
cv::BORDER_REFLECT = 2 fedcba|abcdefgh|hgfedcb 反射法。以两边为轴
cv::BORDER_WRAP = 3 cdefgh|abcdefgh|abcdefg 外包装法。
cv::BORDER_REFLECT_101 = 4 gfedcb|abcdefgh|gfedcba 反射法。以最边缘像素为轴
cv::BORDER_TRANSPARENT = 5 uvwxyz|abcdefgh|ijklmno
cv::BORDER_REFLECT101 = 6 same as BORDER_REFLECT_101
cv::BORDER_DEFAULT = 7 same as BORDER_REFLECT_101
cv::BORDER_ISOLATED = 8 do not look outside of ROI
borderValue = morphologyDefaultBorderValue() 边界值(在边界不变的情况下)
形态学变换
#include <opencv2/imgproc.hpp>
函数说明:void cv::morphologyEx( InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor = Point(-1,-1), int iterations = 1, int borderType = BORDER_CONSTANT, const Scalar &borderValue = morphologyDefaultBorderValue() );
输入参数:
src 输入图像;通道的数量可以是任意的,这些通道是独立处理的,但深度应该是CV_8U、CV_16U、CV_16S、CV_32F或CV_64F中的一个。
dst 输出与src大小和类型相同的图像。
op 形态学运算的类型。
cv::MORPH_ERODE = 0 腐蚀操作
cv::MORPH_DILATE = 1 膨胀操作
cv::MORPH_OPEN = 2 开运算:先腐蚀再膨胀。用于去除微小干扰点/块。
cv::MORPH_CLOSE = 3 闭运算:先膨胀再腐蚀。用于填充闭合区域。
cv::MORPH_GRADIENT = 4 基本梯度:膨胀图(减去)腐蚀图
cv::MORPH_TOPHAT = 5 顶帽:原图(减去)开运算图。注:顶帽和黑帽用于获取图像中的微小细节。
cv::MORPH_BLACKHAT = 6 黑帽:闭运算图(减去)原图
cv::MORPH_HITMISS = 7 “命中或未命中”。仅支持CV_8UC1二进制图像。
kernel 卷积核大小(单通道浮点矩阵)。如果element=Mat(),则使用一个3x3矩形结构化元素。内核可以使用getStructureElement创建。
anchor = Point(-1,-1) 锚点。位于卷积核内;默认值(-1,-1):表示锚点位于内核中心。
iterations = 1 腐蚀和膨胀的迭代次数(默认1)。两次迭代的顺序:侵蚀+侵蚀+扩张+扩张(而不是侵蚀+扩张+侵蚀+扩张)。
borderType = BORDER_CONSTANT 边界类型(即边界填充方式)。默认BORDER_CONSTANT。不支持BORDER_WRAP。
cv::BORDER_CONSTANT = 0 iiiiii|abcdefgh|iiiiiii 常量法。填充常数值
cv::BORDER_REPLICATE = 1 aaaaaa|abcdefgh|hhhhhhh 复制法。复制最边缘像素
cv::BORDER_REFLECT = 2 fedcba|abcdefgh|hgfedcb 反射法。以两边为轴
cv::BORDER_WRAP = 3 cdefgh|abcdefgh|abcdefg 外包装法。
cv::BORDER_REFLECT_101 = 4 gfedcb|abcdefgh|gfedcba 反射法。以最边缘像素为轴
cv::BORDER_TRANSPARENT = 5 uvwxyz|abcdefgh|ijklmno
cv::BORDER_REFLECT101 = 6 same as BORDER_REFLECT_101
cv::BORDER_DEFAULT = 7 same as BORDER_REFLECT_101
cv::BORDER_ISOLATED = 8 do not look outside of ROI
borderValue = morphologyDefaultBorderValue() 边界值(在边界不变的情况下)
获取指定大小和形状的结构化元素
#include <opencv2/imgproc.hpp>
函数说明:Mat cv::getStructuringElement( int shape, Size ksize, Point anchor = Point(-1,-1) );
输入参数:
shape 结构化元素的形状。
cv::MORPH_RECT = 0 矩形结构化元素:E(i,j)=1
cv::MORPH_CROSS = 1 十字形结构元素:E(i,j)=10 if i=anchor.y or j=anchor.x else 0
cv::MORPH_ELLIPSE = 2 椭圆形结构元素:即内切到矩形Rect(0, 0, esize.width, 0.esize.height)中的填充椭圆。
ksize 结构化元素的大小。
anchor 锚点。默认值(−1,−1)表示锚点位于中心。注意,只有十字形元件的形状取决于锚定位置。在其他情况下,锚点只是调节形态学运算的结果偏移了多少。
案例1
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
string img_path1 = "D:/系统默认/桌面/RM/sha.jpeg";
string img_path2 = "D:/系统默认/桌面/RM/tou.jpeg";
Mat src = imread(img_path1, 1);
Mat src2 = imread(img_path2, 1);
if (src.empty() || src2.empty()) {
cout << "can't read image!!" << endl;
return -1;
}
Mat gray, binary;
cvtColor(src, gray, cv::COLOR_BGR2GRAY);
//灰度化
threshold(gray, binary, 0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
//二值化
Mat img_h, img_v;
int x_size = binary.cols / 30; //像素的水平长度=width/30
int y_size = binary.rows / 30; //像素的垂直长度=height/30
Mat h_line = getStructuringElement(cv::MORPH_RECT, cv::Size(x_size, 1), cv::Point(-1, -1));
Mat v_line = getStructuringElement(cv::MORPH_RECT, cv::Size(1, y_size), cv::Point(-1, -1));
morphologyEx(binary, img_h, cv::MORPH_OPEN, h_line, cv::Point(-1, -1), 1, 0); //开运算(提取水平线)
//先腐蚀在膨胀
morphologyEx(binary, img_v, cv::MORPH_CLOSE, v_line, cv::Point(-1, -1), 1, 0); //闭运算(提取垂直线)
//先膨胀,再腐蚀
imshow("binary", binary);
imshow("img_h", img_h);
imshow("img_v", img_v);
cv::waitKey(0);
return 0;
}
案例
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
int main(int argc, char* argv[])
{
string img_path1 = "D:/系统默认/桌面/RM/sha.jpeg";
string img_path2 = "D:/系统默认/桌面/RM/tou.jpeg";
Mat src = imread(img_path1, 1);
Mat src2 = imread(img_path2, 1);
if (src.empty() || src2.empty()) {
cout << "can't read image!!" << endl;
return -1;
}
//(3)灰度化+二值化
cv::Mat gray, binary;
cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
cv::threshold(gray, binary, 0, 255, cv::THRESH_BINARY_INV | cv::THRESH_OTSU);
//(4)形态学变化
int kernel_size = 5;
//getStructuringElement:返回指定大小和形状的结构化元素以进行形态学运算。
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(kernel_size, kernel_size), cv::Point(-1, -1));
cv::Mat img1, img2, img3, img4;
cv::erode(binary, img1, kernel); //腐蚀
cv::erode(binary, img2, kernel, cv::Point(-1, -1), 3); //腐蚀(迭代三次)
cv::dilate(binary, img3, kernel); //膨胀
cv::dilate(binary, img4, kernel, cv::Point(-1, -1), 3); //膨胀(迭代三次)
cv::Mat img11, img22, img33, img44, img55, img66, img77, img88;
cv::morphologyEx(binary, img11, cv::MORPH_ERODE, kernel, cv::Point(-1, -1), 1, 0); //腐蚀
cv::morphologyEx(binary, img22, cv::MORPH_DILATE, kernel, cv::Point(-1, -1), 1, 0); //膨胀
cv::morphologyEx(binary, img33, cv::MORPH_OPEN, kernel, cv::Point(-1, -1), 1, 0); //开运算
cv::morphologyEx(binary, img44, cv::MORPH_CLOSE, kernel, cv::Point(-1, -1), 1, 0); //闭运算
cv::morphologyEx(binary, img55, cv::MORPH_GRADIENT, kernel, cv::Point(-1, -1), 1, 0); //基本梯度
cv::morphologyEx(binary, img66, cv::MORPH_TOPHAT, kernel, cv::Point(-1, -1), 1, 0); //顶帽
cv::morphologyEx(binary, img77, cv::MORPH_BLACKHAT, kernel, cv::Point(-1, -1), 1, 0); //黑帽
cv::morphologyEx(binary, img88, cv::MORPH_HITMISS, kernel, cv::Point(-1, -1), 1, 0); //击中击不中
//(4)显示图像
cv::imshow("src", src);
cv::imshow("腐蚀1", img1);
cv::imshow("腐蚀3", img2);
cv::imshow("膨胀1", img3);
cv::imshow("膨胀3", img4);
cv::imshow("腐蚀", img11);
cv::imshow("膨胀", img22);
cv::imshow("开运算", img33);
cv::imshow("闭运算", img44);
cv::imshow("基本梯度", img55);
cv::imshow("顶帽", img66);
cv::imshow("黑帽", img77);
cv::imshow("击中击不中", img88);
waitKey();
return 0;
}
可以明显看到迭代一遍和三遍的区别
学习链接
主要学习来源:Opencv C++图像处理(全)_c++ opencv 图像处理-优快云博客
关于阈值处理详细理解:[2] 图像处理之----二值化处理-优快云博客
关于腐蚀和膨胀原理:图像腐蚀和膨胀的原理_腐蚀膨胀原理_WitransFer的博客-优快云博客