OpenCV3学习笔记

OpenCV3学习笔记

  • 一、OpenCV数据类型
    • 1. 大型数组类型
      • 1.1 Mat类
      • 1.2 SparseMat类
  • 二、Mat类基础操作
    • 1. 图像的读写
      • 1.1 读取
      • 1.2 写入
    • 2. 创建一个Mat类
    • 3. 色彩空间转换
    • 4. 图像像素指针&掩膜操作(Mask)的实现
    • 5. 像素数据的读写&修改
    • 6. 图像的线性混合
    • 7. 对比度&亮度增强——点操作
    • 8. 绘制基础图型
      • 8.1 图形&文字绘制
      • 8.2 随机线生成
      • 8.3 椒盐噪声
    • 9. 图像模糊
      • 9.1 均值模糊&高斯模糊:
      • 9.2 中值滤波&双边滤波
    • 10. 形态学操作
      • 10.1 膨胀与腐蚀
      • 10.2 开、闭、形态学梯度、顶帽、黑帽:
      • 10.3 水平&竖直线条提取:
    • 11. 图像的升采样和降采样、高斯不同
    • 12. 阈值操作
    • 13. 自定义线性滤波
    • 14. 图像边缘提取
      • 14.1 Sobel算子 & Scharr算子
      • 14.2 Laplance算子
      • 14.3 Canny算子
    • 15.基础图形检测
      • 15.1 霍夫变换——直线检测
      • 15.2 霍夫变换——圆
    • 16. 像素重映射
    • 17.灰度直方图
      • 17.1 直方图均衡化
      • 17.2 直方图计算

一、OpenCV数据类型

  • 第一类是直接从C++中继承的基础数据类型(如int和float等)。这些类型包括简单的数组和矩阵,一些简单的几何概念,比如点、矩形、大小等;
  • 第二类是辅助对象。这同时也代表些对象代表更抽象的概念, 比如垃圾收集指针类、用于数据切片的范围对象 (range objects) 以及抽象的终止条件类等;
  • 第三类可以称为大型数组类型。这些对象原本目的是涵盖数组或一些其他的原语、 程序集或更常见的基础数据类型。这一类的典型代表是cv::Mat类,该类用来代表任意维度的包含任意基础元素的数组。存储图片对象是cv::Mat类的特殊用途。

为快速上手,先从第三类的Mat类开始。

1. 大型数组类型

比较具有代表性的是 cv::Mat和cv::SparseMat 类型。

  • cv::Mat针对的是密集连续性的存储,大多数的图像数据被存储为这种类,即使数据为空,预留的存储空间仍然存在;

  • cv::SparseMat针对的是稀疏的存储方式,只有数据不为0才保留空间,否则不会预留。显然cv::SparseMat存储更为节省空间,典型使用cv::SparseMat的例如直方图数据的存储。

1.1 Mat类

Mat 是一个类,由两个数据部分组成:

  • 矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)

  • 一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。

1.2 SparseMat类

二、Mat类基础操作

1. 图像的读写

1.1 读取

imread功能是加载图像文件成为一个Mat对象,其中:
第一个参数为图像文件的路径;
第二个参数,表示加载的图像是什么类型,支持常见的三个参数值:

  • IMREAD_UNCHANGED (<0) 表示加载原图,不做任何改变
  • IMREAD_GRAYSCALE (=0)表示把原图作为灰度图像加载进来
  • IMREAD_COLOR (>0) 表示把原图作为RGB图像加载进来

注意:OpenCV支持JPG、PNG、TIFF等常见格式图像文件加载。

同时用到的函数还有:

  • namedWindow:功能是创建一个OpenCV窗口,它是由OpenCV自动创建与释放,你无需取销毁它。常见用法是:
    namedWindow(“Window Title”, WINDOW_AUTOSIZE)。
    — WINDOW_AUTOSIZE,会自动根据图像大小,显示窗口大小,不能人为改变窗口大小;
    — WINDOW_NORMAL,跟QT集成的时候会使用,允许修改窗口大小。
  • destroyWindow:功能是销毁指定的窗口。
  • imshow:根据窗口名称显示图像到指定的窗口上去,第一个参数是窗口名称,第二参数是Mat对象
//加载图像
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
     
    Mat image = imread("C://1.jpg");			//加载图像
    if (image.empty())
    {
     
        return -1;
    }
    namedWindow("Example1", WINDOW_AUTOSIZE);	//创建窗口
    imshow("Example1", image);					//显示图像
    waitKey(0);
    destroyWindow("Example1");					//销毁窗口
    return 0;
}

//加载为灰度图
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
     
    Mat image = imread("C://1.jpg", 0);		//IMREAD_GRAYSCALE (=0)表示把原图作为灰度图像加载进来
    if (image.empty())
    {
     
        return -1;
    }
    namedWindow("Example1", WINDOW_AUTOSIZE);
    imshow("Example1", image);
    waitKey(0);
    destroyWindow("Example1");
    return 0;
}

1.2 写入

imwrite功能是保存图像文件到指定目录路径,有三个参数:

  • 第一个参数表示需要写入的路径&文件名,必须要加上后缀,比如“123.png”;

  • 第二个参数表示Mat类型的图像数据,是你要保存的对象。

  • 第三个参数表示为特定格式保存的参数编码,它有一个默认值std::vector< int >(),所以一般情况下不用写。

注意:只有8位、16位的PNG、JPG、Tiff文件格式而且是单通道或者三通道的BGR的图像才可以通过这种方式保存,保存PNG格式的时候可以保存透明通道的图片

#include 
#include 

using namespace std;
using namespace cv;

int main()
{
     
    Mat image = imread("C://1.jpg");
    if (image.empty())
    {
     
        return -1;
    }

    Mat image_out;
    cvtColor(image, image_out, COLOR_BGR2HLS);     //转换为灰度图

    imwrite("D://1_out.png", image_out);	//将转换后image_out的写入到"D://1_out.png"

    namedWindow("Output Window", WINDOW_AUTOSIZE);
    imshow("Output Window", image_out);
    waitKey(0);
    destroyWindow("Output Window");
    return 0;
}

2. 创建一个Mat类

  • Mat对象构造函数:Mat()
  • Mat对象构造方法:A.create()

赋值一个全为0的Mat:img = Scalar(0)。

//三种建立Mat类的方式
#include 
#include 

using namespace cv;
using namespace std;

int main(int argc, char** args) {
     

	Mat M;
	M.create(4, 3, CV_32FC3);
	M = Scalar((126.3928), (12.3334), (246.12));
	cout << "M = " << endl << M << endl << endl;

	Mat A = Mat(3, 2, CV_16SC3);
	A = Scalar(1, 2, 3);
	cout << endl << "A = " << endl << A << endl << endl;

	Mat D = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
	cout << "D = " << endl << D << endl << endl;

	waitKey(0);
	return 0;
	}
/*
M =
[126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12;
 126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12;
 126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12;
 126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12, 126.3928, 12.3334, 246.12]


A =
[1, 2, 3, 1, 2, 3;
 1, 2, 3, 1, 2, 3;
 1, 2, 3, 1, 2, 3]

D =
[0, -1, 0;
 -1, 5, -1;
 0, -1, 0]


C:\Users\CK Yang\source\repos\LXCV01\x64\Debug\LXCV01.exe (进程 18808)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .
*/
}

创建Mat类型的函数十分灵活:

#include 
#include 

using namespace cv;
using namespace std;

int main(int argc, char** args) {
     

	Mat M;
	M.create(4, 3, CV_32FC3);
	M = Scalar((126.3928), (12.3334), (246.12));
	cout << "M = " << endl << M << endl << endl;

	Mat A = Mat(3, 2, CV_16SC3);
	A = Scalar(1, 2, 3);
	cout << endl << "A = " << endl << A << endl << endl;

	Mat B = Mat(Size(3, 4), CV_8SC3);
	B = Scalar(2, 3, 4);
	cout << "B = " << endl << B << endl << endl;

	Mat C = Mat(Size(2, 3), CV_16SC2, Scalar(2, 3, 4));
	cout << "C = " << endl << C << endl << endl;
	
	Mat D = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
	cout << "D = " << endl << D << endl << endl;

	
	waitKey(0);
	return 0;
	}
