1 保存带透明通道的图片
无论如何操作透明通道,都显示出白色背景, 只有通过设置imread(“”,imread_unchanged), 保存的是透明通道
unchanged: 4通道, 透明通道
IMREAD_UNCHANGED
加载代透明图片IMREAD_COLOR
: 加载bgr图片IMREAD_GRAYSCALE
: 加载灰色图片IMREAD_ANYCOLOR
: 加载各种颜色
2. mat对象
mat对象: 存储图像数据(二维数据)的内存对象
存储枚举结果
-
头部: 宽高, 数据类型,通道(单通道通常是灰色图像, 三通道一般是rgb图像. 四通道一般为透明通道)
-
数据部分: 像素值
-
创建Mat对象: Mat(size(x,y),cv_8uc3)
mat对象赋值: 只是地址的指向, 复制变量, 他的内存地址是复制的内存地址
mat对象复制或克隆: 会创造新的mat对象, 而不是直接执行复制的内存地址
mat.depth()
: 返回的是个int类型, 图像的深度
mat. type()
: 返回的是个int类型, 图像的类型
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-JL3V6aZE-1656318416402)(D:\opencvSourse\openImg\imgType&&depth对应表.png)]
type数值:
-
type = depth (channle -1)* 8
-
Mat src = Mat::zeros(Size(100, 100), CV_8UC3); cout << "图片类型8: " << src.type() << "\t" << "图像深度0: " << src.depth() << endl; /图片类型8: 16 图像深度0: 0 Mat src1 = Mat::zeros(Size(100, 100), CV_8SC3); cout << "图片类型9: " << src1.type() << "\t" << "图像深度1: " << src1.depth() << endl; //图片类型9: 17 图像深度1: 1 Mat src2 = Mat::zeros(Size(100, 100), CV_16UC1); cout << "图片类型9: " << src2.type() << "\t" << "图像深度2: " << src2.depth() << endl; //图片类型8 2: 10 图像深度2: 2
2 Mat 对象创建
Mat(x,x,type())
: 这样创建, 他默认不会全部为0, 会带一些颜色
Mat::zeros(Size(x,x), type())
: 创建一个全是0的对象
Mat::zeros
mat::once : 创建一个全是1的对象
[外链图片存储失败,源站可能有防盗链机制,建议保存图片直接上传(img-8JS5rn6r-1656318416403)()]
- mat.clone(): clone无论你是否设置了是否设置了它ROI、COI等影响,clone都会原封不动的克隆过来
- copyTo是实现图像roi正确的操作方法
3 mat对象遍历
imshow("原图", img); Mat dst = Mat::zeros(img.size(), img.type()); /*for (int row = 0; row < img.rows; row ) { for (int col = 0; col < img.cols; col ) { if (img.channels() == 3) { dst.at<Vec3b>(row, col)[0] = img.at<Vec3b>(row, col)[0]; dst.at<Vec3b>(row, col)[1] = img.at<Vec3b>(row, col)[1]; dst.at<Vec3b>(row, col)[2] = img.at<Vec3b>(row, col)[2]; } } }*/ /*imshow("遍历", dst);*/ for (int row = 0; row < img.rows; row ) { uchar* y = img.ptr<uchar>(row); uchar* newI = dst.ptr<uchar>(row); for (int col = 0; col < img.cols; col ) { *newI = *y ; *newI = *y ; *newI = *y ; } } imshow("遍历", dst);
4 图像算术操作
-
注意点:
越界: opencv自动处理, 如果得大于255 ,会等于255, 若小于0,则等于0
add, subtract,multiple, divide
-
API
addWeighted: 用于调节亮度和对比度(伪装透明度)
- 参数2, 4 是权重(*权重)
- 参数5: 合并的dst 参数5
void Person::erithmetic(Mat& img) { imshow("原图", img); Mat src = Mat::zeros(img.size(), img.type()); src = Scalar(128, 128, 128); Mat dst; //伪装透明度 addWeighted(img, 0.5, src, 0.5,0,dst); //addWeighted(img, 1.5, img, -0.2, 0, dst); imshow("位置透明度", dst); }
5 与或非
mask满足条件:
bitwise_and, bitweise_or, bitweise_xor
imshow("原图", img); Mat mask = Mat::zeros(img.size(), CV_8UC1); for (int row = img.rows / 4; row < img.rows / 4 * 3; row ) { uchar* p = mask.ptr<uchar>(row); for (int col = 0 ; col < img.cols / 4 * 2; col ) { *(p (col img.cols / 4)) = 255; } } imshow("mask", mask); Mat dst; bitwise_not(img, dst, mask); imshow("取反", dst);
6 像素信息统计
1. api, 有些颜色可以通过方差或平均值过滤掉
- mean 返回的是scalar对象, 寻求每个通道的平均值
- meanStdDev: 返回的是一个mat对象
- minMaxLoc: 最大值最小值, 最大值的像素点位置, 值小值的point&
- 参数1: 是一个double引用类型(最小值)
- 参数2: 是一个double引用类型(最大值)
- 参数3: 是点类地址 Loc.x
- 参数3: 是点类地址 Loc.y
void Person::distribution(Mat& img)
{
imshow("原图", img);
double min; double max;
Point minLoc; Point maxLoc;
vector<Mat> vMat(img.channels());
split(img, vMat);
//均值
Scalar mea = mean(img);
Scalar mea1;
vector<Mat> stddev(img.channels());
for (int i = 1; i < vMat.size(); i++)
{
//最小最大值
minMaxLoc(vMat[i-1], &min, &max, &minLoc, &maxLoc, Mat());
cout << "通道:" << i << "最小值: " << min << "\t" << "最大值: " << max << "\t" << "最小值像素点: " << minLoc << "最大值像素点" << maxLoc << endl;
cout << "通道:" << i << "均值: " << mea[i - 1] << endl;
meanStdDev(vMat[i - 1], mea1, stddev[i-1], Mat());
cout << "通道:" << i << "方差: " << stddev[i - 1] << endl;
}
}
六.1. 图形绘制与填充
- 直线绘制
-
api: line(canvas, Point(x,y), Point(x1,y1), scalar(B,G,R), thickness, lineType,)
lineType: LINE_AA(反锯齿)
-
rectangle(canvas, Rect(10, 200, 300, 300), Scalar(0, 0, 200), -1, 8);
-
circle(canvas, Point(250, 250), 100, Scalar(200, 10, 10), -1, 8);
-
ellipse(canvas, RotatedRect(Point(300, 100), Size(200, 100), 45.5),Scalar(0, 200, 10), -1, LINE_AA);
-
在img中写文字
putText(canvas, “value”,Point(x,y), Font_, 1.0 , scalar(b,g,r), thickness, line_type)
7. 像素通道的分离和合并 (实现通道提取)
putText(): 向图片中写文字
scalar: 可以()里面可以只有一个值
split: 通道分离
- 输入是img, 输出是
vector <mat>
容器
merge: 合并(输入,输出),
roi:
rect roi
mat sub = img(roi)// sub是roi在img中的地方, 是赋值
mat sub = img(roi).clone// 是引用
8 图像直方图
pic: 最高峰
range: 像素分类
bin: 每个分类好了的像素区域
逻辑: 分离 --> 计算直方图(calcHist)–>输出图片归一化(对应高度)–>把归一化绘制到输出img上
API:
- calcHist
- 图片指针
- 多少张图片
- 哪一个channels
- mask
- 直方图输出
- 直方图的维度
- 直方图有多少个bins: 是一个指针
- rangs, 是一个指针
- 每个bins自动分配取值范围
- 累加器
void Person::calcHistogram(Mat& img)
{
imshow("原图", img);
vector<Mat> vImg;
split(img, vImg);
Mat bImg; Mat gImg; Mat rImg;
int bins = 256;
float rang[] = { 0,255 };
const float* rangs = { rang };
calcHist(&vImg[0], 1, 0, Mat(), bImg, 1, &bins, &rangs, true, false);
calcHist(&vImg[1], 1, 0, Mat(), gImg, 1, &bins, &rangs, true, false);
calcHist(&vImg[2], 1, 0, Mat(), rImg, 1, &bins, &rangs, true, false);
imshow("b", bImg);
imshow("g", bImg);
imshow("r", bImg);
Mat dst = Mat::zeros(Size(500, 300), img.type());
int margin = 50;
int height = dst.rows - 2 * margin;
cout << "高度" << height << endl;
normalize(bImg, bImg, 0, height, NORM_MINMAX);
normalize(gImg, gImg, 0, height, NORM_MINMAX);
normalize(rImg, rImg, 0, height, NORM_MINMAX);
double step = (dst.cols - 2 * 50) / double(bins);
cout << "步长" << step << endl;
for (int i = 0; i < bins-1; i++)
{
//cout << bImg.at<double>(i, 0) << endl;
line(dst, Point(i * step + margin, height + margin - bImg.at<float>(i, 0)), Point((i + 1) * step + margin, height + margin - bImg.at<float>(i + 1, 0)), Scalar(255, 0, 0), 2);
//cout << "电一" << i * step + margin << endl;
line(dst, Point(i * step + margin, height + margin - gImg.at<float>(i, 0)), Point((i + 1) * step + margin, height + margin - gImg.at<float>(i + 1, 0)), Scalar(0, 255, 0), 2);
line(dst, Point(i * step + margin, height + margin - rImg.at<float>(i, 0)), Point((i + 1) * step + margin, height + margin - rImg.at<float>(i + 1, 0)), Scalar(0, 0, 255), 2);
}
imshow("输出", dst);
}
9 图像图均衡化
1. 思路: 划分bins --> 求出bins pik --> pik / rangs = 占比 --> 然后依次每个占比累加 / bins --> 求占比 --> 重新划分 --> 映射
用来增加许多图像的全局对比度, ,
2. API
equalizeHist: 直方图均衡化 equalizeHist:
compareHist: 直方图比较
- HISTCMP_BHATTACHARYYA: 巴斯距离比较
- HISTCMP_CORREL: 相关性比较
3. 重点
下面代码最好把图片转换为hsv(色彩分明), 在进行比较
Mat myCalcHistogram(Mat& img)
{
int bin1 = 256; int bin2 = 256; int bin3 = 256;
int bins[] = { bin1,bin2,bin3 };
float rang1[] = { 0,255 }; float rang2[] = { 0,255 }; float rang3[] = { 0,255 };
const float* rangs[] = { rang1,rang2,rang3 };
int channels[] = { 0,1,2 };
Mat dst;
calcHist(&img, 1, channels, Mat(), dst, 3, bins, rangs, true, false);
return dst;
}
void Person::compare(Mat& img1, Mat& img2)
{
cout << "aa" << endl;
imshow("原图1", img1); imshow("原图2", img2);
Mat histImg = myCalcHistogram(img1);
Mat histImg2 = myCalcHistogram(img2);
normalize(histImg, histImg, 0, 1, NORM_MINMAX);
normalize(histImg2, histImg2, 0, 1, NORM_MINMAX);
double minBha = compareHist(histImg, histImg2, HISTCMP_BHATTACHARYYA);
double maxBha = compareHist(histImg, histImg, HISTCMP_BHATTACHARYYA);
cout << "巴斯距离越大差异越大" << minBha << "相同图" << maxBha << endl;
double minCor = compareHist(histImg, histImg2, HISTCMP_CORREL);
double manCor = compareHist(histImg, histImg, HISTCMP_CORREL);
cout << "相似性越大差异越小: 小" << minCor << "相同图: 大" << manCor << endl;
}
10 颜色查找表
1. api
- applyColorMap(img,dst,color[])
- color:是颜色表, 在百度中可以查的到, 速度快
2. 注意点: 只支持彩色图像和灰度图像
void Person::colorMap(Mat& img)
{
int colormap[] =
{
COLORMAP_AUTUMN ,
COLORMAP_BONE,
COLORMAP_CIVIDIS,
COLORMAP_DEEPGREEN,
COLORMAP_HOT,
COLORMAP_HSV,
COLORMAP_INFERNO,
COLORMAP_JET,
COLORMAP_MAGMA,
COLORMAP_OCEAN,
COLORMAP_PINK,
COLORMAP_PARULA,
COLORMAP_RAINBOW,
COLORMAP_SPRING,
COLORMAP_TWILIGHT,
COLORMAP_TURBO,
COLORMAP_TWILIGHT,
COLORMAP_VIRIDIS,
COLORMAP_TWILIGHT_SHIFTED,
COLORMAP_WINTER
};
imshow("原图", img);
Mat dst;
int index = 0;
while (true)
{
applyColorMap(img, dst, colormap[index % 19]);
index++;
imshow("颜色表", dst);
int key = waitKey(100);
if (key == 27)
{
break;
}
}
}
11. 图像卷积(滤波)
1. 概率
卷积核窗口系数: 卷积核里面的数据
原理: (卷积系数/权重 * 处于卷积核位置的img的像素相加) / 卷积核面积 = result,取整(), 把原图中处于卷积核的中心位置像素替换为 result. 卷积核一次前进移动, 反复执行.
2. 卷积的作用
- 实现图像的模糊
- 计算图像的梯度
- 发现边缘
- 进行噪声抑制
- 图像的锐化或者增强
2. 处理边缘
- 边缘填0
- border_default
- border_replicate
- border_warp
- border_reflect_101
- border_constant
3. API
blur: 一般用于处理图像的随机噪声
boxFilter 是 blur的快速版本, 最好使用boxFilter
- 锚定位置
- Point(-1,-1)——卷积核与原图左边重合;卷积核与原图上边重合;锚定点位于卷积核中心 注意:卷积核大小不同,如3x3,5x5,则卷积核中心不同(偶数无核)
- Point(0,0)——卷积核与原图左边重合;卷积核与原图上边重合;锚定点位于Point(0,0)
- Point(1,1)——卷积核与原图左边-1重合;卷积核与原图上边-1重合;锚定点位于Point(1,1)
- Point(2,2)——卷积核与原图左边-2重合;卷积核与原图上边-2重合;锚定点位于Point(2,2)
4. 注意点:
当卷积核大小为偶数: 其实这个时候中心也为(ksize/2), 对的卷积核,中心位置为Point**(1,1)(2,2)**。
imshow("原图", img);
Mat dst = Mat::zeros(img.size(),img.type());
Mat dst2;
for (int row = 1; row < img.rows -1; row++)
{
for (int col = 0; col < img.cols; col++)
{
int b = round((img.at<Vec3b>(row - 1, col - 1)[0] + img.at<Vec3b>(row - 1, col)[0] + img.at<Vec3b>(row - 1, col + 1)[0] +
img.at<Vec3b>(row, col - 1)[0] + img.at<Vec3b>(row, col)[0] + img.at<Vec3b>(row, col + 1)[0] +
img.at<Vec3b>(row + 1, col - 1)[0] + img.at<Vec3b>(row + 1, col)[0] + img.at<Vec3b>(row + 1, col + 1)[0]) / 9);
int g = round((img.at<Vec3b>(row - 1, col - 1)[1] + img.at<Vec3b>(row - 1, col)[1] + img.at<Vec3b>(row - 1, col + 1)[1] +
img.at<Vec3b>(row, col - 1)[1] + img.at<Vec3b>(row, col)[1] + img.at<Vec3b>(row, col + 1)[1] +
img.at<Vec3b>(row + 1, col - 1)[1] + img.at<Vec3b>(row + 1, col)[1] + img.at<Vec3b>(row + 1, col + 1)[1]) / 9);
int r = round((img.at<Vec3b>(row - 1, col - 1)[2] + img.at<Vec3b>(row - 1, col)[2] + img.at<Vec3b>(row - 1, col + 1)[2] +
img.at<Vec3b>(row, col - 1)[2] + img.at<Vec3b>(row, col)[2] + img.at<Vec3b>(row, col + 1)[2] +
img.at<Vec3b>(row + 1, col - 1)[2] + img.at<Vec3b>(row + 1, col)[2] + img.at<Vec3b>(row + 1, col + 1)[2]) / 9);
dst.at<Vec3b>(row, col)[0] = b; dst.at<Vec3b>(row, col)[1] = g; dst.at<Vec3b>(row, col)[2] = r;
}
}
imshow("手动blue", dst);
blur(img, dst2, Size(3, 3), Point(-1, -1),BORDER_DEFAULT);
imshow("blur", dst2);
5. 处理边缘像素
进行卷积之前就应该填充好
填充类型 | 方法 |
---|---|
BORDERT_CONSTANT(常量填充),填充的是0 | iiii|abc|iii (i:常量,abc:像素值) |
border_replicate(填充的是2头的值) | aaa|abc|hhh |
border_warp(尾填到头,头填到尾) | cba|abc|abc |
border_reflect_101(看上去最合理) | fcb|abcf|cba |
border_default(看上去最合理) | fcb|abcf|cba |
1. 边缘填充:
- copyMakeBorder // 可以用来做边框, 也可以用来卷积
copyMakeBorder(src,dst,margin, margin1, margin2,margin, 填充方式, 填充的颜色)
void Person::fill(Mat& img)
{
imshow("原图", img);
int top = 2; int bottom = 2; int left = 3; int right = 3;
int margin[] = { top,bottom,left,right };
Mat dst; Mat dst1;
copyMakeBorder(img, dst, margin[0], margin[1], margin[2], margin[3], BORDER_CONSTANT, Scalar(0, 0, 250));
imshow("dst", dst);
blur(dst, dst1, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
imshow("blur", dst1);
cout << img.rows << "\t" << dst.rows << "\t" << dst1.rows << endl;
}
12 图像模糊
-
高斯模糊
-
优点: 会更好的保留中心点像素, 对轮廓保存好 -
卷积核系数不一样, 非均值, 越是中心位置权重系数越高, 中心化对称(高斯数学公式),
-
API: GuassianBlur
- 设置了第三个参数, 第四个参数会自动换算, 只要当size = 0的时候才需要设置第四个参数
- 高斯模糊默认是中心位置所有不需要设定锚定点
-
-
盒子模糊(均值模糊)
优点: 可以对图像任意一个方向模糊 (调节卷积核大小) - API: boxfilter
- 参数3: img深度(如果-1就是与原图一致)
- 参数5: 锚定点,
- 参数6: 对卷积核归一化, 加和 = 1
void Person::fill(Mat& img)
{
imshow("原图", img);
int top = 2; int bottom = 2; int left = 3; int right = 3;
int margin[] = { top,bottom,left,right };
Mat dst; Mat dst1; Mat dst2;
copyMakeBorder(img, dst, margin[0], margin[1], margin[2], margin[3], BORDER_CONSTANT, Scalar(0, 0, 250));
imshow("dst", dst);
blur(img, dst1, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
imshow("blur", dst1);
boxFilter(img, dst2, -1, Size(3, 3), Point(-1, -1), true, BORDER_DEFAULT);
imshow("dst2", dst2);
cout << img.rows << "\t" << dst.rows << "\t" << dst1.rows <<"\t" << dst2.rows<<endl;
}
13 自定义卷积核(自定义滤波器)
1. API: filter2D
- 参数5: 用来提升亮度
2.API: convertScaleAbs(Mat,Mat) :
装换为8U的, 并且所有的值为正数
3. 注意点:
-
均值滤波
如果原图是16或者16以下的depth,kernel depth * 2 -
非均值滤波: 非均值卷积核depth应该设置大些, (不然数据会溢出, 有负数), 然后使用abs转换全为正数, 不然输出的全是空白图像
void Person::myFilt(Mat& img)
{
imshow("input", img);
Mat dst; Mat dst1;
int width = 12;
Mat kernel = Mat::ones(width, width, CV_16F) / float(width * width);
filter2D(img, dst, -1, kernel, Point(-1, -1), 0, BORDER_DEFAULT);
imshow("均值滤波", dst);
cout << img.rows << "\t" << dst.rows << endl;
Mat kernel1 = (Mat_<int>(2, 2) << 1, 0, 0, -1);
filter2D(img, dst1, CV_32F, kernel1, Point(-1, -1), 128, 4);
convertScaleAbs(dst1, dst1);
imshow("非均值滤波", dst1);
}
14 图像梯度(一阶导数)
robot算子: 非均值卷积, 求出x的梯度和y梯度相加
1.sobel 算子
- 参数4,5: 哪个方向的梯度(x,y),0为假, 1为真
- 参数7: 扩张的倍数
- 参数8: 用来提升亮度
2. scharr算子
sobel的增强版, scharr算子的卷子核系数大
三种梯度算子呈现强度: robot < sobel < scharr
imshow("原图", img);
Mat gradX; Mat gradY;
Mat robotX = (Mat_<int>(2, 2) << 1, 0, 0, -1);
Mat robotY = (Mat_<int>(2, 2) << 0, 1, -1, 0) ;
filter2D(img, gradX, CV_32F, robotX, Point(-1, -1), 0, 4);
filter2D(img, gradY, CV_32F, robotY);
convertScaleAbs(gradX, gradX);
convertScaleAbs(gradY, gradY);
Mat dst;
add(gradX, gradY, dst);
imshow("robot梯度", dst);
/*Mat sobelx = (Mat_<int>(3,3)<<-1,0,1,
-2,0,2,
-1,0,1);
Mat sobelY = (Mat_<int>(3, 3) -1, -2, -1,
0, 0, 0,
1, 2, 1);
Mat gradSobelX; Mat gradSobelY;
filter2D(img, gradSobelX, CV_32F, sobelx, Point(-1, -1));
filter2D(img, gradSobelY, CV_32F, sobelY, Point(-1, -1));
convertScaleAbs(gradSobelX, gradSobelX);
convertScaleAbs(gradSobelY, gradSobelY);
Mat dst1; Mat result;
add(gradSobelX, gradSobelY, dst1);*/
Mat dst1;
Sobel(img, gradX, CV_32F, 1, 0, 3, 1, 0, BORDER_DEFAULT);
Sobel(img, gradY, CV_32F, 1, 0, 3, 1, 0, BORDER_DEFAULT);
convertScaleAbs(gradX, gradX);
convertScaleAbs(gradY, gradY);
//因为sobel卷积核其中有些系数为2, 通过abs转化会放大梯度
addWeighted(gradX, 0.5, gradY, 0.5, 0, dst1); //不然图片会偏白,
imshow("sobel", dst1);
Mat dst2;
Scharr(img, gradX, CV_32F, 1, 0, 1, 0);
Scharr(img, gradY, CV_32F, 0, 1, 1, 0, 4);
convertScaleAbs(gradX, gradX);
convertScaleAbs(gradY, gradY);
addWeighted(gradX, 0.5, gradY, 0.5, 0, dst2);
imshow("scharr", dst2);
15 图像边缘发现(二阶导数)
四邻域, 八邻域, 拉普拉斯变种
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UKNPniCL-1656318416405)(D:\opencvSourse\openImg\拉普拉斯分类.png)]
推导公式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PWX2C5wE-1656318416406)(D:\opencvSourse\openImg\图像锐化原理.png)]
拉普拉斯api得出来的是边缘
void Person::laplasi(Mat& img)
{
cout << img.channels() << endl;
imshow("原图", img);
Mat Box = (Mat_<int>(3, 3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
Mat dst;
filter2D(img, dst, CV_32F, Box,Point(-1,-1),0,4);
convertScaleAbs(dst, dst);
imshow("拉普拉斯", dst);
Mat dst1; Mat dst2;
Laplacian(img, dst1, -1);
add(img, dst1, dst2);
imshow("锐化", dst1);
}
16 usm锐化
原理: blur/高斯 - 拉普拉斯算子
imshow("原图", img);
Mat gua; Mat lap;
GaussianBlur(img, gua, Size(3,3),0);
Laplacian(img, lap, -1, 3, 1, 0);
Mat result;
addWeighted(gua, 1, lap, -0.7, 0, result);
imshow("usm", result);
17 图像的噪声和去噪
-
API:
- randn 产生噪声
- 参数1: 输入图像
- 均值
- 方差
- randn 产生噪声
-
去噪:
-
中值滤波: 能反映信号理想的样子, 去除极致点,
-
原理: 对图像某一块区域进行排序, 取出sort中间的值替换这块局域的中心点
还有最小值滤波(sort[0]替换中心点), 最大值滤波(sort[lenght-1]替换中心点)
-
作用: 最适用于椒盐噪声非0就255
-
重要: 卷积核必须是奇数而且是大于1
-
-
均值滤波: 没有反映信号本来的样子, 会扰动
-
API:
-
medianBlur: 均值滤波
没有考虑中心像素的权重, 可能会导致图片破坏
-
GaussianBlur 高斯滤波 没有考虑中心像素点与周围像素点差值很大
-
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DqhqGCcS-1656318416407)(D:\opencvSourse\openImg\均值滤波和中值滤波.png)]
18 边缘保留滤波(EPF)
考虑图像的梯度(边缘):高斯双边, 均值前移, 非局部均值去噪, 局部均值方差
1. EPF概念
-
高斯双边模糊
- 原理: 当差异比较大的时候(边缘),权重会趋向于0(有边缘的时候不进行模糊)
- API: bilaterFilter
- 参数3: 过滤过程中每个像素领域的直径范围, 如果这个值是非正数, 则函数会从第五个参数sigmaSpace计算该值
- 参数4: 颜色过滤器, 这个参数越大, 表明该像素领域内有越宽广的颜色会混合到一起, 产生较大的半相等颜色区域(
理解为中心像素权重, 越大中心像素权重越高, 细微的部分会丢失 ), - 参数5: 空间滤波器, 如果该值较大, 则意味着越远的像素将相互影响, 从而使更大的区域中足够相似的颜色获取相同颜色(
理解为,sigmaS越大,去噪越明显, 一般为Half_size为4,可以选用2sigma原则,设置为2 ,)
-
非局部均值滤波
-
简单理解原理: 相似像素块, 权重比较大, 不相似的权重比较小
-
API
- fastNIMeansDenoising 速度慢
- 参数三: h 决定过滤器强度。h 值高可以很好的去除噪声,但也会把图像的细节抹去。(取 10 的效果不错)
- 参数五: 在划分区域的卷积核
- 参数六: 搜索窗口(划分区域)
- fastNIMeansDenoisingColord 彩色版本
- fastNIMeansDenoising 速度慢
-
void Person::noise(Mat& img)
{
Mat result1; Mat result2;
Mat dst = img.clone();
imshow("原图", img);
//salt and pepper
RNG r(12345);
int ipt = 1000;
for (int i = 0; i < ipt; i++)
{
int xNum = r.uniform(0, img.cols);
int yNum = r.uniform(0, img.rows);
if (i % 2 == 1)
{
img.at<Vec3b>(xNum, yNum) = Vec3b(255, 255, 255);
}
else
{
img.at<Vec3b>(xNum, yNum) = Vec3b(0, 0, 0);
}
}
imshow("椒盐噪声", img);
medianBlur(img, result1, 3);
imshow("椒盐去噪", result1);
GaussianBlur(img, result2, Size(3, 3), 0);
imshow("高斯去噪", result2);
//高斯噪声
Mat res1; Mat res2;
Mat src = Mat::zeros(img.size(), img.type());
randn(src, Scalar(25,15,45), Scalar(60,40,30));
add(dst, src, dst);
imshow("高斯", dst);
bilateralFilter(dst, res1, 0, 100, 10);
imshow("双边模糊", res1);
fastNlMeansDenoisingColored(dst, res2, 3, 3, 7, 21);
imshow("非局部去噪",res2);
}
18边缘提取
1. 基本概念
- 边缘法线: 与边缘垂直的线, 在图像像素强度变化最大
- 边缘强度
2. 边缘类型
实际图像可能不平整有噪声
- 跃迁类型: 只有陡坡或者下坡
- 屋脊类型: 有上坡和下坡
3. 基于梯度的边缘提取
步骤:
- 去噪(卷积核不能太大, 不然会破坏掉边缘, 一般使用3或者5),
- 基于梯度提取边缘: robot, sobel, prewit Operator
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WSFkBplS-1656318416407)(D:\opencvSourse\openImg\梯度提取边缘算子.png)]
-
基于阈值T,得到边缘(梯度>T保留, <丢弃)
问题: 基于T的会不够连贯
API: canny 输出是一个二值图像
非最大抑制: 求出角度, 如果中心像素大于2侧梯度值则保留, 否则丢弃, 然后进行阈值连接
阈值连接: T1 / T2 = 2 ≈ 2~3, 大于T1全部保留, 小于T2全部丢弃, t1至T2之间的如果可以连接则保留, 否则丢弃
参数:
- 参数5: suobel算子大小
- 参数6: 为计算图像梯度幅度(gradient magnitude)的标识。其默认值为 False。如果为 True,则使用更精确的 L2 范数进行计算(即两个方向的导数的平方和再开方),否则使用 L1 范数(直接将两个方向导数的绝对值相加)。
- 参数3,4: 高低阈值的比值应该是2~3
void myCanny(int min, void* img)
{
Mat newImg = *(Mat*)img;
Mat dst; Mat dst1;
Canny(newImg, dst, min, 200, 3, false);
bitwise_and(newImg, newImg, dst1, dst);
imshow("边缘提取", dst1);
}
void Person::canny(Mat& img)
{
string barName = "提取范围";
string winName = "边缘提取";
namedWindow(winName, WINDOW_AUTOSIZE);
imshow("原图", img);
int min = 50;
int max = 120;
createTrackbar(barName, winName, &min, max, myCanny,(void*)&img);
myCanny(0, (void*)&img);
}
19 二值图像概念 sic(Bolb分析)
对机器视觉或者工业领域
灰度图像 : 单通道, 取值范围0~255
二值图像: 单通道, 要么是0要么是255, 以黑色作为背景
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IfSlKbwl-1656318416408)(D:\opencvSourse\openImg\二值分割5钟方法.png)]
二值化
- THRESH_BINARY
- THRESH_BINARY_INV
阈值化
- THRESH_TRUNC: 截断, 对应第三种方式
- THRESH_TOZEREO:
- thresh_TOZERO_INV
API:
threshold: 只接受灰色图像
void Person::myThreshold(Mat& img)
{
Mat dst;
cvtColor(img, img, COLOR_BGR2GRAY);
imshow("原图灰色图像", img);
//二值化
threshold(img, dst, 127, 255, THRESH_BINARY);
imshow("二值化", dst);
//反二值化
threshold(img, dst, 127, 255, THRESH_BINARY_INV);
imshow("反二值化", dst);
//阈值化切割, 小于阈值化保持原数据, 否则为T
threshold(img, dst, 127, 255, THRESH_TRUNC);
imshow("阈值化切割", dst);
//阈值化,大于T的保持原数据, 其他的为0
threshold(img, dst, 127, 255, THRESH_TOZERO);
imshow("阈值化", dst);
//反阈值化,
threshold(img, dst, 127, 255, THRESH_TOZERO_INV);
imshow("反阈值化", dst);
}
//返回二值化分割
Mat Person::myThresh(const Mat& img)
{
Mat newImg = img.clone();
GaussianBlur(newImg, newImg, Size(3, 3), 0);
cvtColor(newImg, newImg, COLOR_BGR2GRAY);
Mat dst;
threshold(newImg, dst, 0, 255, THRESH_BINARY | THRESH_OTSU);
return dst;
}
19.1 全局阈值
概述:
-
通过mean, 取0号下标得均值,这个均值设为T
- 缺点: 不能反应图像像素分布情况
-
OTSU
- api: THRESH_OTSU, 返回值是一个double类型
- 计算类内方差: 比重 * 方差 + 比重1*方差1, 取切割后,类内方差最小的阈值T
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kr1Qrs0g-1656318416409)(D:\opencvSourse\openImg\OTSU解释.png)]
-
三角法
- api: thresh_triangle
缺点: 只对单峰比较友好,适合医学领域:x光片, 生物图像.
void Person::tThreshold(Mat& img) { Mat dst; cvtColor(img, img, COLOR_BGR2GRAY); imshow("原图", img); Scalar m = mean(img); threshold(img, dst, m[0], 255, THRESH_BINARY); imshow("均值", dst); //otsu double otsu = threshold(img, dst, 0, 255, THRESH_BINARY | THRESH_OTSU); imshow("otsu", dst); //三角法 double angle = threshold(img, dst, 0, 255, THRESH_BINARY | THRESH_TRIANGLE); imshow("三角法", dst); cout << "otsu切割阈值T: " << otsu << "\t" << "三角法: " << angle << endl <<"\t" << "均值:"<<m[0]; }
-
自适应阈值
概述: 全局阈值的局限性:
相对于全局自适应, 会提取更多的梯度
原图-模糊后的图+偏执常量 > 0 = 255, 否则=0
- API: adaptiveThreshold, 卷积核必须为奇数(倒数第二个)
- ADAPTIVE_THRESH_MEAN_C 盒子模糊
- ADAPTIVE_THRESH_GAUSSIAN_C 高斯模糊
//自适应阈值分割
void Person::myAdaptive(Mat &img)
{
Mat dst;
cvtColor(img, img, COLOR_BGR2GRAY);
imshow("原图", img);
threshold(img, dst, 0, 255, THRESH_BINARY | THRESH_OTSU);
imshow("otsu", dst);
//自适应
adaptiveThreshold(img, dst, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 21, 5);
imshow("自适应", dst);
}
20 ccl连通组件扫描(默认值是8领域)
如果是白色对象 将过滤点不会进行处理, 只有为黑色的时候才会考虑连通性
opencv是基于块扫描结合决策表(DT) ==> BBDT
-
概念: 联通组件标记: CCL
-
算法:
-
基于像素的扫描的方法
缺点: 有大量重复的扫描, 而且不规则扫描
-
基于块扫描的方法
-
两步法扫描
概率: 对前景像素点产生一个临时标记, 通过连通性进行等价队列合并, 然后获取lable
决策表:DT
-
现在opencv联通组件采用的是BBDT: 块扫描+决策表
-
-
api: connectedComponents,
背景必须为黑色, 只能知道有多少个联通组件 输出图片类型为cv_32s . 数量包括背景, 真实数量=result - 1, 组件第一个是背景- 参数:
- 参数3: 选择几领域(8领域或者4领域)
- 参数4: 输出类型, 默认是cv_32s
- 返回值是组件个数+ 1个背景
api:connectedComponentsWithStats
- 携带附加信息(像素值,外接矩形大小,中心位置)
- 第三个mat对象:外接矩形,矩形面积
- stats:
前四个值是连通组件外接矩形的信息(前2个是初始点, 后2个是宽高), 最后一个值是统计出来的这个前景对象的面积(像素面积)
- stats:
- 第四个mat对象:组件的中心坐标
一般先进行高斯模糊降噪
void Person::connectComp(Mat& img) { RNG rn(12345); imshow("原图", img); Mat denoi; GaussianBlur(img, denoi, Size(11, 11), 0); imshow("去噪", denoi); Mat grey; cvtColor(denoi, grey, COLOR_BGR2GRAY); Mat cutting; threshold(grey, cutting, 0, 255, THRESH_BINARY | THRESH_OTSU); imshow("二值化", cutting); Mat labels = Mat::zeros(img.size(), CV_32S); int num = connectedComponents(cutting, labels, 8, CV_32S, CCL_DEFAULT); cout << "数量" << num << endl; //染色 vector<Vec3b>color(num); color[0] = Vec3b(0, 0, 0); for (int index = 1; index < num; index++) { color[index] = Vec3b(rn.uniform(0, 256), rn.uniform(0, 256), rn.uniform(0, 256)); } Mat result = Mat::zeros(img.size(), CV_8UC3); for (int x = 0; x < result.rows; x++) { for (int y = 0; y < result.cols; y++) { result.at<Vec3b>(x, y) = color[labels.at<int>(x, y)]; } } //显示详细信息 Mat stats; Mat centroids; int num1 = connectedComponentsWithStats(cutting, labels, stats, centroids, 8, CV_32S, CCL_DEFAULT); string numn = to_string(num1); for (int i = 1; i < num1; i++) { //center double centerX = centroids.at<double>(i, 0); double centerY = centroids.at<double>(i, 1); //ractangle int racLeft = stats.at<int>(i, CC_STAT_LEFT); int racTop = stats.at<int>(i, CC_STAT_TOP); int racWidth = stats.at<int>(i, CC_STAT_WIDTH); int racHeight = stats.at<int>(i, CC_STAT_HEIGHT); int area = stats.at<int>(i, CC_STAT_AREA); cout << "面积" << i << area << endl; string s = to_string(area); circle(result, Point(centerX, centerY), 3, Scalar(255, 20, 20), 2, 8); Rect rect = Rect(racLeft, racTop, racWidth, racHeight); rectangle(result, rect, Scalar(0, 0, 200), 2, LINE_8); putText(result, s, Point(centerX, centerY), FONT_HERSHEY_PLAIN, 1.0, Scalar(0, 255, 0), 2, 8); putText(result, numn, Point(50,50), FONT_HERSHEY_PLAIN, 3, Scalar(0, 255, 0), 2, 8); } imshow("切割染色", result); }
20.1 图像轮廓发现
1. 轮廓发现(二值化的前景边缘)
-
基本概率: 理解为图像边界, 主要针对二值图像, 轮廓是一系列点的集合
基于联通组件, 反映图像拓扑结构
-
算法:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FeJh7Je9-1656318416410)(D:\opencvSourse\openImg\轮廓发现.png)]
-
API:
-
findContours
轮廓: 是点的集合
vector<vector<point>>P
多个组件, 组件里面是像素层次: vec4i
拓扑结构: list或者tree或者最外层最大的轮廓(external) retr
编码方式:
-
chain_approx_simple
hirearchy里面只存储: 4个顶点位置
-
chain_approx_none
hirearchy里面存储所有轮廓的点位
-
-
drawContours
contourldx: 绘制的是哪一个轮廓,如果是-1就是绘制全部
void Person::myContour(Mat& img) { //binaryimg是一个处理过的二值化图像 Mat binaryImg = this->myThresh(img).clone(); Mat dst = Mat::zeros(img.size(),CV_8UC3); Mat dst1 = dst.clone(); Mat dst2 = dst.clone(); imshow("二值化", binaryImg); vector<vector<Point>>contours; vector<Vec4i>hierarchy; findContours(binaryImg, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point()); for (int i = 0; i < contours.size(); i++) { drawContours(dst, contours, i, Scalar(0, 255, 0), 2, LINE_8); } imshow("轮廓", dst); findContours(binaryImg, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point()); drawContours(dst1, contours, -1, Scalar(255, 0, 0), 2, 8); imshow("最大轮廓", dst1); }
-
20.2 轮廓计算
- 计算公式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yC6XUZCC-1656318416411)(D:\opencvSourse\openImg\轮廓面积和周长计算.png)]
-
contourArea: 轮廓面积 contourArea[contour1]
arcLength: 轮廓周长(contour1, 是否闭合)
boundingRect: 最大外接矩形
nimAreaRect: 最小矩形 , 数据类型是RotatedRect
//轮廓细节 void Person::contourDetails(Mat& img) { Mat result = Mat::zeros(img.size(), CV_8UC3); imshow("原图", img); GaussianBlur(img, img, Size(3, 3), 0); cvtColor(img, img, COLOR_BGR2GRAY); Mat dst; threshold(img, dst, 0, 255, THRESH_BINARY_INV | THRESH_OTSU); imshow("二值化分割", dst); vector<vector<Point>>contours; vector<Vec4i>hierarchy; //Mat result = Mat::zeros(img.size(), CV_8UC3); findContours(dst, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point()); /*double minLength; cout << "输入最小周长" << endl; cin >> minLength; double minArea; cout << "输入最小面积" << endl; cin >> minArea;*/ for (int i = 0; i < contours.size(); i++) { if (arcLength(contours[i],true) < 100 || contourArea(contours[i]) < 10) continue; //最大外接矩形 Rect maxRect = boundingRect(contours[i]); rectangle(result, maxRect, Scalar(200, 20, 20), 2, 8); //最小外接矩形 RotatedRect minRect = minAreaRect(contours[i]); ellipse(result, minRect, Scalar(20, 200, 20), 2, 8); Point2f pts[4]; //通过点连接成线绘制最小外接矩形 minRect.points(pts); for (int i = 0; i < 4; i++) { line(result, pts[i], pts[(i + 1)%4], Scalar(20, 20, 200), 2, 8); } drawContours(result, contours, -1, Scalar(100, 100, 100), 2, 8); cout << "轮廓" << i << "面积: " << contourArea(contours[i]) << "\t" << "轮廓" << i << "周长: " << arcLength(contours[i], true) << endl; } imshow("输出", result); }
20.3 轮廓匹配
作用: 可以匹配大小不一致, 旋转不一致的图像
API
-
Moments : 几何矩 Moments(contours[i])
作用: 计算弧矩, 计算中心位置
根据原理推中心矩: mm.m10 / mm.00 = x || mm.m01 / mm.00 = y
-
HuMoments: 弧矩(Moments, mat对象) : 选择不变性, 缩放不变性
-
matchShapes: 比较弧矩 参数3:contours_match_l1/l2/l3, 第一种比较效果比较好
void contoursFn( Mat &dst, const vector<vector<Point>> &imgContours, const vector<vector<Point>>& srcContours)
{
Moments srcMm = moments(srcContours[0]);
Mat srcHu;
HuMoments(srcMm, srcHu);
for (int i = 0; i < imgContours.size(); i++)
{
Mat imgHu;
Moments imgMm = moments(imgContours[i]);
double pX = imgMm.m10 / imgMm.m00;
double pY = imgMm.m01 / imgMm.m00;
circle(dst, Point(pX, pY), 3, Scalar(200, 200, 20), 2, LINE_8);
HuMoments(imgMm, imgHu);
double ss = matchShapes(imgHu, srcHu, CONTOURS_MATCH_I1,0);
//cout << ss << endl;
if (ss < 2.0)
{
cout << ss << endl;
drawContours(dst, imgContours, i, Scalar(20, 20, 200), 2, 8);
}
else
{
cout << "匹配不成功" << endl;
}
}
imshow("axx", dst);
/*for (int i = 0; i < srcContours.size(); i++)
{
moImg.push_back(moments(srcContours[i]));
}*/
}
void Person::contourComp(Mat& img, Mat& src)
{
namedWindow("匹配轮廓图", WINDOW_FREERATIO);
imshow("匹配轮廓图", src);
Mat binaryImg = this->myThresh(img);
Mat binarysrc = this->myThresh(src);
vector<vector<Point>> imgContours;
vector<vector<Point>> srcContours;
vector<Vec4i> hierarchy;
vector<Vec4i> hierarchy1;
findContours(binaryImg, imgContours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
findContours(binarysrc, srcContours, hierarchy1, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
contoursFn(img, imgContours, srcContours);
}
20.4 轮廓逼近与拟合
-
轮廓拟合和逼近(进行轮廓发现之后通过api越来越接近真实的情况)
-
概念:
-
轮廓逼近, 本质是减少编码点, 轮廓逼近的越厉害, 编码点增多
-
拟合圆, 生成最相似的圆或者椭圆
-
-
API:
-
approxPolyDP: 轮廓逼近, 一般用来区分图形
参数:
-
参数1: contours[i]
-
参数2: mat对象 里面有存放每个点,
-
参数3: 精度,值越低精度越高(编码点越多)(一般为4)
-
参数4:是否为闭合区
-
-
-
fitEllipse
- 图像拟合, 返回值是一个rotateRact,
- 通过rotateRact.size.width/height获取拟合之后的长宽
- 通过rotateRact.center, 获取拟合之后的中心点
-
void Person::contourProx(Mat& img)
{
imshow("原图", img);
Mat binaryImg = this->myThresh(img);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binaryImg, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
for (int i = 0; i < contours.size(); i++)
{
Mat poly;
approxPolyDP(contours[i], poly, 4, true);
cout << "图形:" << i << "行数 " << poly.rows << "列数: " << poly.cols << endl;
double len = arcLength(contours[i], true);
double Area = contourArea(contours[i]);
Moments mm = moments(contours[i]);
double pX = mm.m10 / mm.m00;
double pY = mm.m01 / mm.m00;
//if (poly.rows = 4)
//{
// putText(img, "矩形", Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
// string Sarea = "面积: " + to_string(Area);
// string Slen = "周长: " + to_string(len);
// /*putText(img, Sarea, Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
// putText(img, Slen, Point(pX, pY - 20), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);*/
// circle(img, Point(pX, pY), 4, Scalar(20, 20, 200), 2, LINE_8);
//}
//if (poly.rows = 3)
//{
// putText(img, "三角形", Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
// string Sarea = "面积: " + to_string(Area);
// string Slen = "周长: " + to_string(len);
// /*putText(img, Sarea, Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
// putText(img, Slen, Point(pX, pY - 20), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);*/
// circle(img, Point(pX, pY), 4, Scalar(20, 20, 200), 2, LINE_8);
//}
//if (poly.rows = 6)
//{
// putText(img, "6边形", Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
// string Sarea = "面积: " + to_string(Area);
// string Slen = "周长: " + to_string(len);
///* putText(img, Sarea, Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
// putText(img, Slen, Point(pX, pY - 20), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);*/
// circle(img, Point(pX, pY), 4, Scalar(20, 20, 200), 2, LINE_8);
//}
//if (poly.rows > 12)
//{
// putText(img, "圆", Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
// string Sarea = "面积: " + to_string(Area);
// string Slen = "周长: " + to_string(len);
///* putText(img, Sarea, Point(pX, pY - 10), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
// putText(img, Slen, Point(pX, pY - 20), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);*/
// circle(img, Point(pX, pY), 4, Scalar(20, 20, 200), 2, LINE_8);
//}
putText(img, "我", Point(pX, pY - 20), FONT_HERSHEY_PLAIN, 1, Scalar(20, 200, 20), 2, 8);
}
imshow("判断轮廓图像", img);
}
图像拟合
//返回二值化轮廓
vector<vector<Point>> Person::contourOne(Mat& img)
{
Mat binary = this->myBinary(img);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(binary, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point());
return contours;
}
//拟合
void Person::myFitEllipse(Mat& img)
{
imshow("原图", img);
vector<vector<Point>> contours = this->contourOne(img);
for (int i = 0; i < contours.size(); i++)
{
RotatedRect rotRect = fitEllipse(contours[i]);
Point center = rotRect.center;
string height = to_string(rotRect.size.height);
string width = to_string(rotRect.size.width);
string are = to_string(rotRect.size.area());
ellipse(img, rotRect, Scalar(255, 0, 10), 2, 8);
circle(img, center, 3, Scalar(0, 255, 0), 2, LINE_8);
}
imshow("处理图", img);
}
21 霍夫直线检测
对噪声很敏感, 需要降噪
r = x0*cosθ + y0 * sinθ ==>如果点坐标在同一条直线: r和θ是相同的
-
API:
-
HoughLines : 得出来的是极坐标空间参数 vector(p,θ) 直线
霍夫直线检测出的来的结果是vec3F, 距离,角度,累加
参数:
-
参数2: 输出
vector<vec3f>
, 这个vec3f下标0: 代表R(距离)
下标1: 代表θ
下标2: 代表累加值
-
参数3: 步长
-
参数4: 角度: cv_pi / xx
-
参数5: 阈值, 有多少个点集中在一起 算直线
//InputArray image:输入图像,必须是8位单通道图像。 //OutputArray lines:检测到的线条参数集合。 //double rho: 累加器的距离 //double theta:累加器的角度。 //int threshold:累加计数值的阈值参数,当参数空间某个交点的累加计数的值超过该阈值,则认为该交点对应了图像空间的一条直线。 //double srn:默认值为0,用于在多尺度霍夫变换中作为参数rho的除数,rho=rho/srn。 //double stn:默认值为0,用于在多尺度霍夫变换中作为参数theta的除数,theta=theta/stn。 //如果srn和stn同时为0,就表示HoughLines函数执行标准霍夫变换,否则就是执行多尺度霍夫变换
代码:
void Person::Houline(Mat& img) { imshow("原图", img); Mat binary = this->myBinary(img); vector<Vec3f>lines; HoughLines(binary, lines, 1, CV_PI / 180, 160, 0, 0); Point p1; Point p2; for (int i = 0; i < lines.size(); i++) { double step = lines[i][0]; double angle = lines[i][1]; double add = lines[i][2]; cout << "直线" << i << "\t" << "距离: " << step << "角度: " << angle << i << "累加点: " << add << endl; double x = cos(angle); double y = sin(angle); double x0 = step * x; double y0 = step * y; p1.x = cvRound(x0 + 1000 * -y); p1.y = cvRound(y0 + 1000 * x); p2.x = cvRound(x0 - 1000 * -y); p2.y = cvRound(y0 - 1000 * x); line(img, p1, p2, Scalar(0, 0, 255), 2, 8); } imshow("霍夫直线", img); }
-
-
HoughLinesP() 线段
参数2: 为2个点的坐标的容器:
vector<Vec4i>
参数3, 4 步长, 角度
参数5: 阈值, 有这么多点以上连接的线段
参数6: 检测出的最小长度(minLineLength)
参数7: 线段之间的间隔, 如果超出则为新的线段
void Person::houlineP(Mat& img) { Mat result = Mat::zeros(img.size(), img.type()); imshow("原图", img); Mat binary = this->myBinary(img); //之前有封装一个返回二值化的函数的对象 vector<Vec4i> lines; imshow("binary",binary); HoughLinesP(binary, lines, 1, CV_PI / 180, 80,30,10); //Point p1; Point p2; for (int i = 0; i < lines.size(); i++) { /*p1.x = lines[i][0]; p1.y = lines[i][1]; p2.x = lines[i][2]; p2.y = lines[i][3];*/ //line(result, p1, p2, Scalar(0, 0, 255), 1, 8); line(result, Point(lines[i][0], lines[i][1]), Point(lines[i][2], lines[i][3]), Scalar(255, 0, 0), 1, 8); } imshow("霍夫直线", result); }
-
21.1 霍夫圆检测
x = x0 + rcos(θ); y = y0+rsin(θ) 一直圆心(x0,y0)和r
基于梯度去寻找, 不然计算量太大了
-
圆的参数方程:
圆的参数(X0, Y0, r), 圆任意三个点, 以这些点位圆心 r为半径, 相交的一个点
基于梯度或者边缘,轮廓进行查找
- X = X0 + r * cos(θ)
- Y = Y0 + r * sin(θ)
-
api: HoughCircles
- 参数2: 输出的圆
vector<ve3f>
- 参数3: 方法(可以为霍夫梯度,HOUGH_GRADIENT)
- 参数4: 参数方程空间的整个数据的大小(dp, 在其他参数不变的情况下,参数越高, 越容易检测出圆)
- 参数5: 两个圆直径的最小距离 (防止得到同心圆)
- 参数6: canny边缘提取的高阈值
- 参数7: 累计值(阈值)
- 参数8,9: 最小半径, 最大半径
- 参数2: 输出的圆
void Person::houghCir(Mat &img)
{
imshow("原图", img);
Mat result = Mat::zeros(img.size(), CV_8UC3);
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
GaussianBlur(gray, gray, Size(15,15), 2, 2);
vector<Vec3f> cir;
int dp = 2;
double cirSpace = 5;
int add = 100;
int maxThreshold = 100;
double cirMin = 15; double cirMax = 100;
HoughCircles(gray, cir, HOUGH_GRADIENT, dp, cirSpace, add, maxThreshold, cirMin, cirMax);
for (int i = 0; i < cir.size(); i++)
{
int cenX = round(cir[i][0]);
int cenY = round(cir[i][1]);
int radius = round(cir[i][2]);
circle(result, Point(cenX, cenY), radius, Scalar(0, 0, 200), 2, 8);
}
imshow("霍夫找园", result);
}
21.2 图像形态学
图像形态学操作
概念: 可以对灰度图像和二值化图像处理
1. 腐蚀与膨胀 支持彩色图像
原理:对处于卷积核像素的进行排序 腐蚀, 用最小像素来替换中心像素, 膨胀是用最大像素来替换中心像素
作用: 断开或者连接前景对象
getStructuringElement: 获取类型() 形态学
- 类型: morph_rect
- 大小: size(x,y)
- 中心点: Point()
-
API:
腐蚀: erode
膨胀: dilate
参数: 第3个参数是结构元素(形态学)
void Person::erodeDilate(Mat& img)
{
imshow("原图", img);
Mat result;
Mat result1;
Mat binary = this->myBinary(img);
Mat rect = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
erode(img, result1, rect);
imshow("腐蚀", result1);
dilate(img, result, rect);
imshow("膨胀", result);
}
2. 开闭操作
只对起作用的区域产生变化, 对其他没作用到的不会产生变化
-
开操作 = 腐蚀+膨胀,
删除小的干扰块 -
闭操作 = 膨胀+腐蚀, 填充闭合区域
void Person::erodeDilate(Mat& img)
{
imshow("原图", img);
Mat result;
Mat result1;
Mat binary = this->myBinary(img);
Mat rect = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
erode(img, result1, rect);
imshow("腐蚀", result1);
dilate(img, result, rect);
imshow("膨胀", result);
}
-
API: morphologyEx
-
参数3:morph_open
-
参数6: 连续操作(iterations):
作用: 速度会高于直接提升kernel大小
-
3. 结构元素形状(morph形态学)
- 线形 - 水平与垂直
- 矩形 - w*h
- 十字交叉形状
void Person::openCloss(Mat& img)
{
Mat dst;
imshow("原图", img);
Mat binary = this->myBinary(img);
imshow("二值化", binary);
Mat kernel = getStructuringElement(MORPH_RECT, Size(15, 1), Point(-1, -1));
morphologyEx(binary, dst, MORPH_OPEN, kernel, Point(-1, -1), 1);
imshow("腐蚀", dst);
}
4. 形态学梯度(轮廓,线) 支持彩色
- 基本梯度: 膨胀减去腐蚀之后的结果
- 内梯度: 原图减去腐蚀之后的结果
- 外梯度: 膨胀减去原图的结果
6. 更多形态学操作
-
黑帽和顶帽
- 顶帽: 是原图减去开操作之后的结果(morph_TOPHAT)
- 黑帽: 是闭操作之后的结果减去原图(morph_BLACKHAT)
- 顶帽与黑帽的作用是用来提取图像中微小有用信息块
-
击中击不中变换(morph_hitmiss)
通过特点的元素去匹配 如果匹配成功则击中
MORPH_CROSS: 十字交叉元素
21.3 形态学梯度
- 基本梯度: 膨胀减去腐蚀之后的结果
- 内梯