opencv 入门学习笔记(C++)
4.1.2 Mat 结构的使用
关于Mat类,我们首先要知道的是:
(1)不必再手动为其开辟空间。
(2)不必再在不需要时立即将空间释放
总结:
- OpenCV 函数中输出图像的内存分配是自动完成的
- 使用opencv的c++结构时不需要考虑内存释放的问日
- 赋值运算符和拷贝构造函数
Mat B(A)
只复制信息头 - 使用函数
clone()
或者copyTo()
来复制一副图像的矩阵
4.1.3 像素值的存储方法
- RGB/RGBA
- YCrCb在JPEG中广泛使用
- HSV和HLS 把颜色分解成色调、饱和度和亮度。
4.1.4 显式创建Mat对象的七种方式
-
【方法一】使用Mat()构造函数
Mat M(2,2,CV_8UC3,Scalar(0,0,255)); //CV_[位数][带符号与否][类型前缀]C[通道数] //预先定义的通道数可以多达4个
-
【方法二】在C/C++中通过构造函数进行初始化
int sz[3]={2,2,2}; Mat L(3,sz,cv_8UC,Scalar::all(0)); //上面的例子演示了如何创建一个超过两维的矩阵:指定维数,然后传递给一个指向数组的指针,这个数组包含每个维度的尺寸;后续两个参数与方法一中的相同
-
【方法三】为已存在的IplImage指针创建信息头
IplImage* img = cvLoadImage("1.jpg",1); Mat mtx(img);//转换 IplImage*->Mat
-
【方法四】利用Create()函数
M.create(4,4,CV_8UC(2)); //需要注意的是,此方法不能为矩阵设初值,只是在该百年尺寸时重新为矩阵数据开辟内存而已。
-
【方法六】对小矩阵使用逗号分隔式初始化函数
Mat C = (Mat_<double>(3,3)<<0,-1,0,-1,5,-1,0,-1,0); cout<<"C = "<<endl<<" "<C<<endl<<endl;
-
【方法七】为已存在的对象创建新信息头
Mat RowClone = C.row(1).clone(); cout<<"RowClone = "<<endl<<" "<<RowClone<<endl<<endl; //方法七为使用成员函数clone()或者copyTo()为一个已存在的Mat对象创建一个新的信息头
cvtColor
- cvtColor的功能是把图像从一个色彩空间转换到另外一个色彩空间,有三个参数,第一个参数表示源图像,第二个参数表示色彩空间转换之后的图像,第三个参数表示源和目标色彩空间如:COLOR_BGR2HLS、COLOR_BGR2GRAY等
- cvtColor(image,gray_image,COLOR_BGR2GRAY);
imwrite
- 保存文件到指定的目录途径
- 只有8位,16位的PNG/JPG/Tiff文件格式而且是单通道或者三通道的BGR图像 才可以通过这种方式保存
- 保存PNG格式的时候可以保存透明通道的图片(RGBA)
- 可以指定压缩参数
矩阵的掩码操作
-
获取图像像素指针
-
掩码操作解释
-
代码演示
像素处理范围 saturate_cast<uchar>
这个函数的功能是确保RGB值得范围在0~255之间
掩码操作实现图像对比度的调整 I ( i , j ) = 5 ∗ I ( i , j ) − [ I ( i − 1 , j ) + I ( i + 1 , j ) + I ( i , j − 1 ) , I ( i , j + 1 ) ] I(i,j)=5*I(i,j)-[I(i-1,j)+I(i+1,j)+I(i,j-1),I(i,j+1)] I(i,j)=5∗I(i,j)−[I(i−1,j)+I(i+1,j)+I(i,j−1),I(i,j+1)]
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
int main(int argc,char** argv)
{
Mat src = imread("test.jpg");
Mat dst;
if (!src.data)//如果图像为空,返回 error
{
std::cout << "can't load this img" <<std::endl;
return -1;
}
namedWindow("input image", WINDOW_AUTOSIZE);
imshow("input image", src);
//RGB图像按列存储,注意存储顺序是BGR
int cols = (src.cols - 1) * src.channels();
//cols 应该用width更为妥当,-1是因为要空出一列像素以防无定义
int offsetx = src.channels();
//偏移量是通道数
int rows = src.rows;
//RGB按照列存储,与行数无关
dst = Mat::zeros(src.size(), src.type());
//重新创建一个mat矩阵用于存放输出的图像
//src.copyTo(dst);也可
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 = offsetx; col < cols; col++) {
output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offsetx] + current[col + offsetx] + previous[col] + next[col]));
}
}
namedWindow("constrast image", WINDOW_AUTOSIZE);
imshow("constrast image", dst);
waitKey(0);
return 0;
}
函数filter 2D实现掩码操作
可直接用opencv的API做掩码操作来提高对比度
filter2D(src,dst,src.depth(),kernel):其中src与dst是Mat类型变量,src.depth表示位图深度,有32、24、8等,直接写-1表示与输入图深度一致。(filter滤波器)
定义掩码: Mat lernel = (Mat_<char>(3,3)<<0,-1,0,-1,5,-1,0,-1,0);
#include <opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
#include<stdlib.h>
using namespace cv;
int main(int argc, char** argv) {
Mat src = imread("C:/Users/admin/Desktop/lenna.png");//读入图片
Mat dst;//提高对比度之后的图片矩阵
if (src.empty()){ // 特判
printf("cannot see\n");
return -1;
}
namedWindow("opencv setup1", CV_WINDOW_AUTOSIZE);
imshow("opencv setup1", src);
Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);//定义掩码规则
filter2D(src, dst, src.depth()/*-1*/, kernel);
//输出目标图像
namedWindow("opencv setup2", CV_WINDOW_AUTOSIZE);
imshow("opencv setup2", dst);
waitKey(0);
system("pause"); //以便在退出程序前调用系统的暂停命令暂停命令行
//爽得很!!
}
Mat
mat对象的使用:
- 部分复制:一般情况下只会复制mat对象的头和指针部分,不会复制数据部分
- 完全复制:使用如下两个API:
Mat F=A.clone();
//或者
Mat G;
A.copyTo(G);
函数用法:
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main(int argc,char** argv)
{
Mat src = imread("test.jpg");
if (!src.data)
{
std::cout << "can't load this img" <<std::endl;
return -1;
}
//namedWindow("input image", WINDOW_AUTOSIZE);
//imshow("input image", src);
/*Mat dst;
dst = Mat(src.size(), src.type());
dst = Scalar(255, 0, 255);
namedWindow("out put", WINDOW_AUTOSIZE);
imshow("out put", dst);*/
/*Mat dst = src.clone();
namedWindow("out put", WINDOW_AUTOSIZE);
imshow("out put", dst);*/
//Mat dst;
//cvtColor(src, dst, COLOR_BGR2GRAY);
//namedWindow("out put", WINDOW_AUTOSIZE);
//imshow("out put", dst);
//cout << src.channels() << endl;//通道数目
//cout << dst.channels() << endl;
//int cols = dst.cols;//全部的列
//int rows = dst.rows;//全部的行
//const uchar* firstRow = dst.ptr<uchar>(0);
//printf("%d", *firstRow);
Mat M(3, 3, CV_8UC3, Scalar(0, 0, 255));
Mat m1;
m1.create(src.size(),src.type());
m1=Scalar(0,0,255);
Mat m2 = Mat::zeros(src.size(),src.type());
Mat m2 = Mat::zeros(2,2,CV_8UC1);
Mat m2 = Mat::eye(2,2,CV_8UC1);
cout << M << endl;
waitKey(0);
return 0;
}
图片操作
读写图像
-
imread
可以指定加载为灰度图像或者RGB图像 -
imwrite
保存图像文件,类型由扩展名决定
读写像素(pixel)
- 读取一个GRAY像素点的像素值(CV_8UC1)
Scalar intensity = img.at<uchar>(y,x);
或者
Scalar intensity = img.at<uchar>(Point(y,x));
- 读一个RGB像素点的像素值
Vec3f intensity = img.at<Vec3f>(y,x);
float blue = intensity.val[0];
float green = intensity.val[1];
float red = intensity.val[2];
-
获取像素的练习
#include <opencv2/core/utils/logger.hpp> #include<opencv2/opencv.hpp> #include<iostream> using namespace cv; using namespace std; int main(int argc, char** argv) { cv::utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);//不再输出日志 Mat src = imread("test.jpg"); if (!src.data) { cout << "could not load this image..." << endl; } char output_origin[] = "origin demo"; char output_gray[] = "gray demo"; namedWindow(output_origin, WINDOW_AUTOSIZE); imshow(output_origin, src); Mat dst; cvtColor(src, dst, COLOR_BGR2GRAY); namedWindow(output_gray, WINDOW_AUTOSIZE); imshow(output_gray, dst); dst.create(src.size(), src.type()); for (int row = 0; row < dst.rows; row++) for (int col = 0; col <dst.cols; col++) { //int gray = dst.at<uchar>(row,col); //dst.at<uchar>(row,col) = 255 - gray; int b = src.at<Vec3b>(row, col)[0]; int g = src.at<Vec3b>(row, col)[1]; int r = src.at<Vec3b>(row, col)[2]; dst.at<Vec3b>(row, col)[0]=255-b; dst.at<Vec3b>(row, col)[1] = 255 -g; dst.at<Vec3b>(row, col)[2] = 255 - r; //另一种转换成灰度图像的方法: //dst.at<uchar>(row,col)=max(r,max(b,g)); //dst.at<uchar>(row,col)=min(r,min(b,g)); } namedWindow("anti color demo", WINDOW_AUTOSIZE); imshow("anti color demo", dst); waitKey(0); } //已经有封装好的API干嘛要练习这个 bitwise_not(src,dst);
Vec3b与Vec3f
-
Vec3b对应的三通道的顺序是blue、green、red的uchar类型的数据
-
Vec3f对应三通道的float类型数据
-
把CV_8UC1转换到CV32F1实现如下:
src.convertTo(dst,CV_32F);
图像混合
理论-线性混合操作
g ( x ) = ( 1 − α ) f 0 ( x ) + α f 1 ( x ) 其中 α 的取值范围为 0 到 1 之间 g(x)=(1-\alpha)f_0(x)+\alpha f_1(x)\\其中\alpha的取值范围为0到1之间 g(x)=(1−α)f0(x)+αf1(x)其中α的取值范围为0到1之间
相关的API(addWeighted):
addWeight(src1,alpha,src2,beta,gamma(校验值),dst);
d s t ( I ) = s a t u r a t e _ c a s t ( s r c 1 ( I ) ∗ a l p h a + s r c 2 ( I ) ∗ b e t a + g a m m a ) dst(I)=saturate\_cast(src1(I)*alpha+src2(I)*beta+gamma) dst(I)=saturate_cast(src1(I)∗alpha+src2(I)∗beta+gamma)
改变图像的亮度和对比度
理论:
-
图像变换可以看作如下:
- 像素变换——点操作
- 邻域操作——区域
调整图像亮度属于像素变换-点操作 g ( i , j ) = α f ( i , j ) + β g(i,j)=\alpha f(i,j)+\beta g(i,j)=αf(i,j)+β其中 α \alpha α>0, β \beta β是增益变量
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
Mat src = imread("test.jpg");
char input_win[] = "src image";
if (!src.data) {
cout << "could not load the image..." << endl;
}
namedWindow(input_win, WINDOW_AUTOSIZE);
imshow(input_win, src);
Mat dst;
int height = src.rows;
int width = src.cols;
dst = Mat::zeros(src.size(), src.type());
double alpha = 1.5;
double beta = 10;
src.convertTo(src, CV_32F);
for (int row = 0; row < height; row++)
for (int col = 0; col < width; col++) {
if (src.channels() 3) {
float b = src.at<Vec3f>(row, col)[0];
float g = src.at<Vec3f>(row, col)[1];
float r = src.at<Vec3f>(row, col)[2];
dst.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(b * alpha + beta);
dst.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(g * alpha + beta);
dst.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(r * alpha + beta);
}
else if (src.channels() 1) {
int v = src.at<uchar>(row, col);
dst.at<uchar>(row, col) = saturate_cast<uchar>(v * alpha + beta);
}
//else
}
char output_title[] = "contrast and brightness change demo";
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow(output_title, dst);
waitKey(0);
}
绘制图像和文字
-
使用Point与Scalar
-
Point表示2D平面上一个点(x,y)
Point p; p.x=10;p.y=8;//p=Point(10,8);
-
Scalar表示四个元素的向量
Scalar(a,b,c);//a=bule,b=green,c=red表示RGB三个通道
-
-
在图片中插入线、矩形、椭圆、圆、多边形。
#include<opencv2/opencv.hpp> #include<iostream> using namespace cv; using namespace std; Mat src; void lines(); void myRectangle(); void myEllipse(); void myCircle(); void myPolygon(); int main(int argc, char** argv) { src = imread("test.jpg"); if (!src.data) { cout << "could not liad this image.." << endl; return -1; } lines(); myRectangle(); myEllipse(); myCircle(); myPolygon(); namedWindow("line_demo", WINDOW_AUTOSIZE); imshow("line_demo", src); waitKey(0); } void lines() {//线 Point p1 = Point(20, 30); Point p2 = Point(200, 300); Scalar color =Scalar(0, 0, 255); line(src, p1, p2, color, 5, LINE_AA); } void myRectangle() {//矩形 Rect rect = Rect(200, 100, 300, 300); Scalar color = Scalar(0, 0, 255); rectangle(src, rect, color,1, LINE_8); } void myEllipse() {//椭圆 Scalar color = Scalar(175, 145, 76); ellipse(src, Point(src.cols/ 2, src.rows / 2), Size(src.rows / 4, src.cols / 8), 0, 0, 360, color, 1, LINE_AA); } void myCircle() {//圆 Scalar color = Scalar(175, 146, 76); circle(src, Point(src.cols / 2, src.rows / 2), src.cols / 4, color, 1, LINE_AA); } void myPolygon() {//多边形 Scalar color = Scalar(0, 0, 255); Point pts [1][6]; pts[0][0] = Point(100, 100); pts[0][1] = Point(100, 200); pts[0][2] = Point(200, 200); pts[0][3] = Point(200, 100); pts[0][4] = Point(150, 50); pts[0][5] = Point(100, 100); const Point* ppts[] = { pts[0] }; int npt[] = { 6 }; fillPoly(src, ppts, npt, 1, color, 8); }
-
在图像中插入文字。
putText();
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
Mat src;
int main(int argc, char** argv)
{
src = imread("test.jpg");
if (!src.data) {
cout << "could not liad this image.." << endl;
return -1;
}
putText(src, "hello opencv", Point(src.cols / 2-60, src.rows / 2), FONT_HERSHEY_COMPLEX, 0.8, Scalar(150, 12, 255), 1, 8);
namedWindow("line_demo", WINDOW_AUTOSIZE);
imshow("line_demo", src);
waitKey(0);
}
//随机生成颜色
-
随机生成线条
#include<opencv2/opencv.hpp> #include<iostream> using namespace cv; using namespace std; Mat src; void RandowLineDemo(); int main(int argc, char** argv) { src = imread("test.jpg"); if (!src.data) { cout << "could not liad this image.." << endl; return -1; } //putText(src, "hello opencv", Point(src.cols / 2-60, src.rows / 2), FONT_HERSHEY_COMPLEX, 0.8, Scalar(150, 12, 255), 1, 8); RandowLineDemo(); //namedWindow("line_demo", WINDOW_AUTOSIZE); //imshow("line_demo", src); waitKey(0); } void RandowLineDemo() { RNG rng(12346);//RNG 变量(种子数) Mat bg = Mat::zeros(src.size(), src.type()); for (int i = 0; i < 10000; i++) { line(bg, Point(rng.uniform(0, src.cols), rng.uniform(0, src.rows)), Point(rng.uniform(0, src.cols), rng.uniform(0, src.rows)), Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), 1, LINE_AA); //rng.uniform(范围); if (waitKey(50) > 0) { break; } imshow("RandowLineDemo", bg); } }
模糊图像
模糊原理
- Smooth/Blur是图像处理中最简单和常用的操作之一
- 使用该操作的原因之一就是为了给图像预处理的时候减低噪声
- 使用Smooth/Blur操作背后是数学的卷积计算 g ( i , j ) = ∑ k j f ( i + k , j + l ) h ( k , l ) g(i,j)=\sum\limits_{kj}f(i+k,j+l)h(k,l) g(i,j)=kj∑f(i+k,j+l)h(k,l)
- 通常这些卷积算子计算都是线性操作,所以又叫线性滤波
- 归一化盒子滤波(均值滤波)和高斯滤波(权重不一样,会保留一些原有像素特质)
- 相关API
- 均值模糊:
blur(Mat src,Mat dst,Size(xradius,yradius),Point(-1,-1));
- 高斯模糊:
GaussianBlur(Mat src,Mat dst,Size(11,11),sigmax,sigmay);
其中Size(x,y)必须是正数而且是奇数。
- 均值模糊:
均值模糊
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main() {
Mat src, dst;
src = imread("test.jpg");
if (src.empty()) {
cout << "could not load this image..." << endl;
return -1;
}
namedWindow("originalDemo", WINDOW_AUTOSIZE);
imshow("originalDemo", src);
blur(src, dst, Size(3, 3), Point(-1, -1));
namedWindow("blur3Demo", WINDOW_AUTOSIZE);
imshow("blur3Demo", dst);
blur(src, dst, Size(5, 5), Point(-1, -1));
namedWindow("blur5Demo", WINDOW_AUTOSIZE);
imshow("blur5Demo", dst);
waitKey(0);
}
高斯模糊
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main() {
Mat src, dst;
src = imread("test.jpg");
if (src.empty()) {
cout << "could not load this image..." << endl;
return -1;
}
namedWindow("originalDemo", WINDOW_AUTOSIZE);
imshow("originalDemo", src);
GaussianBlur(src, dst, Size(11, 11), 5, 5);
namedWindow("GaussianBlurDemo", WINDOW_AUTOSIZE);
imshow("GaussianBlurDemo", dst);
//blur(src, dst, Size(5, 5), Point(-1, -1));
//namedWindow("blur5Demo", WINDOW_AUTOSIZE);
//imshow("blur5Demo", dst);
waitKey(0);
}
- 中值滤波
- 统计排序滤波器
- 中值对椒盐噪声有很好的抑制作用
- 高斯双边滤波
- 均值模糊无法克服边缘像素信息丢失缺陷。原因是均值滤波是基于平均权重
- 高斯模糊部分克服了该缺陷,但是无法完全避免,因为没有考虑像素值的不同
- 高斯双边模糊-是边缘保留的滤波方法,避免看边缘信息丢失,保留了图像轮廓不变,所以双边模糊需要输入两个,一个空域kernel(空间),一个值域kernel(值)
- 相关的API
- 中值模糊
medianBlur(Mat src,Mat dest,ksize)
- 双边模糊
bilateralFilter(src,dest,d=15,150,3);
- 15-计算的半径,半径之内的像素都会被纳入计算,如果提供-1则会根据sigma space参数取值
- 150-sigma color 决定多少差值之内的像素会被计算
- 3-sigma space如果d的值大于0则声明无效,否则根据它来计算d值
- 中值模糊的ksize大小必须大于一而且必须是奇数
- 中值模糊
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main() {
Mat src, dst;
src = imread("cat1.jpg");
if (src.empty()) {
cout << "could not load this image..." << endl;
return -1;
}
namedWindow("originalDemo", WINDOW_AUTOSIZE);
imshow("originalDemo", src);
//GaussianBlur(src, dst, Size(11, 11), 5, 5);
//medianBlur(src, dst, 3);
bilateralFilter(src, dst, 15, 150, 3);
namedWindow("BilateralFilterDemo", WINDOW_AUTOSIZE);
imshow("BilateralFilterDemo", dst);
waitKey(0);
}
膨胀与腐蚀
形态学操作-膨胀
- 图像形态学操作-基于形状的一系列图像处理操作的合集,主要是基于集合论基础上的形态学数学
- 形态学有四个基本操作:膨胀、腐蚀、开、闭
- 跟卷积操作类似,假设有图像A和结构元素B,结构元素B在A上面移动,其中B定义其中心为锚点计算B覆盖下A的最大像素值(腐蚀是最小像素值)用来替换锚点的像素,其中B作为结构体可以是任意形状
相关API
-
getStructuringElement(int shape,Size ksize,Point anchor)
- 形状(MORPH_RECT/MORPH_CROSS/MORPH_ELLIPSE)
- 大小(奇数)
- 锚点 默认是Point(-1,-1)意思就是中心像素
dilate(src,dst,kernel)
erode(src,dst,kernel)
动态调整结构元素大小
-
createTrackbar(constString&trackbarname,winname,int*value,int count,Trackbarcallback func,void* userdata=0)
- 其中最主要的是callback函数功能。如果设置为NULL就是说只有值update,但是不会调用callback的函数
#include<opencv2/opencv.hpp>
#include<iostream>
#include<opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
void CallBack_Demo(int, void*);//回调函数
int element_size = 3;
int Max_size = 21;
Mat src, dst;
int main()
{
src = imread("cat1.jpg");
if (!src.data)
{
cout << "could not load this image.." << endl;
return -1;
}
namedWindow("input image", WINDOW_AUTOSIZE);
imshow("input image", src);
namedWindow("output image", WINDOW_AUTOSIZE);
createTrackbar("Element Size:", "output image", &element_size, Max_size, CallBack_Demo);
CallBack_Demo(0, 0);
waitKey(0);
return 0;
}
void CallBack_Demo(int, void*)
{
int s = element_size * 2 + 1;
Mat structureElement = getStructuringElement(MORPH_RECT, Size(s, s), Point(-1, -1));
//dilate(src, dst, structureElement, Point(-1, -1), 1);//膨胀
erode(src, dst, structureElement, Point(-1, -1), 1);//腐蚀
imshow("output image", dst);
return;
}
//深入理解createTrackBar函数
#include<opencv2/opencv.hpp>
#include<iostream>
#include<opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
//void CallBack_Demo(int, void*);
int element_size = 3;
int Max_size = 21;
Mat src, dst;
void text(int, void*) {
cout << element_size << endl;
}
int main()
{
src = imread("cat1.jpg");
if (!src.data)
{
cout << "could not load this image.." << endl;
return -1;
}
namedWindow("测试窗口", WINDOW_AUTOSIZE);
createTrackbar("数字", "测试窗口", &element_size, Max_size, text);
text(0, 0);
waitKey(0);
return 0;
}
形态学操作
- 相关API
morphologyEx(src,dest,CV_MOP_BLACKHAT,kernel);
- Mat src-输入图像
- Mat dest-输出结果
- int OPT-CV_MOP_OPEN/CV_MOP_CLOSE/CV_MOP_GRADIENT/CV_MOP_TOPHAT/CV_MOP_BLACKHAT形态学操作类型
- Mat kernel -结构元素
- int Iteration-迭代次数,默认是1
- 开操作-open
- 先腐蚀后膨胀
- 可以去掉较小的对象
- 闭操作-close
- 先膨胀后腐蚀
- 可以填充小的洞(fill hole)
- 形态学梯度-MOPGRADIENT
- 膨胀减去腐蚀
- 又称为基本梯度(其他还包括-内部梯度、方向梯度)
- 顶帽-TOPHAT
- 顶帽是原图像与开操作之间的差值图像
- 计算结果是把较小的噪声对象呈现出来
- 黑帽
- 黑帽是闭操作图像与原图像的差值图像
针对二值图像进行处理
形态学操作应用-提取水平与垂直线
膨胀:输出的像素值是结构元素覆盖下输入图像的最大像素值
腐蚀:输出的像素值是结构元素覆盖下输入图像的最小像素值
结构元素
- 上述膨胀与腐蚀过程可以使用任意的结构元素
- 常见的形状:直线、圆、磁盘形状等各种自定义形状。
提取步骤
- 输入图像imread
- 转换为灰度图像-cvtColor
- 转换为二值图像-adaptiveThreshold
- API说明-adaptiveThreshold
- Mat src-输入的图像
- Mat dest-二值图像
- double maxValue-二值图像最大值
- int adaptiveMethod-自适应方法,只能其中之一-ADAPTIVE_THRESH_MEAN_C,ADAPTIVE_THRESH_GAUSSIAN_C
- int thresholdType-阈值类型(binary)
- int blockSize-块大小(15)
- double C-常量C,可以是正数,0,负数(-2)
- 定义结构元素
- 开操作(腐蚀+膨胀)提取水平与垂直线在结构元素上做文章,把结构元素定义为水平线或者垂直线
bitwise_not(src,dst):dst=255-src;
代码样例
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
Mat src,temp, dst;
src = imread("0-1.jpg");
if (src.empty()) {
cout << "could not load this img..." << endl;
return -1;
}
imshow("src", src);
cvtColor(src, temp, COLOR_BGR2GRAY);
adaptiveThreshold(temp, temp, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
Mat x_kernel = getStructuringElement(MORPH_RECT, Size(src.cols / 16, 1), Point(-1, -1));
Mat y_kernel = getStructuringElement(MORPH_RECT, Size(1,src.rows/16), Point(-1, -1));
morphologyEx(temp, dst, MORPH_OPEN, x_kernel);
bitwise_not(dst, dst);
blur(dst,dst,Size(3,3),Point(-1,-1));
namedWindow("x_line", WINDOW_AUTOSIZE);
imshow("x_line", dst);
waitKey(0);
return 0;
}
- 去除干扰项 --转换为二值图像–定义核矩形–形态学操作–开
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
Mat src,temp, dst;
src = imread("ganrao.png");
if (src.empty()) {
cout << "could not load this img..." << endl;
return -1;
}
imshow("src", src);
cvtColor(src, temp, COLOR_BGR2GRAY);
adaptiveThreshold(temp, temp, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);//自适应阈值,可以转换为二值图像
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));//核,getstructingelement获取结构元素
morphologyEx(temp, dst, MORPH_OPEN, kernel);
//形态学操作--开--morph——open
bitwise_not(dst, dst);
imshow("干扰去除", dst);
waitKey(0);
return 0;
}
图像上采样与降采样
-
图像金字塔概念
- 采样越多,分辨率越高,金字塔变换特征是不会改变的
- 我们在图像畜栏里中常常会调整图像的大小,最常见的就是放大(zoom in)和缩小(zoom out),尽管几何变换也可以实现图像放大和缩小,但是这里我们介绍图像金字塔
- 一个图像金字塔是一系列图像组成,最底下一张是图像尺寸最大,最上方的图像尺寸最小,从孔金赏从上向下看就像一个古代的金字塔。
- 高斯金字塔-用来对图像进行降采样
- 从底向上,逐层降采样得到
- 降采样之后图像的大小是M/2*N/2,即得到降采样之后上一层的图片
- 高斯金字塔的生成过程分为两步:
- 对当前层进行高斯模糊
- 删除当前层的偶数行与列
- 拉普拉斯金字塔-用来重建一张图片根据它的上层降采样图片
- 高斯不同(Difference of Gaussian-DOG)
- 定义:就是把一张图像在不同的参属下做高斯模糊之后的结果相减,得到的输出图像。称为高斯不同
- 高斯不同是图像的内在特征,在灰度图像增强、角点检测中经常用到
-
采样API
- 上采样(pyrUp)-zoom in 放大
- 降采样(pyrDown)-zoom out 缩小
如何进行上采样和降采样从而得到高斯金字塔的图像,在一个是如何对每一层进行处理得到它的DOG,归一化,彩色图像通道也可做DOG
#include<iostream> #include<opencv2/opencv.hpp> using namespace cv; using namespace std; int main(int argc, char** argv) { Mat src,temp, g1,g2,dogImg; src = imread("test.jpg"); if (src.empty()) { cout << "could not load this img..." << endl; return -1; } imshow("src", src); //DOG cvtColor(src, temp, COLOR_BGR2GRAY);//转换为灰度图像 GaussianBlur(temp, g1, Size(3, 3), 0, 0);//两次高斯模糊 GaussianBlur(g1, g2, Size(3, 3), 0,0); subtract(g1, g2, dogImg, Mat());//减去 //归一化显示 normalize(dogImg, dogImg, 255, 0, NORM_MINMAX);//用此函数也可化为0-1图像 imshow("DOG Image", dogImg); waitKey(0); return 0; }
基本阈值操作(threshold)
- 阈值是什么,简单来说是把图像分割的标尺
阈值类型
-
阈值二值化(threshold binary)
-
阈值反二值化(threshold binary Inverted)
-
阈值截断(threshold trunc)
-
阈值取零(threshold to zero)
-
阈值反取零(threshold to zero Inverted)
- OTSU与TRANGLE
首先转化为灰度图像
阈值操作
threshold(src,dst,threshold_value,threshold_max,THRESH_TYPE);
#include<iostream>
#include<opencv2/opencv.hpp>
#include<math.h>
using namespace cv;
using namespace std;
int threshold_value = 127;//阈值
int threshold_max = 255;//最大阈值
Mat src, dst;
void threshold_Demo(int, void*);//声明回调函数
int main(int argc, char** argv) {
src = imread("test.jpg");
if (src.empty()) {
cout << "could not load this img..." << endl;
return -1;
}
namedWindow("input image", WINDOW_AUTOSIZE);
namedWindow("output image", WINDOW_AUTOSIZE);
imshow("input image", src);
createTrackbar("threshold", "output image", &threshold_value, threshold_max, threshold_Demo);//创建滑动条
threshold_Demo(0, 0);
waitKey(0);
return 0;
}
void threshold_Demo(int, void*) {
cvtColor(src, dst, COLOR_BGR2GRAY);//转换为灰度图
threshold(dst, dst, threshold_value, threshold_max, THRESH_BINARY);//转换为二值图像
imshow("output image", dst);
}
输入输出XML和YAML文件
- XML,是“可扩展表示为语言”,任何满足XML命名规则的名称都可以标记,这就像不同的应用程序打开了大门
- YAML是以数据为中心,而不是以置标语言为重点。YAML是一个可读性高,用来表达资料序列的格式。哦内阁制,YAML试图用一种比XML更敏捷的方式,来完成XML所完成的任务
FileStorage类操作文件的使用引导
XML是使用非常广泛的文件格式,可以利用XML或者YAML格式的文件存储和还原各式各样的数据结构。当然,他们还可以存储和载入任意复杂的数据结构,其中就包括了OpenCV相关周边的数据结构,以及各种原始数据类型,如整数和浮点数字和文本字符串。
过程:
- 实例化一个
FileStorage
类的对象,用默认带参数的构造函数完成初始化,或者用FileStorage::open()
成员函数辅助初始化。 - 使用流操作符<<进行文件写入操作,或者>>进行文件读取操作,类似C++中的文件输入输出流。
- 使用
FileStorage::release()
函数析构掉FileStorage
类对象,同时关闭文件。
示例程序:XML和YAML文件的写入
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<opencv2/opencv.hpp>
#include<math.h>
#include<time.h>
using namespace cv;
using namespace std;
int main() {
//初始化
FileStorage fs("test.yaml", FileStorage::WRITE);
fs << "frameCount" << 5;
time_t rawtime;
time(&rawtime);
fs << "calibrationDate" << asctime(localtime(&rawtime));
Mat cameraMatrix = (Mat_<double>(3, 3) << 1000, 0, 320, 0, 1000, 240, 0, 0, 1);
Mat distCoeffs = (Mat_<double>(5, 1) << 0.1, 0.01, -0.001, 0, 0);
fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;
fs << "features" << "[";
for (int i = 0; i < 3; i++)
{
int x = rand() % 640;
int y = rand() % 480;
uchar lbp = rand() % 256;
fs << "{:" << "x" << x << "y" << y << "lbp" << "[:";
for (int j = 0; j < 8; j++)
fs << ((lbp >> j) & 1);
fs << "]" << "}";
}
fs << "]";
fs.release();
return 0;
}
模板匹配(template match)
-
模板也是一个小的图像,用小图像匹配的过程叫做模板匹配
-
从左到右,从上到下计算匹配度
-
计算归一化平方不同TM_SQDIFF_NORMED -1
- R ( x , y ) = ∑ x ′ y ′ ( T ( x ′ , y ′ ) − I ( x + x ′ , y + y ′ ) ) 2 R(x,y)=\sum\limits_{x'y'}(T(x',y')-I(x+x',y+y'))^2 R(x,y)=x′y′∑(T(x′,y′)−I(x+x′,y+y′))2
-
计算归一化相关性TM_CCORR_NORMED ----3
-
计算归一化相关系数TM_CCOEFF_NORMED -----5
-
相关API介绍
- matchTemplate(src,templ,result,method)
- src-源图像,必须是8-bit或者32-bit浮点数图像
- templ-模板图像,类型与输入图像一致
- result-输出结果,必须是单通道32位浮点数,假设源图像W*H,模板图像w*h,则结果必须为W-w+1,H-h+1的大小
- int method 使用的匹配方法
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
using namespace cv;
using namespace std;
int trackbar_value = TM_CCOEFF_NORMED;//阈值,
int max_track = 5;//最大阈值/类型数目
Mat src, temp, dst;
void templateMatch_demo(int, void*);
int main(int argc, char** argv) {
src = imread("src.jpg");
temp = imread("template.jpg");
if (!src.data || !temp.data) {
cout << "could not load this image..." << endl;
return -1;
}
//namedWindow("input img", WINDOW_AUTOSIZE);
namedWindow("output img", WINDOW_AUTOSIZE);
createTrackbar("templatemacth", "output img", &trackbar_value, max_track, templateMatch_demo);//创建滑动条
templateMatch_demo(0, 0);
waitKey(0);
}
void templateMatch_demo(int ,void*) {
int height = src.rows - temp.rows + 1;//格式要求:大-小+1
int width = src.cols - temp.cols + 1;
Mat result(width, height, CV_32FC1);//要求输出32位单通道用于模板匹配
matchTemplate(src, temp, result, trackbar_value,Mat());//模板匹配
normalize(result, result, 0, 1, NORM_MINMAX,-1,Mat());//归一化0-1图像
Point minLoc, maxLoc,temLoc;//定位
src.copyTo(dst);//把源图像给dst
double min, max;
minMaxLoc(result, &min, &max, &minLoc, &maxLoc,Mat());
if (trackbar_value TM_SQDIFF || trackbar_value TM_SQDIFF_NORMED) {
temLoc = minLoc;
}
else temLoc = maxLoc;
rectangle(dst, Rect(temLoc.x, temLoc.y, temp.cols, temp.rows), Scalar(0, 0, 255),2,8);//在dst上画出框
rectangle(result, Rect(temLoc.x, temLoc.y, temp.cols, temp.rows), Scalar(0, 0, 255),2,8);
imshow("output img", result);
imshow("match", dst);
}
轮廓发现(find contour in your image)
- 轮廓发现是基于图像边缘提取的基础寻找对象轮廓的方法。所以边缘提取的阈值选定会影响最终轮廓发现效果
-
API介绍
-
findContours发现轮廓
- img 输入图像,非0的像素被看成1,0的像素保持不变,8-bit
- contours 全部发现的轮廓对象
- hierachy 图像的拓扑结构,可选,该轮廓发现算法正是基于图像拓扑结构实现
- mode 轮廓返回的模式
- method 发现方法
- Point offset-Point() 轮廓像素的位移,默认(0,0)没有位移
-
drawContours绘制轮廓
- img 输出图像
- contours 全部发现的轮廓对象
- contourldx 轮廓索引号
- color 绘制时候颜色
- thickness 绘制线宽
- lineType 线的类型LINE_8
- hierarchy 拓扑结构图
- maxlevel最大层数,0表示只绘制当前的,1表示绘制当前及其内嵌的轮廓
-
findContours发现轮廓
- 过程
- 输入图像转为灰度图像cvtColor
- 使用Canny进行边缘提取,得到二值图像
- 使用findContours寻找轮廓
- 使用drawContours绘制轮廓
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int threshold_value = 100;
int threshold_max = 255;
Mat src, dst;
void Demo_Contours(int, void*);
RNG rng;
int main() {
src = imread("cat3.jpg");
if (src.empty()) {
cout << "could not load this image..." << endl;
return -1;
}
Demo_Contours(0, 0);//contours轮廓
waitKey(0);
return 0;
}
void Demo_Contours(int, void*) {
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
Canny(src, dst, threshold_value, threshold_value * 2, 3, false);
//Canny(输入,输出,低阈值,高阈值(低阈值的2·3倍),3,false);
findContours(dst, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));//寻找轮廓
Mat drawImg = Mat::zeros(dst.size(), CV_8UC3);//8bit3通道的彩色图像
for (size_t i = 0; i < contours.size(); i++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));//RNG rng生成一个随机数。
drawContours(drawImg, contours, i, color, 2, LINE_8, hierarchy, 0, Point(0, 0));//画出轮廓(颜色随机),
}
imshow("output", drawImg);
}
凸包-Convex Hull
- 在一个多边形边缘或者内部任意两个点的连线都包含在多边形边界或者内部—包含集合S中所有点的最小凸多边形称为凸包
Graham扫描算法
- 首先选择Y方向最低的点作为起始点P0
- 从P0开始极坐标扫描,依次添加p1…pn(排序顺序是根据极坐标的角度大小,逆时针方向)
- 对每个点pi来说,如果添加pi点到凸包中导致一个左转向(逆时针方向)则添加该点到凸包,反之则从凸包中删除该点。
API说明 convexHull
- convexHull(
- pointd,//输入候选点,来自findContours
- hull//凸包
- bool clockwise//顺时针方向
- returnPoints)//true表示返回点荷属,如果第二个参数是vector则自动忽略
凸包代码演示(凸包只是一个比较特殊的轮廓而已)
- 首先把图像从RGB转为灰度
- 然后再转为二值图像
- 再通过发现轮廓得到候选点
- 凸包API调用
- 绘制显示
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int threshold_value = 100;
int threshold_max = 255;
Mat src,src_gray;
void Thershold_Callback(int, void*);
RNG rng(12345);
const char* OUTPUT = "output";
const char* TRACKBAR = "trackbar";
int main() {
src = imread("cat1.jpg");
if (src.empty()) {
cout << "could not load this image..." << endl;
return -1;
}
cvtColor(src, src_gray, COLOR_BGR2GRAY);//先转化为灰度图像
blur(src_gray, src_gray, Size(3, 3), Point(-1, -1));//然后进行模糊,降低噪声,以用来更好的二值化
namedWindow(OUTPUT, WINDOW_AUTOSIZE);
createTrackbar(TRACKBAR, OUTPUT, &threshold_value, threshold_max, Thershold_Callback);
Thershold_Callback(0, 0);
waitKey(0);
return 0;
}
void Thershold_Callback(int, void*) {
Mat threshold_output;
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
threshold(src_gray, threshold_output, threshold_value, threshold_max, THRESH_BINARY);//转化为二值图像
findContours(threshold_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));//通过发现轮廓的到候选点
vector<vector<Point>> hull(contours.size());
for (size_t i = 0; i < contours.size(); i++) {
convexHull(Mat(contours[i]), hull[i], false);
}
Mat dst = Mat::zeros(threshold_output.size(), CV_8UC3);
for (size_t i = 0; i < contours.size(); i++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(dst, hull, i, color, 1, LINE_8, hierarchy, 0, Point(0, 0));
drawContours(dst, contours, i, color, 1, LINE_8, hierarchy, 0, Point(0, 0));
}
imshow(OUTPUT, dst);
}
轮廓周围绘制矩形框和圆形框
从彩色图像转化为灰度图像,然后进行模糊,然后进行二值化处理,或者使用Canny边缘检测
- API介绍
-
approxPolyDP(InputArray curve,OutputArray approxCurve,double epsilon,bool closed)
基于RDP算法实现,目的是减少多边形轮廓点数 - 轮廓周围矩形绘制-API
-
boundingRect(InputArray points)
得到轮廓周围最小矩形左上角点坐标和右下角点坐标,绘制一个矩形 -
minAreaRect(InputArray points)
得到一个旋转的矩形,返回旋转矩形
-
- 轮廓周围绘制圆和椭圆-API
-
minEnclosingCircle(InputArray points)
//得到最小区域圆形- Point2f& center //圆心位置
- float& radius//圆的半径
-
fitEllipse(InputArray points)
得到最小椭圆
-
-
- 步骤
- 首先将图像变为二值图像(先变为灰度图像,然后进行模糊,进而用阈值函数变为二值图像,或者用canny边缘检测变为二值图像)
- 发现轮廓,找到图像轮廓(
findContours
) - 通过相关API再轮廓点上找到最小包含矩形和圆,旋转矩形与椭圆。
- 绘制他们
//坏代码
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int threshold_value = 170;
int threshold_max = 255;
Mat src, src_gray, dst;
void Contours_Callback(int, void*);
RNG rng(12345);
const char* OUTPUT = "rectangle-Demo";
const char* TRACKBAR = "trackbar";
int main() {
src = imread("cat1.jpg");
if (src.empty()) {
cout << "could not load this image..." << endl;
return -1;
}
cvtColor(src, src_gray, COLOR_BGR2GRAY);//先转化为灰度图像
blur(src_gray, src_gray, Size(3, 3), Point(-1, -1));//然后进行模糊,降低噪声,以用来更好的二值化
namedWindow(OUTPUT, WINDOW_AUTOSIZE);
createTrackbar(TRACKBAR, OUTPUT, &threshold_value, threshold_max, Contours_Callback);
Contours_Callback(0, 0);
waitKey(0);
return 0;
}
void Contours_Callback(int, void*) {
Mat binary_output;//把灰度图像变为二值图像
vector<vector<Point>>contours;
vector<Vec4i> hierachy;
threshold(src_gray, binary_output, threshold_value, threshold_max, THRESH_BINARY);
findContours(binary_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(-1, -1));
vector<vector<Point>>contours_ploy(contours.size());
vector<Rect>ploy_rects(contours.size());
vector<Point2f> ccs(contours.size());
vector<float> radius(contours.size());
vector<RotatedRect> minRect(contours.size());
vector<RotatedRect> myellipse(contours.size());
for (size_t i = 0; i < contours.size(); i++) {
approxPolyDP(Mat(contours[i]), contours_ploy[i], 3, true);//初始化
/*ploy_rects[i] = boundingRect(contours_ploy[i]);
minEnclosingCircle(contours_ploy[i], ccs[i], radius[i]);*/
if (contours_ploy.size() > 5) {
myellipse[i] = fitEllipse(contours_ploy[i]);
minRect[i] = minAreaRect(contours_ploy[i]);
}
}
src.copyTo(dst);
Point2f pts[4];
for (size_t i = 0; i < contours.size(); i++) {
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
if (contours_ploy.size() > 5) {
//rectangle(dst, ploy_rects[i], color, 2, LINE_8);
//circle(dst, ccs[i], radius[i], color, 2, LINE_8);
ellipse(dst, myellipse[i], color, 1, LINE_8);
minRect[i].points(pts);
for (int r = 0; r < 4; r++) {
line(dst, pts[r], pts[(r + 1) % 4], color, 1, LINE_8);
}
}
}
imshow(OUTPUT, dst);
}
图像矩(Image Moments)
持续更新…文章来源:https://www.toymoban.com/news/detail-774560.html
自用整理,仅为了记录,如需转载请标明出处。文章来源地址https://www.toymoban.com/news/detail-774560.html
到了这里,关于opencv 入门学习笔记(C++)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!