/*
A =
[1, 2, 3, 1, 2, 3;
 1, 2, 3, 1, 2, 3;
 1, 2, 3, 1, 2, 3]

B =
[  2,   3,   4,   2,   3,   4,   2,   3,   4;
   2,   3,   4,   2,   3,   4,   2,   3,   4;
   2,   3,   4,   2,   3,   4,   2,   3,   4;
   2,   3,   4,   2,   3,   4,   2,   3,   4]

C =
[2, 3, 2, 3;
 2, 3, 2, 3;
 2, 3, 2, 3]

D =
[0, -1, 0;
 -1, 5, -1;
 0, -1, 0]

*/

还可通过imread(“C:/2.jpg”);的方式建立图像的Mat

#include 
#include 

using namespace cv;
using namespace std;

int main(int argc, char** args)
	{
     
	Mat image = imread("C:/2.jpg");

	if (image.empty()) 
	{
     
		return -1;
	}

	namedWindow("My Image", WINDOW_AUTOSIZE);
	imshow("My Image", image);

	Mat M;
	M.create(4, 3, CV_32FC3);
	M = Scalar((126.3928), (12.3334), (246.12));
	cout << "M = " << endl << " " << M << endl << endl;
	uchar* firstRow = M.ptr<uchar>(0);
	printf("%d\n", *firstRow);

	Mat C = (Mat_<double>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
	cout << "C = " << endl << " " << C << endl << endl;

	waitKey(0);
	cvDestroyAllWindows();
	return 0;
}

Mat的type和depth:
:CV_32FC3——表示的是元素类型是一个32位的浮点数,通道为3。
OpenCV3学习笔记_第1张图片

  • 数据类型中:
    U(unsigned integer)表示的是无符号整数,
    S(signed integer)是有符号整数,
    F(float)是浮点数。
  • 通道数中:C1,C2,C3,C4则表示通道数是1,2,3,4。

type一般是在创建Mat对象时设定,如果要取得Mat的元素类型,则无需使用type,使用depth:
depth是矩阵中元素的一个通道的数据类型,这个值和type是相关的。
例如: type为 CV_16SC2,一个2通道的16位的有符号整数。那么,depth则是CV_16S。

  • depth:将type的值去掉通道信息就是depth值:CV_8U、CV_16S、CV_32F、CV_64F。
  • elemSize:矩阵一个元素占用的字节数,例如:type是CV_16SC3,那么elemSize = 3 * 16 / 8 = 6
    bytes
  • elemSize1:矩阵元素一个通道占用的字节数,例如:type是CV_16CS3,那么elemSize1 = 16 / 8 = 2
    bytes = elemSize / channels

常用方法:

  • void copyTo(Mat):完全复制;
  • Mat clone():完全复制;
  • void convertTo(Mat, int type):转换Mat为指定type;
  • int type():查看Mat的type;
  • size():查看Mat的size;
  • int channels():查看Mat的channels;
  • int depth():查看Mat的depth;
  • bool empty():查看Mat是否为empty;
  • uchar* ptr(i=0):像素指针。

注:

部分复制:一般情况下只会复制Mat对象的头和指针部分,不会复制数据部分

  • Mat A= imread(imgFilePath);
  • Mat B(A)

完全复制:如果想把Mat对象的头部和数据部分一起复制,可以通过如下两个API实现

  • Mat F = A.clone();
  • Mat G;
    A.copyTo(G);
#include 
#include 

using namespace cv;
using namespace std;

int main(int argc, char** args) {
     

	Mat M;
	M.create(4, 3, CV_32FC3);
	M = Scalar((126.3928), (12.3334), (246.12));
	cout << "M = " << endl << M << endl << endl;

	Mat A = Mat(3, 2, CV_16SC3);
	A = Scalar(1, 2, 3);
	cout << endl << "A = " << endl << A << endl << endl;

	Mat B = Mat(Size(3, 4), CV_8SC3);
	B = Scalar(2, 3, 4);
	cout << "B = " << endl << B << endl << endl;

	Mat E;
	A.copyTo(E);
	cout << "E = " << endl << E << endl << endl;

	Mat F;
	A.convertTo(F, CV_32FC3);
	cout << "F = " << endl << F << endl << endl;

	Mat G = B.clone();
	cout << "G = " << endl << G << endl << endl;

3. 色彩空间转换

cvtColor( image, gray_image, COLOR_BGR2GRAY )
cvtColor功能是把图像从一个彩色空间转换到另外一个色彩空间,有三个参数:

  • 第一个参数表示源图像;

  • 第二参数表示色彩空间转换之后的图像;

  • 第三个参数表示源和目标色彩空间如:COLOR_BGR2HLS 、COLOR_BGR2GRAY 等。

#include 
#include 

using namespace std;
using namespace cv;

int main()
{
     
    Mat image = imread("C://1.jpg");
    if (image.empty())
    {
     
        return -1;
    }

    Mat image_out;
    cvtColor(image, image_out, COLOR_BGR2HLS);     //转换色彩空间为COLOR_BGR2HLS

    namedWindow("Output Window", WINDOW_AUTOSIZE);
    imshow("Output Window", image_out);
    waitKey(0);
    destroyWindow("Output Window");
    return 0;
}

4. 图像像素指针&掩膜操作(Mask)的实现

Mat.ptr(int i=0) 功能是获取像素矩阵的指针,索引 i 表示行数,从0开始计数。

  • 获得当前行指针const uchar* current= myImage.ptr< uchar>(row);
  • 获取当前像素点P(row, col)的像素值 p(row, col) = current[col]。

注意:掩膜Mask也被称之为Kernel。

实现一个下图的掩膜操作,功能是提高图像的对比度:
在这里插入图片描述
这能干嘛?我下载的某个PDF文档看着比较瞎眼,就可以用这个来让它看着没那么瞎眼。

saturate_cast< uchar>()是像素范围处理函数,功能是确保RGB值的范围在0~255之间。

  • saturate_cast(-100),返回 0;
  • saturate_cast(288),返回255;
  • saturate_cast(100),返回100。
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
     
    Mat image = imread("C://2.png");
    Mat image_output = Mat::zeros(image.size(), image.type());	//创立一个元素全为0的Mat类型作为输出图像,size和type与输入图像保持一致。

    if (image.empty())
    {
     
        return -1;
    }

    namedWindow("Input",WINDOW_AUTOSIZE);
    imshow("Input", image);
    
    int cols = image.cols * image.channels();
    int rows = image.rows;

    for (int row = 1; row < (rows - 1); row++)
    {
     
        const uchar* current = image.ptr<uchar>(row);		//获取当前行指针
        const uchar* next = image.ptr<uchar>(row + 1);		//获取下一行指针
        const uchar* previous = image.ptr<uchar>(row - 1);	//获取上一行指针

        uchar* output = image_output.ptr<uchar>(row);		//获取想要输出的图像的行指针

        for (int col = 1 * image.channels(); col < cols; col++)		//为什么要* image.channels()?思考下彩色图像显示的原理。
        {
     
            output[col] = saturate_cast<uchar> ( current[col] * 5 - (current[col] - image.channels()) - (current[col] + image.channels()) - next[col] - previous[col] );
            //掩膜操作
        }
    }

    namedWindow("Output", WINDOW_AUTOSIZE);
    imshow("Output", image_output);
    
    waitKey(0);
    destroyWindow("Output");
    destroyWindow("INput");

    return 0;
}


除了利用图像像素指针进行掩膜操作,还能用OpenCV的api完成同样的操作,操作步骤:

  1. 定义掩膜:Mat kernel = (Mat_(3,3) << 0, -1, 0, -1, 5, -1, 0, -1,
    0);
  2. 调用filter2D( image, image_output, src.depth(), kernel):
    其中image和 image_output是Mat类型变量;
    src.depth表示位图深度,有32、24、8等,根据图像属性变化;
    kernel是刚才定义的掩膜。
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
     
    Mat image = imread("C://2.png");
    Mat image_output = Mat::zeros(image.size(), image.type());

    if (image.empty())
    {
     
        return -1;
    }

    namedWindow("Input", WINDOW_AUTOSIZE);
    imshow("Input", image);

    Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);	//定义掩膜
    filter2D(image, image_output, image.depth(), kernel);	//调用filter2D

    namedWindow("Output", WINDOW_AUTOSIZE);
    imshow("Output", image_output);

    waitKey(0);
    destroyWindow("Output");
    destroyWindow("INput");

    return 0;
}

5. 像素数据的读写&修改

  • 灰度图像:只有1个通道,可直接赋值修改。
    img.at(y, x) = 128;
