分成三个部分,环境配置,代码过程中遇到的问题以及代码学习 ,包括贾志刚老师opencv基础课程中演示的代码,持续记录中
VS:
注释: 先CTRL+K,然后CTRL+C
取消注释: 先CTRL+K,然后CTRL+U
环境配置
借鉴了这位老哥的,vs2017,vs2017+opencv3.4.1
遇到的问题
1.Mat未定义
参考Mat未定义解决
#include <opencv2\highgui\highgui.hpp> //opencv申明
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv; //不然会报错显示没有定义的符号
using namespace std;
只需要第三行的申明就OK了
2.无法解析的外部命令
a.通常情况
b.debug库的问题,在附加依赖项里加入release库opencv_world341.lib
(我的问题不是这个=。=)
c.对象错了,第一次配成了Debug|win32,无语
代码学习
在B站看的opencv基础课程,老师讲课磕磕巴巴的,但好歹内容循序渐进
1.图像加载、显示、保存
int load_image()
{
//加载
Mat src = imread("D:\\opencv_test_img\\sz.jpg", -1);
//0是灰度图像,>0 BGR格式,<0原格式读取
Mat cvt_src;
if (src.empty())
{
cout<<"fail";
return 0;
}
namedWindow("load img", WINDOW_AUTOSIZE);
//打开一个新的窗口,根据str寻找对应的窗口内容,
//WINDOW_NORMAL图案自动填充窗口,图片可根据窗口拉伸,WINDOW_AUTOSIZE窗口自动适应图片大小,不可调整窗口
imshow("load img", src); //如果未在此功能之前创建窗口,则假定使用创建窗口CV_WINDOW_AUTOSIZE。第一个参数是窗口名称
cvtColor(src,cvt_src,CV_BGR2HLS); //转换图像的色彩模式CV_X2X
imshow("change img", cvt_src);
imwrite("D:\\opencv_test_img\\cvt_src.png",cvt_src); //根据路径中的后缀名保存图像
return 1;
}
imwrite可以参考:imwrite参数以及示例代码
2.mask操作(filter_2D)(更像是卷积,不过没有翻转操作)
手写的卷积操作
//手动卷积= =
int mask()
{
Mat src = imread("D:\\opencv_test_img\\sz.jpg", -1); //0是灰度图像,>0 BGR格式,<0原格式读取
Mat dst;
if (src.empty())
{
cout << "fail";
return 0;
}
//获取图片长宽、通道数,列数=图像列数*通道数,通道数=处理的步长
int cols = src.cols*src.channels();
int rows = src.rows;
int offset = src.channels();
dst = Mat::zeros(src.size(),src.type());
//卷积操作
for (int row = 1; row < rows - 1; row++)
{
const uchar* previous = src.ptr<uchar>(row - 1);
const uchar* current = src.ptr<uchar>(row);
const uchar* next = src.ptr<uchar>(row + 1);
uchar* output = dst.ptr<uchar>(row);
for (int col= offset;col<cols-1;col++) //每行3个数据,构成一个像素点
{
output[col] = saturate_cast<uchar>(5*current[col]-previous[col]-current[col-offset]-current[col+ offset]-next[col]); //saturate_cast<uchar>
}
}
namedWindow("raw img", WINDOW_AUTOSIZE);
imshow("raw img", src);
namedWindow("mask img", WINDOW_AUTOSIZE);
imshow("mask img", dst);
waitKey(0);
system("pause");
return 0;
}
新内容主要有:
src.ptr<uchar>(row) //获取某一行的指针,返回uchar类型的指针
dst = Mat::zeros(src.size(),src.type()); //创建0矩阵,等会儿存放卷积结果
saturate_cast<uchar>() //剪切数据在0~255
//两个for循环,外层控制行,内层控制像素点
//前面cols处理了一下(dst = Mat::zeros(src.size(),src.type());),所以内层是逐数据进行的
filter_2D
//直接调用函数,所以之前写了个寂寞
int filter()
{
Mat src = imread("D:\\opencv_test_img\\sz.jpg", -1); //0是灰度图像,>0 BGR格式,<0原格式读取
Mat dst;
if (src.empty())
{
cout << "fail";
return 0;
}
//获取图片长宽、通道数,列数=图像列数*通道数,通道数=处理的步长
Mat kernel = (Mat_<char>(3, 3) << 0,-1,0,-1,5,-1,0,-1,0);
dst = Mat::zeros(src.size(), src.type());
filter2D(src,dst,src.depth(),kernel);
namedWindow("row img", WINDOW_AUTOSIZE);
imshow("row img", src);
namedWindow("conv img", WINDOW_AUTOSIZE);
imshow("conv img", dst);
waitKey(0);
return 0;
}
调用之后的结果是一样的,主要的新东西有
Mat kernel = (Mat_<char>(3, 3) << 0,-1,0,-1,5,-1,0,-1,0); //创建一个卷积核
filter2D(src,dst,src.depth(),kernel); //调用2D卷积
//(操作对象,存放结果,位图深度(-1是万能参数),卷积核)
3.Mat类型
另一个总结
4.像素读写
首先确定定位像素点的坐标,(0,1)即第0行第1列,Point(0,1)表示第0列第1行
像素读写的代码
float g = gray.at<uchar>(0, 0);
Vec3b v = src.at<Vec3b>(0, 0); //取一个像素点[B,G,R]
//Vec3b,3表示3通道,b表示BGR,Vec3f,则表示三通道的float类型
//如果要使用Vec3f,需要先使用convertTo转换到CV_32F
float B = v[0];
像素值取反的示例代码
void read_pixel()
{
Mat src, gray;
src = imread("D:\\opencv_test_img\\sz.jpg", 1);
cvtColor(src, gray,CV_BGR2GRAY);
int col = src.cols;
int row = src.rows;
int channel = src.channels();
for (int i=0;i<row;i++)
{
for (int j=0;j<col;j++)
{
if (channel == 1)
{
gray.at<uchar>(i, j) = 255 - gray.at<uchar>(i, j); //读取灰度图(1通道)的像素点
}
else if(channel==3)
{
int R = src.at<Vec3b>(i, j)[0]; //读取BGR的像素点(3通道)
int B = src.at<Vec3b>(i, j)[1];
int G = src.at<Vec3b>(i, j)[2];
src.at<Vec3b>(i, j)[0]= 255 - R;
src.at<Vec3b>(i, j)[1] = 255 - B;
src.at<Vec3b>(i, j)[2] = 255 - G;
}
}
}
bitwise_not(gray,gray); //像素取反函数
namedWindow("gray",WINDOW_AUTOSIZE);
imshow("gray",gray);
namedWindow("src", WINDOW_AUTOSIZE);
imshow("src", src);
}
生成空白图像
dst=Scalar(0);
5.图像混合
两张图像线性加权,权重和为1,相关API
addWeighted(src1,0.5,src2,0.5,0,src); //权重和
add(src1,src2,src); //和
multiply(src1,src2,src,0.002); //带参乘积
6.调整图像亮度和对比度
void change_pb_pc()
{
Mat src;
src = imread("D:\\opencv_test_img\\bea2.jpg", 1);
cvtColor(src,src,CV_BGR2GRAY);
int col = src.cols;
int row = src.rows;
float alpha = 0.8;
float gamma = 100;
int channel = src.channels();
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (channel == 3)
{
float R = src.at<Vec3b>(i, j)[0]; //读取BGR的像素点(3通道)
float B = src.at<Vec3b>(i, j)[1];
float G = src.at<Vec3b>(i, j)[2];
src.at<Vec3b>(i, j)[0] = saturate_cast<uchar>( R * alpha + gamma);
src.at<Vec3b>(i, j)[1] = saturate_cast<uchar>(B * alpha + gamma);
src.at<Vec3b>(i, j)[2] = saturate_cast<uchar>(G * alpha + gamma);
}
else if (channel==1)
{
int gray = src.at<uchar>(i, j);
src.at<uchar>(i, j) = saturate_cast<uchar>(gray * alpha + gamma);
}
}
}
imshow("src", src);
}
alpha改变对比度(倍乘),gamma改变亮度(相加)
tips:
alpha和gamma定义为浮点型,使用saturate_cast()裁剪值
7.绘制图形和文字
Mat src;
src = imread("D:\\opencv_test_img\\bea2.jpg", 1);
Point p1 = Point(src.cols / 2, src.rows / 2);
Point p2 = Point(src.cols/4,src.rows/8);
Rect r = Rect(100, 100, 200, 200);
Scalar color = Scalar(0, 255, 255);
//线条
line(src,p1,p2,color,1,LINE_AA); //LINE_AA非锯齿形
//矩形
rectangle(src,r,color);
//圆形
circle(src,p1,100,color);
//椭圆
ellipse(src,RotatedRect(p1,Size(src.cols / 4, src.rows / 8),90),color,1,8);
//文字
putText(src,"hello beauty",p1,CV_FONT_BLACK,5,color,5);
8.模糊
//均值滤波&高斯滤波
void blur()
{
Mat src ,dst;
src = imread("D:\\opencv_test_img\\bea2.jpg", 1);
if (src.empty())
{
cout << "can not read img" << endl;
return;
}
blur(src,dst,Size(5,5),Point(-1,-1));
imshow("blur",dst);
GaussianBlur(src,dst,Size(5,5),5,5);
imshow("GaussianBlur",dst);
waitKey(0);
}
//双边滤波&中值滤波
void blur2()
{
Mat src, dst;
src = imread("D:\\opencv_test_img\\bea2.jpg", 1);
if (src.empty())
{
cout << "can not read img" << endl;
return;
}
medianBlur(src,dst,3);
imshow("medianblur", dst);
bilateralFilter(src,dst,15,100,3); //(输入,输出,半径,允许的像素差值,半径非0时自动确定)
imshow("bilateralFilter", dst);
Mat kernel = (Mat_<int>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
filter2D(dst,dst,-1,kernel,Point(-1,-1));
imshow("shaper", dst);
waitKey(0);
}
双边滤波可以在模糊的时候保留图形边缘信息,双边+锐化可以实现磨皮美白的效果
9.膨胀和腐蚀
int size_e_d = 3;
void CallBack_Demo(int,void*);
void main()
{
namedWindow("window",WINDOW_AUTOSIZE);
createTrackbar("element size","window",&size_e_d,21, CallBack_Demo); //21是max_size
CallBack_Demo(0,0);
waitKey(0);
}
void CallBack_Demo(int,void*)
{
Mat src, dst;
src = imread("D:\\opencv_test_img\\bea2.jpg", 1);
if (src.empty())
{
cout << "can not read img" << endl;
return;
}
int s = size_e_d*2+1;
Mat s_element = getStructuringElement(MORPH_ELLIPSE,Size(s,s),Point(-1,-1)); //MORPH_X
//dilate(src,dst, s_element,Point(-1,-1));
erode(src, dst, s_element, Point(-1, -1));
imshow("window",dst);
}
10.形态学操作
形态学操作主要针对二值图像进行处理:
开操作:先腐蚀后膨胀,可以去掉背景中小对象
闭操作:先膨胀后腐蚀,填充小洞
形态学梯度:膨胀减去腐蚀,可以突出边缘信息
顶帽:原图-开操作,留下比较亮的区域
黑帽:闭操作-原图,突出填补的小洞部分
//基本操作
void mor_EX()
{
Mat src, dst;
src = imread("D:\\opencv_test_img\\bea2.jpg", 1);
if (src.empty())
{
cout << "can not read img" << endl;
return;
}
Mat kernel = getStructuringElement(MORPH_ELLIPSE,Size(9,9),Point(-1,-1));
morphologyEx(src,dst,CV_MOP_BLACKHAT,kernel);
namedWindow("mor_EX",WINDOW_AUTOSIZE);
imshow("mor_EX",dst);
namedWindow("ori", WINDOW_AUTOSIZE);
imshow("ori", src);
waitKey(0);
}
11.提取水平、垂直线
提取步骤:
adaptiveThreshold的计算单位是像素的邻域块,邻域块取多大,由blocksize作决定。
OpenCV自适应阈值化函数adaptiveThreshold详解,并附实例源码!
void abs_l()
{
Mat src, gray_img,bit_img,h_ele,v_ele,dst,s1;
src = imread("D:\\opencv_test_img\\ab_hv.jpg", 1);
if (src.empty())
{
cout << "can not read img" << endl;
return;
}
//先转换为灰度再转换为二值
cvtColor(src,gray_img,CV_BGR2GRAY);
adaptiveThreshold(~gray_img,bit_img,255,ADAPTIVE_THRESH_MEAN_C,THRESH_BINARY,15,-2); //得到背景为黑色
//定义提取元素
v_ele = getStructuringElement(MORPH_RECT,Size(1,src.cols/16),Point(-1,-1)); //生成列值为1
h_ele = getStructuringElement(MORPH_RECT, Size(src.rows/16,1), Point(-1, -1)); //生成行值为1
//提取操作
//morphologyEx(~bit_img,dst,CV_MOP_CLOSE,h_ele);
//morphologyEx(bit_img,dst,CV_MOP_OPEN,h_ele);
erode(bit_img,s1,h_ele,Point(-1,-1));
dilate(s1, dst, h_ele, Point(-1, -1));
//开操作先腐蚀再膨胀,bit_img背景为黑色
//闭操作先膨胀再腐蚀,~bit_img背景为白色
//如果背景是黑色,应该先腐蚀再膨胀,如果是白色,先膨胀再腐蚀
//显示
imshow("bit_img", bit_img);
imshow("s1", s1);
imshow("dst",dst);
waitKey(0);
}
12.图像金字塔-上采样和下采样
高斯金字塔:
(1)进行高斯模糊
(2)删除偶数行和偶数列
每一层迭代进行。
高斯不同:对一张图像在不同参数的情况下,得到的高斯模糊结果相减,得到的图像。
高斯不同是图像的内在特征,在灰度图像增强和角点检测中经常使用。
void Gaussian_pyr()
{
Mat src, src_up,src_down;
src = imread("D:\\opencv_test_img\\bea2.jpg", 1);
if (src.empty())
{
cout << "can not read img" << endl;
return;
}
pyrUp(src,src_up,Size(src.cols*2,src.rows*2));
pyrDown(src, src_down, Size(src.cols / 2, src.rows / 2));
//高斯不同
Mat g1, g2, gray,sub;
cvtColor(src,gray,CV_BGR2GRAY);
GaussianBlur(gray,g1,Size(9,9),0,0);
GaussianBlur(g1, g2, Size(9, 9), 0, 0);
subtract(g1,g2,sub);
normalize(sub,sub,255,0,NORM_MINMAX);
//展示
imshow("up",src_up);
imshow("down",src_down);
imshow("sub",sub);
waitKey(0);
}
13.阈值操作
threshold只能处理灰度图像:(二值化、反二值化、截断、取0、反取0)
THRESH_BINARY:阈值二值化,大于阈值部分取设定的最大值,小于阈值取0
THRESH_BINARY_INV:阈值反二值化,大于取0,小于取设定值
THRESH_TRUNC:超过阈值则等于阈值,否则不变
THRESH_TOZERO:小于阈值则阈值取0
THRESH_TOZERO_INV:大于阈值取0
THRESH_OTSU、THRESH_TRIANGLE:帮助计算阈值,不需设定阈值参数
int max_size=255;
int type=0;
Mat th_src, th_dst,th_gray;
void threshold_demo(int, void*);
void main()
{
namedWindow("treshold", WINDOW_AUTOSIZE);
createTrackbar("threshold size", "treshold", &max_size, 255, threshold_demo);
createTrackbar("threshold type", "treshold", &type, 4, threshold_demo);
threshold_demo(0,0)
waitKey(0);
system("pause");
}
void threshold_demo(int, void*)
{
th_src = imread("D:\\opencv_test_img\\zf.jpg", 1);
if (th_src.empty())
{
cout << "can not read img" << endl;
return;
}
cvtColor(th_src, th_gray,CV_BGR2GRAY);
threshold(th_gray,th_dst,0, max_size, type);
imshow("treshold",th_dst);
}
14.卷积,三种提取边缘信息的算子
Robert算子:
Y方向[1,0,
0,-1]
X方向[0,1,
-1,0]
Sobel算子:
水平方向:[-1,0,1;
-2,0,2;
-1,0,1]
垂直方向:[-1,-2,-1;
0,0,0;
1,2,1]
拉普拉斯算子:[0,-1,0
-1,4,-1
0,-1,0]
void fil_blur()
{
Mat src = imread("D:\\opencv_test_img\\d.png", 1);
if (src.empty())
{
cout << "can not read img" << endl;
return;
}
Mat ro_dst, so_dst, lap_dst,dst;
Mat r_k, s_k, l_k,k;
r_k = (Mat_<int>(2, 2) << 1, 0, 0, -1);
s_k= (Mat_<int>(3, 3) << -1, -2, -1, 0,0,0,1,2,1);
l_k= (Mat_<int>(3, 3) << 0, -1, 0, -1,4,-1,0,-1,0);
//模糊动态展示
int k_size = 0;
int index=0;
namedWindow("src", WINDOW_AUTOSIZE);
while (true)
{
char c = waitKey(500);
if ((int)c == 27)
break;
k_size = (index % 5) * 2+1;
k = (Mat::ones(Size(k_size, k_size),CV_32F))/ (k_size* k_size);
filter2D(src,dst,-1,k,Point(-1,-1));
index++;
imshow("blur", dst);
}
//边缘提取
//filter2D(src,ro_dst,-1,r_k,Point(-1,-1));
//filter2D(src, so_dst, -1, s_k, Point(-1, -1));
//filter2D(src, lap_dst, -1, l_k, Point(-1, -1));
//
//namedWindow("blur_robet",WINDOW_AUTOSIZE);
//namedWindow("blur_sobel", WINDOW_AUTOSIZE);
//namedWindow("blur_lap", WINDOW_AUTOSIZE);
//namedWindow("src", WINDOW_AUTOSIZE);
//imshow("src", src);
//imshow("blur_robet", ro_dst);
//imshow("blur_sobel", so_dst);
//imshow("blur_lap", lap_dst);
waitKey(0);
}
15.边缘处理
边缘填充
void border_fi()
{
Mat src = imread("D:\\opencv_test_img\\d3.png", 1);
if (src.empty())
{
cout << "can not read img" << endl;
return;
}
Mat dst;
Scalar color;
int border_type = BORDER_DEFAULT;
int top = (0.05*src.rows);
int bottom = (0.05*src.rows);
int left = (0.05*src.cols);
int right = (0.05*src.cols);
RNG r = RNG(12345);
int c;
namedWindow("border_fill",WINDOW_AUTOSIZE);
while (true)
{
c = waitKey(500); //ESC退出
if (c == 27)
{
break;
}else if (c=='r') //按r
{
border_type = BORDER_REPLICATE; // 重复:对边界像素进行复制
}else if (c=='c')
{
border_type = BORDER_CONSTANT; //赋值常数,此时color起作用,和BORDER_ISOLATED效果一样
}else if (c == 'w')
{
border_type = BORDER_WRAP; //用对边、对角像素进行填充
}else if (c == 'f')
{
border_type = BORDER_REFLECT; //反射
}
else if (c == 'd')
{
border_type = BORDER_DEFAULT; //效果与反射类似
}
color = Scalar(r.uniform(0,255), r.uniform(0, 255), r.uniform(0, 255));
copyMakeBorder(src,dst,top,bottom,left,right,border_type,color);
imshow("border_fill",dst);
}
waitKey(0);
}
GaussianBlur高斯模糊中的边界参数
while (true)
{
c = waitKey(500);
if (c == 27)
{
break;
}
else if (c == 'r')
{
border_type = BORDER_REPLICATE; // 重复:对边界像素进行复制
}
else if (c == 'c')
{
border_type = BORDER_CONSTANT; //赋值常数,此时color起作用,和BORDER_ISOLATED效果一样
}
else if (c == 'w')
{
border_type = BORDER_WRAP; //用对边、对角像素进行填充
}
else if (c == 'f')
{
border_type = BORDER_REFLECT; //反射
}
else if (c == 'd')
{
border_type = BORDER_DEFAULT; //效果与反射类似
}
GaussianBlur(src,dst,Size(5,5),-1,-1, border_type);
imshow("border_fill", dst);
}
16.Sobel算子
Sobel计算图像灰度的近似梯度,结合了高斯平滑和微分求导,计算X方向和Y方向上的梯度图像,
水平方向:[-1,0,1;
-2,0,2;
-1,0,1]
垂直方向:[-1,-2,-1;
0,0,0;
1,2,1]
在方向上扩大差异,最终近似的图像梯度=|Gx|+|Gy|。
Sobel算子对噪声比较敏感,容易受到影响,使用高斯模糊降噪,精度对算法的影响很重要,输出精度一定要比输入精度大,最后再使用convertScaleAbs,将数据转换为8位。
python opencv 4.1.0 cv2.convertScaleAbs()函数
有两个sobel算子的函数Soble,Scharr
void f_sobel()
{
Mat src = imread("D:\\opencv_test_img\\d3.png", 1);
if (src.empty())
{
cout << "can not read img" << endl;
return;
}
Mat gray, xgrad, ygrad, xygrad;
GaussianBlur(src,gray,Size(3,3),-1);
cvtColor(gray,gray,CV_BGR2GRAY);
imshow("src",src);
imshow("gray",gray);
Sobel(gray, xgrad, CV_16S,1,0);
Sobel(gray, ygrad, CV_16S, 0, 1);
/*Scharr(gray, xgrad, CV_16S, 1, 0);
Scharr(gray, ygrad, CV_16S, 0, 1);*/
addWeighted(xgrad,0.5,ygrad,0.5,-1,xygrad); //近似梯度
convertScaleAbs(xygrad,xygrad); //转换为8位
convertScaleAbs(xgrad, xgrad);
convertScaleAbs(ygrad, ygrad);
imshow("xgrad",xgrad);
imshow("ygrad", ygrad);
imshow("xygrad", xygrad);
waitKey(0);
}
两个算子函数有区别,第一幅为Scharr,第二幅为Soble
17.拉普拉斯算子
计算二阶导数,获取图像边缘信息。
-高斯模糊去噪点
-转换为灰度图像
-拉普拉斯算子
-转换为8位图像
-获取更高边缘图像质量的其它操作(阈值操作等)
void lap_edg()
{
Mat src = imread("D:\\opencv_test_img\\d3.png", 1);
if (src.empty())
{
cout << "can not read img" << endl;
return;
}
Mat gray, lap;
namedWindow("src",WINDOW_AUTOSIZE);
namedWindow("gray",WINDOW_AUTOSIZE);
namedWindow("lap",WINDOW_AUTOSIZE);
GaussianBlur(src,gray,Size(3,3),-1,-1);
cvtColor(gray,gray,CV_BGR2GRAY);
Laplacian(gray,lap,CV_16U,3);
convertScaleAbs(lap,lap);
imshow("src",src);
imshow("gray",gray);
imshow("lap",lap);
imwrite("lap.jpg",lap);
waitKey(0);
}
结果:
18.边缘提取-canny
在sobel算子的前提下,主要有一步非最大信号抑制以及高低阈值输出二值图像,依然是对灰度图像进行处理,之后可以利用copyTo转换到三通道图像。
Mat c_src, c_gray;
int c_th, c_max = 255;
void f_canny(int,void*);
void main()
{
c_src = imread("D:\\opencv_test_img\\d3.png", 1);
if (c_src.empty())
{
cout << "can not read img" << endl;
return;
}
cvtColor(c_src,c_gray,CV_RGB2GRAY);
blur(c_gray, c_gray, Size(3, 3), Point(-1, -1),BORDER_DEFAULT);
namedWindow("canny", WINDOW_AUTOSIZE);
createTrackbar("canny threshold", "canny", &c_th, 255, f_canny);
f_canny(0,0);
system("pause");
}
void f_canny(int,void*)
{
Mat c_edge,dst;
Canny(c_gray,c_edge,c_th,c_th*2);
dst.create(c_src.size(),c_src.type());
c_src.copyTo(dst,c_edge);
imshow("canny", dst);
}
处理结果(最低阈值为34,最高为68时的结果):
19.霍夫变换-几何检测
用于几何检测,先将图像使用canny处理,再使用霍夫变换。直线的霍夫变换即将每一个像素点转换到极坐标空间(r,θ),n个像素点就有n条曲线(三角函数曲线),转换公式:
rθ=xicosθ+yisinθ
每个(r,θ)点有曲线经过就加一,最后最大的点,就是n条曲线相交次数最多的点,取这个点的r、θ值,将这两个值看作已知的参数,代回上面的方程就能得到直线方程。
最后调用直线检测霍夫变换API得到的是几组坐标点。
霍夫变换具有两个API:Houghlines以及HoughlinesP,前一个返回(r,θ),后一个返回直线的坐标点。
void hough_getl()
{
Mat src = imread("D:\\opencv_test_img\\d3.png", 1);
if (src.empty())
{
cout << "can not read img" << endl;
return;
}
Mat dst_c, dst_BGR;
Canny(src,dst_c,46,46*2);
cvtColor(dst_c,dst_BGR,CV_GRAY2BGR);
imshow("after canny",dst_BGR);
vector<Vec4f> f_p;
HoughLinesP(dst_c,f_p,1,CV_PI/180,10,0,0); //检测直线的像素间隔,若直线不连续可以调整最后一个参数
Scalar color = Scalar(200,0,0);
for (size_t i = 0; i < f_p.size(); i++)
{
Vec4f p = f_p[i];
line(dst_BGR,Point(p[0],p[1]),Point(p[2],p[3]),color,2,LINE_AA);
}
imshow("hough",dst_BGR);
waitKey(0);
}
霍夫空间圆检测,利用圆的方程组进行变换:
x=a+Rcosθ
y=b+Rsinθ
其中a、b表示圆形,R表示半径,在给定R时,变换θ,得到不同的a、b值,以此得到一条关于a、b的圆曲线,n个像素点有n条圆曲线,相交次数最多,即在霍夫空间中的高亮点,就是检测到的圆心。
在opencv中,霍夫变换是基于图像梯度实现的,先检测边缘,发现可能的圆心,再计算最佳半径大小。
void hough_getc()
{
Mat src = imread("D:\\opencv_test_img\\sz.jpg", 1);
if (src.empty())
{
cout << "can not read img" << endl;
return;
}
Mat blur_m, gray, dst_f;
vector<Vec3f> p_c;
//中值模糊
medianBlur(src,blur_m,3);
imshow("blur_m", blur_m);
cvtColor(blur_m,gray,CV_BGR2GRAY);
imshow("gray",gray);
霍夫检测
HoughCircles(gray,p_c,CV_HOUGH_GRADIENT,1,10,100,30,0,100);
CV_HOUGH_GRADIENT表示霍夫梯度法,累加器与原图具有相同分辨率
两个圆心之间最短距离为10,canny高阈值为100,圆心累加器阈值为30(超过则判定为圆心)
圆半径的最小值为0.最大值为100
for (size_t i=0;i<p_c.size();i++)
{
Vec3f p = p_c[i];
circle(gray,Point(p[0],p[1]),p[2],Scalar(0,0,255),2,LINE_AA);
circle(gray,Point(p[0],p[1]),1, Scalar(0, 0, 255), 2, LINE_AA);
}
imshow("hough",gray);
waitKey(0);
}
cv2.HoughCircles函数的参数
圆心累加器阈值过低会导致误判大大增加,过高会导致无法检测出该检测的圆。
20.像素重映射
操作步骤:创建映射表(Mat)、计算新的像素数位置(映射表里存放的是像素坐标),调用映射API。
Mat ys_src, ys_dst, x_map, y_map;
int index = 0;
void ys()
{
ys_src = imread("D:\\opencv_test_img\\d3.png", 1);
if (ys_src.empty())
{
cout << "can not read img" << endl;
return;
}
x_map.create(ys_src.size(),CV_32FC1);
y_map.create(ys_src.size(), CV_32FC1);
namedWindow("remap",WINDOW_AUTOSIZE);
while (true)
{
index = waitKey(500);
if (index == 27)
{
break;
}
index = index % 4;
update_map();
remap(ys_src, ys_dst,x_map,y_map,INTER_LINEAR,BORDER_CONSTANT,Scalar(0,0,255));
imshow("remap",ys_dst);
}
}
//获取新的坐标点,存放新图中,对应的旧图的像素位置
void update_map()
{
int c = ys_src.cols;
int r = ys_src.rows;
for (int col = 0; col < c; col++)
{
for (int row = 0; row < r; row++)
{
switch (index)
{
case 0: //缩效二分之一
if (col>c*0.25&&col<c*0.75&&row>r*0.25&&row<r*0.75)
{
x_map.at<float>(row, col) = 2* (col-c*0.25); //相当于插值的操作,取偶数行的像素
y_map.at<float>(row, col) = 2* (row - r * 0.25);
}
else
{
x_map.at<float>(row, col) = 0;
y_map.at<float>(row, col) = 0;
}
break;
case 1: //水平翻转
x_map.at<float>(row, col) = c - col-1;
y_map.at<float>(row, col) = row;
break;
case 2: //垂直翻转
x_map.at<float>(row, col) = col;
y_map.at<float>(row, col) = r - row - 1;
break;
case 3: //水平+垂直翻转
x_map.at<float>(row, col) = c - col - 1;
y_map.at<float>(row, col) = r - row - 1;
break;
}
}
}
}
21.直方图均衡化
杂
//获取运行时间
double t=getTickCount();
double time=(getTickCount()-t)/getTickFrequency();