//获取灰度图像的像素值
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
     
	Mat image = imread("C://wn.jpg");
	Mat image_gray;

	if (image.empty())
	{
     
		return -1;
	}

	namedWindow("input", WINDOW_AUTOSIZE);
	imshow("input", image);

	cvtColor(image, image_gray, COLOR_BGR2GRAY);
	namedWindow("output_gray", WINDOW_AUTOSIZE);
	imshow("output_gray", image_gray);

	int height = image_gray.rows;		//获取image_gray的行数,作为图像的高度
	int width = image_gray.cols;		//获取image_gray的列数,作为图像的宽度

	for (int row = 0; row < height; row++) {
     
		for (int col = 0; col < width; col++) {
     
			int pixel_gray = image_gray.at<uchar>(row, col);	//获取每一个灰度像素点的值
			cout << "rows = " << row << ",cols = " << col << ",pixel_gray = " << pixel_gray << endl;
		}
	}


	waitKey(0);

	return 0;
}

OpenCV3学习笔记_第2张图片
修改像素值,并显示修改像素值后的灰度图片,以颜色反转为例:

//灰度图像颜色反转
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
     
	Mat image = imread("C://wn.jpg");
	Mat image_gray;

	if (image.empty())
	{
     
		return -1;
	}

	namedWindow("input", WINDOW_AUTOSIZE);
	imshow("input", image);

	cvtColor(image, image_gray, COLOR_BGR2GRAY);
	namedWindow("output_gray", WINDOW_AUTOSIZE);
	imshow("output_gray", image_gray);

	int height = image_gray.rows;		//获取image_gray的行数,作为图像的高度
	int width = image_gray.cols;		//获取image_gray的列数,作为图像的宽度

	for (int row = 0; row < height; row++) {
     
		for (int col = 0; col < width; col++) {
     
			int pixel_gray = image_gray.at<uchar>(row, col);	//获取每一个灰度像素点的值,赋值给pixel_gray
			cout << "rows = " << row << ",cols = " << col << ",pixel_gray = " << pixel_gray << endl;
			image_gray.at<uchar>(row, col) = 255 - pixel_gray;	//修改每一个灰度像素点的值,效果是颜色反转。
		}
	}

	namedWindow("output_gray_reverse", WINDOW_AUTOSIZE);
	imshow("output_gray_reverse", image_gray);		//显示修改像素值后的图片

	waitKey(0);

	return 0;
}

OpenCV3学习笔记_第3张图片

  • RGB三通道图像:分通道逐个修改。
    img.at(y,x)[0]=128; // blue
    img.at(y,x)[1]=128; // green
    img.at(y,x)[2]=128; // red

彩色像素值的修改,以颜色反转为例:

//rgb图像颜色反转
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
     
	Mat image = imread("C://wn.jpg");
	Mat image_gray;

	if (image.empty())
	{
     
		return -1;
	}

	namedWindow("input", WINDOW_AUTOSIZE);
	imshow("input", image);

	cvtColor(image, image_gray, COLOR_BGR2GRAY);
	namedWindow("output_gray", WINDOW_AUTOSIZE);
	imshow("output_gray", image_gray);

	int height = image.rows;
	int width = image.cols;

	for (int row = 0; row < height; row++) {
     
		for (int col = 0; col < width; col++) {
     
			int pixel_b = image.at<Vec3b>(row, col)[0];
			int pixel_g = image.at<Vec3b>(row, col)[1];
			int pixel_r = image.at<Vec3b>(row, col)[2];

			cout << "rows = " << row << ",cols = " << col << ",Blue = " << pixel_b << ",Green = " << pixel_g << ",Red = " << pixel_r << endl;

			image.at<Vec3b>(row, col)[0] = 255 - pixel_b;
			image.at<Vec3b>(row, col)[1] = 255 - pixel_g;
			image.at<Vec3b>(row, col)[2] = 255 - pixel_r;
		}
	}
	namedWindow("output_reverse", WINDOW_AUTOSIZE);
	imshow("output_reverse", image);
	
	waitKey(0);

	return 0;
}

OpenCV3学习笔记_第4张图片
注意:OpenCV中有api可以实现图片色彩反转:bitwise_not(image, image_out);

//利用bitwise_not(image, image_out);进行色彩反转
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
     
	Mat image = imread("C://wn.jpg");
	Mat image_gray;
	Mat image_out;

	if (image.empty())
	{
     
		return -1;
	}

	namedWindow("input", WINDOW_AUTOSIZE);
	imshow("input", image);

	cvtColor(image, image_gray, COLOR_BGR2GRAY);
	namedWindow("output_gray", WINDOW_AUTOSIZE);
	imshow("output_gray", image_gray);

	bitwise_not(image, image_out);
	
	namedWindow("output_reverse", WINDOW_AUTOSIZE);
	imshow("output_reverse", image_out);
	
	waitKey(0);

	return 0;
}

OpenCV3学习笔记_第5张图片
注意:数据类型有区别!

  • Vec3b对应三通道的顺序是blue、green、red的uchar类型数据;
  • Vec3f对应三通道的顺序是blue、green、red的float类型数据;
  • 把CV_8UC1转换到CV32F1实现如下:
    Mat.convertTo(dst, CV_32F)。

6. 图像的线性混合

在这里插入图片描述

f0——第一张图像;
f1——第二张图像;
α——混合参数。

相关api:addWeighted(),作用就是线性混合。

  • 参数1:输入图像Mat
  • 参数2:输入图像的alpha值
  • 参数3:输入图像Mat
  • 参数4:输入图像的alpha值
  • 参数5:gamma值
  • 参数6:输出图像Mat

注意:两张图像的大小和类型必须一致才可以进行操作。

#include 
#include 

using namespace std;
using namespace cv;

int main()
{
     
	Mat image0 = imread("C://eg//wn.jpg");	//输入图像0
	Mat image1 = imread("C://eg//m.jpg");	//输入图像1

	Mat image_out;

	float alpha = 0.1;	//混合参数α

	if (image0.empty() || image1.empty()) {
     
		cout << "images are not found " << endl;
		return -1;
	}
	

	namedWindow("input0", WINDOW_AUTOSIZE);
	imshow("input0", image0);
	namedWindow("input1", WINDOW_AUTOSIZE);
	imshow("input1", image1);
	
	if (image0.cols == image1.cols && image0.rows == image1.rows && image0.type() == image1.type()) {
     
		addWeighted(image0, alpha, image1, (1 - alpha), 0.0, image_out);
		namedWindow("output", WINDOW_AUTOSIZE);
		imshow("output", image_out);
	}
	else {
     
		cout << "2 images' size or type are different " << endl;
		return -1;
	}
	waitKey(0);
	cvDestroyAllWindows();

	return 0;
}
}

权重不同,混合程度也不同,结果如下:
alpha = 0.25
OpenCV3学习笔记_第6张图片
alpha = 0.75
OpenCV3学习笔记_第7张图片

7. 对比度&亮度增强——点操作

图像变换可以看作如下:

  • 像素变换 – 点操作;
  • 邻域操作 – 区域。

调整图像亮度和对比度属于像素变换-点操作。

在这里插入图片描述

  • alpha——对比度
  • beta——亮度

相关api:

  • Mat new_image = Mat::zeros( image.size(), image.type() );
    创建一张跟原图像大小和类型一致的空白图像、像素值初始化为0
  • saturate_cast(value)确保值大小范围为0~255之间
  • Mat.at(y,x)[index]=value 给每个像素点每个通道赋值
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
     
	Mat image = imread("C://eg//jr.jpg");	//输入图像
	Mat image_gray;
	cvtColor(image, image_gray, COLOR_BGR2GRAY);

	Mat image_out = Mat::zeros(image.size(), image.type());	//创立和image同规格的图像一张,装满0

	float alpha = 1.3;	//参数
	float beta = 30;

	if (image.empty()) {
     
		return -1;
	}
	
	namedWindow("input", WINDOW_AUTOSIZE);
	imshow("input", image);
	
	int height = image.rows;
	int width = image.cols;

	Mat image_cvt2F;
	image.convertTo(image_cvt2F, CV_32FC3);		//image.at(row, col)[0];会报错,需要先convertTo CV_32FC3类型再image_cvt2F.at(row, col)[0];即可

	for (int row = 0; row < height; row++) {
     
		for (int col = 0; col < width; col++) {
     
			if (image.channels() == 3) {
     
				float pixel_b = image_cvt2F.at<Vec3f>(row, col)[0];
				float pixel_g = image_cvt2F.at<Vec3f>(row, col)[1];
				float pixel_r = image_cvt2F.at<Vec3f>(row, col)[2];

				image_out.at<Vec3f>(row, col)[0] = saturate_cast<uchar>(pixel_b * alpha + beta);
				image_out.at<Vec3f>(row, col)[1] = saturate_cast<uchar>(pixel_g * alpha + beta);
				image_out.at<Vec3f>(row, col)[2] = saturate_cast<uchar>(pixel_r * alpha + beta);
				
			}
			else if(image.channels() == 1) {
     
				int pixel_gray = image.at<uchar>(row, col);
				
				image_out.at<uchar>(row, col) = saturate_cast<uchar>(pixel_gray * alpha + beta);

			}
		}
	}

	namedWindow("output", WINDOW_AUTOSIZE);
	imshow("output", image_out);

	waitKey(0);
	cvDestroyAllWindows();

	return 0;
}

8. 绘制基础图型

8.1 图形&文字绘制

#include 
#include 

using namespace std;
using namespace cv;

Mat image;			//声明全局的Mat image

void Line();		//声明绘制Line的函数Line()
void Rectangle();	//矩形
void Ellipse();		//椭圆
void Circle();		//圆
void FillPolygon();	//实心多边形
void Text();		//文本

int main()
{
     
	image = imread("C://eg//2.jpg");	//输入图像

	if (image.empty()) {
     
		return -1;
	}

	Line();			//
	Rectangle();	//
	Ellipse();		//
	Circle();
	FillPolygon();
	Text();

	namedWindow("input", WINDOW_AUTOSIZE);
	imshow("input", image);

	waitKey(0);
	cvDestroyAllWindows();

	return 0;
}

void Line() {
     
	Point a = Point(10, 20);			//坐标
	Point b = Point(110, 120);

	Scalar color = Scalar(0, 0, 255);	//颜色为红

	line(image, a, b, color, 1, LINE_4);
}

void Rectangle() {
     
	Rect rect = Rect(50, 100, 50, 50);	//坐标&形状
	Scalar color = Scalar(255, 0, 0);	//蓝色

	rectangle(image, rect, color, 1, LINE_4);
}

void Ellipse() {
     
	Point a = Point(image.rows / 2, image.cols / 2);	//圆心
	Size b = Size(image.rows / 8, image.cols / 4);		//尺寸
	Scalar color = Scalar(0, 255, 0);	//绿色

	ellipse(image, a, b, 90, 0, 360, color, 1, LINE_4);	
}

void Circle() {
     
	Point a = Point(image.rows / 1.5, image.cols / 1.2);	//圆心
	int r = image.rows / 6;					//半径
	Scalar color = Scalar(203, 192, 255);	//pink

	circle(image, a, r, color, 1, LINE_4);
}	

void FillPolygon() {
     
	Point pts[1][5];
	pts[0][0] = Point(100, 100);
	pts[0][1] = Point(150, 225);
	pts[0][2] = Point(200, 100);
	pts[0][3] = Point(80, 180);
	pts[0][4] = Point(220, 180);

	const Point* ppts[] = {
      pts[0] };
	int npt[] = {
      5 };
	Scalar color = Scalar(219, 112, 147);

	fillPoly(image, ppts, npt, 1, color, 8);
} 

void Text() {
     
	Point p = Point(160, 460);
	Scalar color = Scalar(60, 20, 220);
	string text = "????";

	putText(image, text, p, CV_FONT_NORMAL, 2, color, 2, LINE_4);
}

OpenCV3学习笔记_第8张图片

8.2 随机线生成

#include 
#include 

using namespace std;
using namespace cv;

Mat image;			//声明全局的Mat image

void RDMLine();

int main()
{
     
	image = imread("C://eg//2.jpg");	//输入图像

	if (image.empty()) {
     
		return -1;
	}

	RDMLine();

	waitKey(0);
	cvDestroyAllWindows();

	return 0;
}

void RDMLine() {
     
	RNG rng(getTickCount());
	Point p1, p2;
	Mat image0 = Mat::zeros(image.size(), image.type());

	namedWindow("RDMLine", WINDOW_AUTOSIZE);
	for (int i = 0; i < 100000; i++) {
     
		p1.x = rng.uniform(0, image.cols);
		p2.x = rng.uniform(0, image.cols);
		p1.y = rng.uniform(0, image.rows);
		p2.y = rng.uniform(0, image.rows);

		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		if (waitKey(50) > 0) {
     
			break;
		}
		line(image0, p1, p2, color, 1, LINE_4);
		imshow("RDMLine", image0);
	}
}

OpenCV3学习笔记_第9张图片

8.3 椒盐噪声

#include  
#include  
using namespace cv;

void SPNoice(Mat image_0, int n);

int main(int argc, char** argv) {
     

    Mat image;
	image = imread("C://eg//akane.jpg");
	if (image.empty()) {
     
		return -1;
	}
	
	char input_title[] = "input image";
	char output_title0[] = "output image";

	namedWindow(input_title, CV_WINDOW_AUTOSIZE);
	namedWindow(output_title0, CV_WINDOW_AUTOSIZE);

	imshow(input_title, image);

    SPNoice(image,500);
    imwrite("C://eg//akane_spnoice.jpg", image);	//保存椒盐噪声图
	imshow(output_title0, image);

	waitKey(0);
	cvDestroyAllWindows();
	return 0;
}

//
void SPNoice(   Mat image_0,    //输入输出图像
                int n           //椒盐个数
            ) 
{
     
    int noice = 0;  //噪声种类,0为椒噪声,1为盐噪声

    int C_row = 0;  //像素点
    int C_col = 0;
   
    //每循环一次,添加一个椒盐噪声
    while (n > 0) {
     
        noice = rand() % 2;     //随机确定椒盐噪声种类
                              
        //随机产生函数获取被修改的图像Mat对应像素点的位置
        C_row = rand() % image_0.rows;
        C_col = rand() % image_0.cols;

        //灰度图
        if (image_0.channels() == 1) {
     

            if (noice == 0) {
     
                image_0.at<uchar>(C_row, C_col) = 0;
            }
            else if(noice == 1) {
     
                image_0.at<uchar>(C_row, C_col) = 255;
            }
        }

        //RGB图
        else if(image_0.channels() == 3) {
     

            if (noice == 0) {
     
                image_0.at<Vec3b>(C_row, C_col)[0] = 0;
                image_0.at<Vec3b>(C_row, C_col)[1] = 0;
                image_0.at<Vec3b>(C_row, C_col)[2] = 0;
            }
            else if (noice == 1) {
     
                image_0.at<Vec3b>(C_row, C_col)[0] = 255;
                image_0.at<Vec3b>(C_row, C_col)[1] = 255;
                image_0.at<Vec3b>(C_row, C_col)[2] = 255;
            }
        }
        
       
        n--;
    }
}

OpenCV3学习笔记_第10张图片

9. 图像模糊

卷积:假设有6x6的图像像素点矩阵。6x6上面是个3x3的窗口,从左向右,从上向下移动,黄色的每个像个像素点值之和取平均值赋给中心红色像素作为它卷积处理之后新的像素值。每次移动一个像素格。

OpenCV3学习笔记_第11张图片

9.1 均值模糊&高斯模糊:

#include  
#include  
using namespace cv;

int main(int argc, char** argv) {
     
	Mat image, image_blur, image_Gblur;
	image = imread("C://eg//akane_spnoice.jpg");
	if (image.empty()) {
     
		return -1;
	}

	char input_title[] = "input image";
	char output_title0[] = "blur image";
	String output_title1 = "gaussian blur image";
	namedWindow(input_title, CV_WINDOW_AUTOSIZE);
	namedWindow(output_title0, CV_WINDOW_AUTOSIZE);
	namedWindow(output_title1, WINDOW_AUTOSIZE);

	imshow(input_title, image);

	blur(image, image_blur, Size(5, 5), Point(-1, -1));		//均值模糊
	imshow(output_title0, image_blur);

	GaussianBlur(image, image_Gblur, Size(11, 11), 11, 11);		//高斯模糊
	imshow(output_title1, image_Gblur);

	waitKey(0);
	cvDestroyAllWindows();

	return 0;
}

OpenCV3学习笔记_第12张图片

9.2 中值滤波&双边滤波

中值模糊medianBlur(Mat src, Mat dest, ksize)

  • Mat src:输入图像;
  • Mat dest:输出图像;
  • ksize:卷积核尺寸。

双边模糊bilateralFilter(src, dest, d, sigma color, sigma space)

  • src:输入图像;
  • dest:输出图像;
  • d:计算的半径,半径之内的像数都会被纳入计算,如果提供-1 则根据sigma space参数取值;
  • sigma color 颜色空间过滤器的sigma值,这个参数的值月大,表明该像素邻域内有更宽广的范围颜色会被混合到一起,产生较大的半相等颜色区域,看着雾蒙蒙的;
  • sigma space 如果d的值大于0则声明无效,否则根据它来计算d值,中值模糊的ksize大小必须是大于1,而且必须是奇数。
#include  
#include  
using namespace cv;

int main(int argc, char** argv) {
     
	Mat image, image_median_blur, image_bilateral_blur;
	image = imread("C://eg//akane_spnoice.jpg");
	if (image.empty()) {
     
		return -1;
	}

	char input_title[] = "input image";
	char output_title0[] = "median blur image";
	String output_title1 = "bilateral blur image";
	namedWindow(input_title, CV_WINDOW_AUTOSIZE);
	namedWindow(output_title0, CV_WINDOW_AUTOSIZE);
	namedWindow(output_title1, WINDOW_AUTOSIZE);

	imshow(input_title, image);

	medianBlur(image, image_median_blur, 3);
	imshow(output_title0, image_median_blur);

	bilateralFilter(image, image_bilateral_blur, 15, 150, 20);
	imshow(output_title1, image_bilateral_blur);

	waitKey(0);
	cvDestroyAllWindows();

	return 0;
}

OpenCV3学习笔记_第13张图片

10. 形态学操作

10.1 膨胀与腐蚀

关键api:

  • getStructuringElement(int shape, Size ksize, Point anchor):返回指定形状和尺寸的结构元素。
    shape形状 (MORPH_RECT \MORPH_CROSS \MORPH_ELLIPSE);
    ksize大小;
    anchor锚点,默认是Point(-1, -1)意思就是中心像素。

  • dilate(image, image_dilate, kernel)膨胀;

  • erode(image, image_erode, kernel)腐蚀.

#include  
#include  
using namespace cv;

int main(int argc, char** argv) {
     
	Mat image, image_dilate, image_erode;
	image = imread("C://eg//i.png");
	if (image.empty()) {
     
		return -1;
	}
	imshow("image", image);

	Mat kernel = getStructuringElement(MORPH_RECT, Size(7, 7), Point(-1, -1));

	dilate(image, image_dilate, kernel);

	erode(image, image_erode, kernel);

	imshow("image_dilate", image_dilate);
	imshow("image_erode", image_erode);
	

	waitKey(0);
	cvDestroyAllWindows();

	return 0;
}

OpenCV3学习笔记_第14张图片

10.2 开、闭、形态学梯度、顶帽、黑帽:

  • 开:先腐蚀后膨胀,可以去掉小的对象,假设对象是前景色,背景是黑色;

  • 闭:先膨胀后腐蚀,可以填充小的洞,假设对象是前景色,背景是黑色;

  • 形态学梯度:膨胀减去腐蚀,又称为基本梯度(其它还包括-内部梯度、方向梯度);

  • 顶帽:原图像和开操作图像的差值;

  • 黑帽:闭操作图像和原图像的差值。

关键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
#include  
#include  
using namespace cv;

int main(int argc, char** argv) {
     
	Mat image, image_open, image_close, image_gradient, image_tophat, image_blackhat;
	image = imread("C://eg//i_spnoice.jpg");
	if (image.empty()) {
     
		return -1;
	}
	imshow("image", image);

	Mat kernel = getStructuringElement(MORPH_RECT, Size(3,3), Point(-1, -1));

	morphologyEx(image, image_open, CV_MOP_OPEN, kernel);			//开
	morphologyEx(image, image_close, CV_MOP_CLOSE, kernel);			//闭
	morphologyEx(image, image_gradient, CV_MOP_GRADIENT, kernel);	//形态学梯度
	morphologyEx(image, image_tophat, CV_MOP_TOPHAT, kernel);		//顶帽
	morphologyEx(image, image_blackhat, CV_MOP_BLACKHAT, kernel);	//黑帽

	imshow("image_open", image_open);
	imshow("image_close", image_close);
	imshow("image_gradient", image_gradient);
	imshow("image_tophat", image_tophat);
	imshow("image_blackhat", image_blackhat);


	waitKey(0);
	cvDestroyAllWindows();

	return 0;
}


OpenCV3学习笔记_第15张图片

10.3 水平&竖直线条提取:

  1. 输入图像彩色图像 imread
  2. 转换为灰度图像 – cvtColor
  3. 转换为二值图像 – adaptiveThreshold
  4. 定义结构元素
  5. 开操作(腐蚀+膨胀)提取水平与垂直线
#include  
#include  
using namespace cv;

int main(int argc, char** argv) {
     
	Mat image, image_gray, image_binary, imahe_line_w, imahe_line_h;
	image = imread("C://eg//3.png");
	if (image.empty()) {
     
		return -1;
	}
	imshow("image", image);

	if (image.channels() == 3) {
     
		cvtColor(image, image_gray, COLOR_BGR2GRAY);
	}
	else if(image.channels() == 1) {
     
		image_gray = image;
	}
	namedWindow("image_gray", WINDOW_AUTOSIZE);
	imshow("image_gray", image_gray);


	adaptiveThreshold(image_gray, image_binary, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, 1);
	bitwise_not(image_binary, image_binary);

	namedWindow("image_binary", WINDOW_AUTOSIZE);
	imshow("image_binary", image_binary);


	Mat line_w, line_h;
	line_w = getStructuringElement(MORPH_RECT, Size(image.cols / 30, 1), Point(-1, -1));
	line_h = getStructuringElement(MORPH_RECT, Size(1, image.rows / 30), Point(-1, -1));

	morphologyEx(image_binary, imahe_line_w, CV_MOP_OPEN, line_w);
	morphologyEx(image_binary, imahe_line_h, CV_MOP_OPEN, line_h);
	imshow("imahe_line_w", imahe_line_w);
	imshow("imahe_line_h", imahe_line_h);

	waitKey(0);
	cvDestroyAllWindows();

	return 0;
}

OpenCV3学习笔记_第16张图片

OpenCV3学习笔记_第17张图片

11. 图像的升采样和降采样、高斯不同

  • 两种采样的目的是放缩图片大小,放缩图像也可以使用几何变换,这里使用图像金字塔的方式,

  • 升采样(cv::pyrUp) – zoom in 放大
    降采样 (cv::pyrDown) – zoom out 缩小

  • 高斯金子塔是从底向上,逐层降采样得到。
    降采样之后图像大小是原图像MxN的M/2 x N/2 ,就是对原图像删除偶数行与列,即得到降采样之后上一层的图片。高斯金子塔的生成过程分为两步:

    1. 对当前层进行高斯模糊
    2. 删除当前层的偶数行与列

    这样即可得到上一层的图像,这样上一层跟下一层相比,都只有它的1/4大小

  • 高斯不同的定义:就是把同一张图像在不同的参数下做高斯模糊之后的结果相减,得到的输出图像。称为高斯不同(DOG)。

  • 高斯不同是图像的内在特征,在灰度图像增强、角点检测中经常用到。

#include 
#include 
#include "math.h"

using namespace std;
using namespace cv;
int main(int agrc, char** argv) {
     
	Mat image;
	image = imread("C://eg//3.jpg");
	if (image.empty()) {
     
		return -1;
	}
	Mat image_big, image_small;
	namedWindow("image", CV_WINDOW_AUTOSIZE);
	namedWindow("image_big", CV_WINDOW_AUTOSIZE);
	imshow("image", image);

	pyrUp(image, image_big, Size(image.cols * 2, image.rows * 2));//升采样
	imshow("image_big", image_big);

	pyrDown(image, image_small, Size(image.cols / 2, image.rows / 2));//降采样
	imshow("image_small", image_small);

	Mat image_gray, g1, g2, g3;
	cvtColor(image, image_gray, CV_BGR2GRAY);
	GaussianBlur(image_gray, g1, Size(5, 5), 0, 0);
	GaussianBlur(g1, g2, Size(5, 5), 0, 0);
	subtract(g1, g2, g3, Mat());	//高斯不同

	Mat image_DOG = g3;
	normalize(image_DOG, image_DOG, 255, 0, NORM_MINMAX);
	imshow("DOG Image", image_DOG);

	waitKey(0);
	return 0;
}

OpenCV3学习笔记_第18张图片

12. 阈值操作

OpenCV3学习笔记_第19张图片
OpenCV3学习笔记_第20张图片

基本的图像阈值操作有:二值化、反二值化、截断、取零、反取零,还有后面三个以后再学。

#include 
#include 
#include 
using namespace std;
using namespace cv;

int thresh = 128;

int main(int argc, char** argv) {
     
	Mat image, image_gray, image_out0, image_out1, image_out2, image_out3, image_out4;
	image = imread("C://eg//3.png");
	if (image.empty()) {
     
		printf("could not load image...\n");
		return -1;
	}
	cvtColor(image, image_gray, CV_BGR2GRAY);

	threshold(image_gray, image_out0, thresh, 255, THRESH_BINARY);		//二值化
	threshold(image_gray, image_out1, thresh, 255, THRESH_BINARY_INV);	//反二值化
	threshold(image_gray, image_out2, thresh, 255, THRESH_TRUNC);		//截断
	threshold(image_gray, image_out3, thresh, 255, THRESH_TOZERO);		//取零
	threshold(image_gray, image_out4, thresh, 255, THRESH_TOZERO_INV);	//反取零

	imshow("THRESH_BINARY", image_out0);
	imshow("THRESH_BINARY_INV", image_out1);
	imshow("THRESH_TRUNC", image_out2);
	imshow("THRESH_TOZERO", image_out3);
	imshow("THRESH_TOZERO_INV",image_out4);

	waitKey(0);
	return 0;
}

利用二值化阈值操作来去水印

#include 
#include 
#include 
using namespace std;
using namespace cv;

int thresh = 175;

int main(int argc, char** argv) {
     
	Mat image, image_gray, image_out0;
	image = imread("C://eg//4.png");
	if (image.empty()) {
     
		printf("could not load image...\n");
		return -1;
	}
	imshow("image", image);

	cvtColor(image, image_gray, CV_BGR2GRAY);

	threshold(image_gray, image_out0, thresh, 255, THRESH_BINARY);
	imshow("THRESH_BINARY", image_out0);
	imwrite("C://eg//4_out.png", image_out0);

	waitKey(0);
	return 0;
}


OpenCV3学习笔记_第21张图片

13. 自定义线性滤波

  • 卷积是图像处理中一个操作,是kernel在图像的每个像素上的操作。Kernel本质上一个固定大小的矩阵数组,其中心点称为锚点(anchor point)。
    OpenCV3学习笔记_第22张图片

  • 把kernel放到像素数组之上,求锚点周围覆盖的像素乘积之和(包括锚点),用来替换锚点覆盖下像素点值称为卷积处理。

  • 自定义卷积模糊:
    filter2D(
    Mat src, //输入图像
    Mat dst, // 模糊图像
    int depth, // 图像深度32/8
    Mat kernel, // 卷积核/模板
    Point anchor, // 锚点位置
    double delta // 计算出来的像素+delta
    )
    注意:其中 kernel是可以自定义的卷积核

#include 
#include 

using namespace std;
using namespace cv;



int main(int argc, char** argv) {
     
	Mat image, image_out;
	image = imread("C://eg//jr.jpg");
	if (image.empty()) {
     
		printf("could not load image...\n");
		return -1;
	}
	imshow("image", image);
	int ksize = 5;
	Mat kernel = Mat::ones(Size(ksize, ksize), CV_32F) / (ksize * ksize);

	filter2D(image, image_out, -1, kernel, Point(-1, -1));
	imshow("image_out", image_out);


	waitKey(0);
	return 0;
}


OpenCV3学习笔记_第23张图片
卷积边界问题:图像卷积的时候边界像素,不能被卷积操作,原因在于边界像素没有完全跟kernel重叠,所以当3x3滤波时候有1个像素的边缘没有被处理,5x5滤波的时候有2个像素的边缘没有被处理。

  • 相关api:
    copyMakeBorder(
    Mat src, // 输入图像
    Mat dst, // 添加边缘图像
    int top, // 边缘长度,一般上下左右都取相同值,
    int bottom,
    int left,
    int right,
    int borderType // 边缘类型
    Scalar value)
  • 边缘类型:
    BORDER_DEFAULT – OpenCV中默认的处理方法,镜像填充;
    BORDER_CONSTANT – 用指定像素值填充边缘;
    BORDER_REPLICATE – 用最边缘的像素值作为插值来填充边缘像素;
    BORDER_WRAP – 用另外一边的图像来填充。
#include 
#include 

using namespace std;
using namespace cv;

int main(int argc, char** argv) {
     
	Mat image, image_out0, image_out1, image_out2, image_out3;
	image = imread("C://eg//jr.jpg");
	if (image.empty()) {
     
		printf("could not load image...\n");
		return -1;
	}
	imshow("image", image);

	copyMakeBorder(image, image_out0, 9, 9, 9, 9, BORDER_CONSTANT, Scalar(123, 123, 123));
	imshow("image_out", image_out0);
	
	copyMakeBorder(image, image_out1, 9, 9, 9, 9, BORDER_DEFAULT, Scalar(123, 123, 123));
	imshow("image_out1", image_out1);

	copyMakeBorder(image, image_out2, 9, 9, 9, 9, BORDER_WRAP, Scalar(123, 123, 123));
	imshow("image_out2", image_out2);

	copyMakeBorder(image, image_out3, 9, 9, 9, 9, BORDER_REPLICATE, Scalar(123, 123, 123));
	imshow("image_out3", image_out3);

	waitKey(0);
	return 0;
}


OpenCV3学习笔记_第24张图片
加边界-卷积-去边界操作:

#include 
#include 

using namespace std;
using namespace cv;



int main(int argc, char** argv) {
     
	Mat image, image_addborder, image_out;
	image = imread("C://eg//jr.jpg");
	if (image.empty()) {
     
		return -1;
	}
	imshow("image", image);
	int ksize = 3;

	copyMakeBorder(image, image_addborder, ksize / 2, ksize / 2, ksize / 2, ksize / 2, BORDER_REPLICATE, Scalar(123, 123, 123));
	imshow("image_addborder", image_addborder);


	Mat kernel = Mat::ones(Size(ksize, ksize) , CV_32F) / (float)(ksize * ksize);


	filter2D(image_addborder, image_out, -1, kernel, Point(-1, -1), (0, 0), BORDER_REPLICATE);
	imshow("image_out", image_out);


	waitKey(0);
	return 0;
}


OpenCV3学习笔记_第25张图片

14. 图像边缘提取

  • 边缘是像素值发生跃迁的地方,是图像的显著特征之一,在图像特征提取、对象检测、模式识别等方面都有重要的作用。
    OpenCV3学习笔记_第26张图片

  • 如何捕捉/提取边缘?
    对图像求导数,delta = f(x) – f(x-1), delta越大,说明像素在X方向变化越大,边缘信号越强。

14.1 Sobel算子 & Scharr算子

OpenCV3学习笔记_第27张图片
注意:相比sobel算子,scharr算子能计算出更小的梯度变化

  • 处理步骤:
  1. 高斯模糊去噪声

  2. 转换为灰度图像

  3. 分别在水平梯度、垂直梯度进行计算。
    水平梯度
    垂直梯度

  4. 取绝对值并求和,得到最终结果
    OpenCV3学习笔记_第28张图片

  • 相关api:
    • cv::Sobel (
      InputArray Src // 输入图像
      OutputArray dst// 输出图像,大小与输入图像一致
      int depth // 输出图像深度.
      Int dx. // X方向,几阶导数
      int dy // Y方向,几阶导数.
      int ksize, SOBEL算子kernel大小,必须是1、3、5、7、
      double scale = 1
      double delta = 0
      int borderType = BORDER_DEFAULT
      )
    • cv::Scharr (
      InputArray Src // 输入图像
      OutputArray dst// 输出图像,大小与输入图像一致
      int depth // 输出图像深度.
      Int dx. // X方向,几阶导数
      int dy // Y方向,几阶导数.
      double scale = 1
      double delta = 0
      int borderType = BORDER_DEFAULT
      )
    • GaussianBlur( src, dst, Size(3,3), 0, 0, BORDER_DEFAULT );
    • cvtColor( src, gray, COLOR_RGB2GRAY );
    • addWeighted( A, 0.5,B, 0.5, 0, AB);
    • convertScaleAbs(A, B)// 计算图像A的像素绝对值,输出到图像B
    • saturate_cast< uchar> () //截断
#include 
#include 

using namespace std;
using namespace cv;



int main(int argc, char** argv) {
     
	Mat image, image_GaussianBlur, image_gray;
	image = imread("C://eg//2.jpg");
	if (image.empty()) {
     
		return -1;
	}
	imshow("image", image);

	GaussianBlur(image, image_GaussianBlur, Size(3, 3), 0, 0);
	cvtColor(image_GaussianBlur, image_gray, CV_BGR2GRAY);
	imshow("image_gray", image_gray);
	
	//sobel
	Mat image_sobel_x, image_sobel_y;

	Sobel(image_gray, image_sobel_x, CV_16S, 1, 0, 3);
	Sobel(image_gray, image_sobel_y, CV_16S, 0, 1, 3);

	convertScaleAbs(image_sobel_x, image_sobel_x);
	convertScaleAbs(image_sobel_y, image_sobel_y);

	Mat image_sobel = Mat(image_sobel_x.size(), image_sobel_x.type());

	int width = image_sobel_x.cols;
	int height = image_sobel_x.rows;

	for (int row = 0; row < height; row++) {
     
		for (int col = 0; col < width; col++) {
     
			int pixel_x = image_sobel_x.at <uchar>(row, col);
			int pixel_y = image_sobel_y.at <uchar>(row, col);

			image_sobel.at<uchar>(row, col) = saturate_cast<uchar>(pixel_x + pixel_y);
		}
	}
	imshow("image_sobel", image_sobel);

	//scharr
	Mat image_scharr_x, image_scharr_y;

	Scharr(image_gray, image_scharr_x, CV_16S, 1, 0, 3);
	Scharr(image_gray, image_scharr_y, CV_16S, 0, 1, 3);

	convertScaleAbs(image_scharr_x, image_scharr_x);
	convertScaleAbs(image_scharr_y, image_scharr_y);

	Mat image_scharr = Mat(image_scharr_x.size(), image_scharr_x.type());
	int width1 = image_scharr_x.cols;
	int height1 = image_scharr_x.rows;

	for (int row = 0; row < height1; row++) {
     
		for (int col = 0; col < width1; col++) {
     
			int pixel_x = image_scharr_x.at<uchar>(row, col);
			int pixel_y = image_scharr_y.at<uchar>(row, col);

			image_scharr.at<uchar>(row, col) = saturate_cast<uchar>(pixel_x + pixel_y);
		}
	}
	imshow("image_scharr", image_scharr);

	waitKey(0);
	return 0;
}

OpenCV3学习笔记_第29张图片

14.2 Laplance算子

OpenCV3学习笔记_第30张图片

  • 处理步骤
  1. 高斯模糊去噪声
  2. 转换为灰度图像
  3. 拉普拉斯 – 二阶导数计算Laplacian()
  4. 取绝对值convertScaleAbs(),显示结果
#include 
#include 

using namespace std;
using namespace cv;



int main(int argc, char** argv) {
     
	Mat image, image_GaussianBlur, image_gray;
	image = imread("C://eg//2.jpg");
	if (image.empty()) {
     
		return -1;
	}
	imshow("image", image);

	GaussianBlur(image, image_GaussianBlur, Size(3, 3), 0, 0);
	cvtColor(image_GaussianBlur, image_gray, CV_BGR2GRAY);
	imshow("image_gray", image_gray);
	
	Mat image_laplance;
	Laplacian(image_gray, image_laplance, CV_16S, 3);
	convertScaleAbs(image_laplance, image_laplance);
	//threshold(image_laplance, image_laplance, 0, 255, THRESH_OTSU | THRESH_BINARY);

	imshow("image_laplance", image_laplance);

	waitKey(0);
	return 0;
}

OpenCV3学习笔记_第31张图片

14.3 Canny算子

Canny算子被很多人推崇为当今最优的边缘检测的算法。

  • Canny算子的流程:
  1. 高斯滤波消除噪声;

  2. 将图像转换为灰度图;

  3. 用一阶偏导的有限差分来计算梯度的幅值和方向;
    OpenCV3学习笔记_第32张图片

  4. 对梯度幅值进行非极大值抑制;
    OpenCV3学习笔记_第33张图片

  5. 用双阈值算法检测和连接边缘:T1, T2为阈值,凡是高于T2的都保留,凡是小于T1都丢弃,从高于T2的像素出发,凡是大于T1而且相互连接的,都保留。最终得到一个输出二值图像。
    推荐的高低阈值比值为 T2: T1 = 3:1/2:1其中T2为高阈值,T1为低阈值。

  • 相关api:
    • Canny(
      InputArray src, // 8-bit的输入图像
      OutputArray edges,// 输出边缘图像, 一般都是二值图像,背景是黑色
      double threshold1,// 低阈值,常取高阈值的1/2或者1/3
      double threshold2,// 高阈值
      int aptertureSize,// Soble算子的size,通常3x3,取值3
      bool L2gradient // 选择 true表示是L2来归一化,否则用L1归一化,默认选择L1。
#include 
#include 

using namespace std;
using namespace cv;

int main(int argc, char** argv) {
     
	Mat image, image_GaussianBlur, image_gray;

	int value_min = 50;
	float value_mul = 2.0;
	int value_max = 255;

	image = imread("C://eg//3.jpg");
	if (image.empty()) {
     
		return -1;
	}
	imshow("image", image);

	GaussianBlur(image, image_GaussianBlur, Size(3, 3), 0, 0);
	cvtColor(image_GaussianBlur, image_gray, CV_BGR2GRAY);
	imshow("image_gray", image_gray);

	Mat image_canny;
	Canny(image_gray, image_canny, value_min, value_min * value_mul, 3, false);
	imshow("image_canny", image_canny);

	waitKey(0);
	return 0;
}

OpenCV3学习笔记_第34张图片

15.基础图形检测

15.1 霍夫变换——直线检测

  • 在边缘检测已经完成的条件下,霍夫直线变换用来做直线检测,主要是平面空间到极坐标空间转换。

  • 直线从直角坐标系转换到极坐标系:r = xcosθ + ysinθ

  • 可以看到,根据以上公式,同时在图像空间处于一条直线的 n个点 将他们的坐标代入,会得到n个在参数空间中关于r和θ的函数,表示为图像就是n条曲线,n条曲线总会相交于同一点。图像空间中共线的点在参数空间中曲线相交于一点。 OpenCV3学习笔记_第35张图片

  • 也就是说,如果一幅图像空间(x-y)中的像素构成一条直线,那么这些像素坐标值(x, y)在参数空间对应的曲线一定相交于一个点,所以我们只需要将图像空间(x-y)中的所有像素点变换成参数空间(r-θ)的曲线,并在参数空间检测曲线的交点,得到交点(r,θ)就可以确定直线了

  • 霍夫变换的直线检测是把图像空间中的直线变换到参数空间中的点,通过统计特性来解决检测问题。为何要用?以及如何使用统计特性?因为确定一点时,找到一个能够与这点构成直线的点有无限种可能。在现实应用里,我们就要限定(r-θ)的密度,也就是要将θ离散化。

  • 霍夫变换检测直线的原理:

  1. 图像空间(x-y) > 参数空间(r-θ);
  2. 将参数空间的θ离散化,并求出每个θ对应的r;
  3. 统计每个θ对应的r,并对r计数,通过计数最多的r及其θ计算出直线。
    OpenCV3学习笔记_第36张图片
    霍夫变换api:
//标准霍夫变换、多尺度霍夫变换
CV_EXPORTS_W void HoughLines( InputArray image, OutputArray lines, 
  double rho, double theta, int threshold, 
  double srn = 0, double stn = 0, 
  double min_theta = 0, double max_theta = CV_PI );
  
  //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函数执行标准霍夫变换,否则就是执行多尺度霍夫变换。

//渐进概率式霍夫变换
CV_EXPORTS_W void HoughLinesP( InputArray image, OutputArray lines, 
  double rho, double theta, int threshold, 
  double minLineLength = 0, double maxLineGap = 0 ); 
  
  //InputArray image:输入图像,必须是8位单通道图像。 
  //OutputArray lines:检测到的线条参数集合。 
  //double rho:直线搜索时的距离步长,以像素为单位。 
  //double theta:直线搜索时的角度步长,以弧度为单位。 
  //int threshold:累加计数值的阈值参数,当参数空间某个交点的累加计数的值超过该阈值,则认为该交点对应了图像空间的一条直线。 
  //double minLineLength:默认值为0,表示最小线段长度阈值(像素)。 
  //double maxLineGap:线段上最近两点之间的阈值.默认值为0,表示直线断裂的最大间隔距离阈值。即如果有两条线段是在一条直线上,但它们之间有间隙,那么如果这个间隔距离小于该值,则被认为是一条线段,否则认为是两条线段。 

  • 使用霍夫变换api检测直线具体步骤:
  1. 转换为灰度图;
  2. 高斯模糊去噪;
  3. 边缘检测;
  4. 二值化(判断此处是否为直线点,就看灰度值==255);
  5. 映射到霍夫空间(准备两个容器,一个用来展示hough-space概况,一个数组hough-space用来储存voting的值,因为投票过程往往有某个极大值超过阈值,多达几千,不能直接用灰度图来记录投票信息);
  6. 取局部极大值,设定阈值,过滤干扰直线;
  7. 绘制直线、标定角点。
#include 
#include 

using namespace cv;
using namespace std;
int thresh_b = 128;
int main(int argc, char** argv) {
     
	Mat image, image_gray,image_GaussianBlur, image_binary, image_canny, ;
	
	image = imread("C://eg//line1.png");
	if (image.empty()) {
     
		return -1;
	}

	namedWindow("image",CV_WINDOW_AUTOSIZE);
	imshow("image", image);

	cvtColor(image, image_gray, CV_BGRA2GRAY);	
	threshold(image_gray, image_binary, thresh_b, 255, THRESH_BINARY);
	GaussianBlur(image_binary, image_GaussianBlur, Size(3, 3), 0);
	Canny(image_GaussianBlur, image_canny, 50, 100);

	vector<Vec2f> lines;
	HoughLines(image_canny, lines, 1, CV_PI / 180, 150, 0, 0);
	for (size_t i = 0; i < lines.size(); i++) {
     
		float rho = lines[i][0]; 
		float theta = lines[i][1];
		Point pt1, pt2;
		double a = cos(theta), b = sin(theta);
		double x0 = a * rho, y0 = b * rho;

		pt1.x = cvRound(x0 + 1000 * (-b)); //cvRound四舍五入
		pt1.y = cvRound(y0 + 1000 * (a));
		pt2.x = cvRound(x0 - 1000 * (-b));
		pt2.y = cvRound(y0 - 1000 * (a));
		line(image, pt1, pt2, Scalar(0, 0, 255), 1, CV_AA);
	}
	imshow("image_out0", image);

	waitKey(0);
	return 0;
}

OpenCV3学习笔记_第37张图片

#include 
#include 

using namespace cv;
using namespace std;
int thresh_b = 128;
int main(int argc, char** argv) {
     
	Mat image, image_gray,image_GaussianBlur, image_binary, image_canny, ;
	
	image = imread("C://eg//line1.png");
	if (image.empty()) {
     
		return -1;
	}

	namedWindow("image",CV_WINDOW_AUTOSIZE);
	imshow("image", image);

	cvtColor(image, image_gray, CV_BGRA2GRAY);	
	threshold(image_gray, image_binary, thresh_b, 255, THRESH_BINARY);
	GaussianBlur(image_binary, image_GaussianBlur, Size(3, 3), 0);
	Canny(image_GaussianBlur, image_canny, 50, 100);

	vector<Vec4f> plines;
	HoughLinesP(image_canny, plines, 1, CV_PI / 180.0, 10, 0, 10);
	Scalar color = Scalar(0, 0, 255);
	for (size_t i = 0; i < plines.size(); i++) {
     
		Vec4f hline = plines[i];
		line(image, Point(hline[0], hline[1]), Point(hline[2], hline[3]), color, 3, LINE_AA);
	}
	imshow("image_output", image);

	waitKey(0);
	return 0;
}

OpenCV3学习笔记_第38张图片

15.2 霍夫变换——圆

HoughCircles(
InputArray image, // 输入图像 ,必须是8位的单通道灰度图像
OutputArray circles, // 输出结果,发现的圆信息圆心坐标,圆半径
Int method, 	// 方法 - HOUGH_GRADIENT
Double dp, 		// dp = 1; 
Double mindist, // 10 最短距离-可以分辨是两个圆的,否则认为是同心圆- src_gray.rows/8
Double param1, 	// canny edge detection low threshold
Double param2, 	// 中心点累加器阈值 – 候选圆心
Int minradius, 	// 最小半径
Int maxradius	// 最大半径 
)

#include 
#include 

using namespace cv;
using namespace std;

int thresh_b = 128;

int main(int argc, char** argv) {
     
	Mat image, image_gray, image_GaussianBlur, image_binary, image_canny;
	
	image = imread("C://eg//circle.png");
	if (image.empty()) {
     
		return -1;
	}

	namedWindow("image",CV_WINDOW_AUTOSIZE);
	imshow("image", image);

	cvtColor(image, image_gray, CV_BGRA2GRAY);	
	threshold(image_gray, image_binary, thresh_b, 255, THRESH_BINARY);
	GaussianBlur(image_binary, image_GaussianBlur, Size(5, 5), 0);

	vector<Vec3f> circles;
	HoughCircles(image_GaussianBlur, circles, HOUGH_GRADIENT, 1, 10, 100, 32, 70, 125);

	for (size_t i = 0; i < circles.size(); i++) {
     
		Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
		int r = cvRound(circles[i][2]);

		circle(image, center, r, Scalar(0, 0, 255), 8, LINE_4, 0);
	}

	imshow("image_out", image);

	waitKey(0);
	return 0;
}

OpenCV3学习笔记_第39张图片

16. 像素重映射

相关api:

	remap(
    InputArray src,     //输入图像
    OutputArray dst,    //输出图像
    InputArray map1,	//x映射表  CV_32FC1/CV_32FC2
    InputArray map2,	//y映射表
    Int interpolation,  //插值方式
    Int borderMode, 	//边界模式
    Const Scalar borderValue //边界Color
	)
#include 
#include 

using namespace cv;
using namespace std;

int main()
{
     
	Mat image, image_out, map_x, map_y;

	image = imread("C://eg//1.png");

	image_out.create(image.size(), image.type());
	map_x.create(image.size(), CV_32FC1);
	map_y.create(image.size(), CV_32FC1);

	for (size_t j = 0; j < image.rows; j++)
	{
     
		for (size_t i = 0; i < image.cols; i++)
		{
     
			//旋转180度
			map_x.at<float>(j, i) = static_cast<float>(image.cols - i);
			map_y.at<float>(j, i) = static_cast<float>(image.rows - j);

			//缩小
//			if (i > (image.cols * 0.25) && i <= (image.cols * 0.75) && j > (image.rows * 0.25) && j <= (image.rows * 0.75)) {
     
//				map_x.at(j, i) = 2 * (i - (image.cols * 0.25));
//				map_y.at(j, i) = 2 * (j - (image.rows * 0.25));
//			}
//			else {
     
//				map_x.at(j, i) = 0;
//				map_y.at(j, i) = 0;
//			}
		}
	}

	remap(image, image_out, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));

	imshow("image", image);
	imshow("image_out", image_out);

	waitKey(0);
	return 0;
}

17.灰度直方图

灰度直方图是表示不同灰度出现频率的图像,为柱状图;是图像的统计学特征。
OpenCV3学习笔记_第40张图片

17.1 直方图均衡化

目的是提高图像的对比度,拉伸图像的灰度值范围。
OpenCV3学习笔记_第41张图片
OpenCV3学习笔记_第42张图片
相关api:

equalizeHist(
InputArray src,//输入图像,必须是8-bit的单通道图像
OutputArray dst// 输出结果
)
#include 
#include 

using namespace cv;
using namespace std;

int main()
{
     
	Mat image, image_gray, image_out;

	image = imread("C://eg//4.jpg");
	if (image.empty()) {
     
		return -1;
	}
	cvtColor(image, image_gray, CV_BGR2GRAY);

	equalizeHist(image_gray, image_out);

	imshow("image", image);
	imshow("image_gray", image_gray);
	imshow("image_out", image_out);

	waitKey(0);
	return 0;
}

OpenCV3学习笔记_第43张图片

17.2 直方图计算

split(// 把多通道图像分为多个单通道图像
const Mat &src, //输入图像
Mat* mvbegin)// 输出的通道图像数组

calcHist(
const Mat* images,//输入图像指针
int images,// 图像数目
const int* channels,// 通道数
InputArray mask,// 输入mask,可选,不用
OutputArray hist,//输出的直方图数据
int dims,// 维数
const int* histsize,// 直方图级数
const float* ranges,// 值域范围
bool uniform,// true by default
bool accumulate// false by defaut
)

你可能感兴趣的:(opencv,计算机视觉)