OpenCV基础(基于Opencv4.4+VS2019)

OpenCV基础(基于Opencv4.4+VS2019)

1、OpenCV介绍

  • OpenCV是计算机视觉开源库,主要算法涉及图像处理和机器学习相关方法。
  • 是Intel公司贡献出来的,俄罗斯工程师贡献大部分C/C++代码
  • 在多数图像处理相关的应用程序中被采用,BSD许可,可以免费应用在商业和研究领域。

2、核心模块

  • core. Core functionality
  • imgproc. Image Processing
  • imgcodecs. Image file reading and writing
  • videoio. Video I/O
  • highgui. High-level GUI
  • video. Video Analysis
  • calib3d. Camera Calibration and 3D Reconstruction
  • features2d. 2D Features Framework
  • objdetect. Object Detection
  • dnn. Deep Neural Network module
  • ml. Machine Learning
  • flann. Clustering and Search in Multi-Dimensional Spaces
  • photo. Computational Photography
  • stitching. Images stitching
  • gapi. Graph API

3、开发环境搭建

步骤

  • 下载VS
  • 下载OpenCV
  • 配置环境变量
  • 在VS中引入头文件、库文件、连接库

安装

基于win10、OpenCV4.4.0、VS2019

Windows

  1. 将OpenCV.exe将其解压到你需要的位置

OpenCV基础(基于Opencv4.4+VS2019)_第1张图片

  1. 打开环境变量配置

OpenCV基础(基于Opencv4.4+VS2019)_第2张图片

  1. 找到Path,点击编辑,新建,复制如下路径粘贴到环境变量中

OpenCV基础(基于Opencv4.4+VS2019)_第3张图片

  1. 创建一个c++控制台项目

OpenCV基础(基于Opencv4.4+VS2019)_第4张图片

  1. 打开属性管理器

OpenCV基础(基于Opencv4.4+VS2019)_第5张图片

  1. 在属性管理器中,右键Debug | x64,选择属性

OpenCV基础(基于Opencv4.4+VS2019)_第6张图片

  1. 选择VC++目录,编辑里面的包含目录库目录

OpenCV基础(基于Opencv4.4+VS2019)_第7张图片
OpenCV基础(基于Opencv4.4+VS2019)_第8张图片
OpenCV基础(基于Opencv4.4+VS2019)_第9张图片
OpenCV基础(基于Opencv4.4+VS2019)_第10张图片

  1. 选择链接器中的输入,编辑附加依赖项

OpenCV基础(基于Opencv4.4+VS2019)_第11张图片

找到build\x64\vc15\lib下的lib文件

OpenCV基础(基于Opencv4.4+VS2019)_第12张图片

复制lib全名,粘贴到附加依赖项中

OpenCV基础(基于Opencv4.4+VS2019)_第13张图片

  1. 将vs中的解决方案平台改为x64

在这里插入图片描述

  1. 创建一个c++文件,在文件中输入以下代码
#include 
#include 

using namespace cv;
int main()
{
	Mat src = imread("D:/2.jpg");

	if (src.empty()) {
		printf("Could not load image.");
		return -1;
	}

	namedWindow("test", WINDOW_AUTOSIZE);
	imshow("test", src);

	waitKey(0);
	return 0;

}
  1. 运行,若能显示出你的图片,证明环境成功

若报错,错误为计算机丢失opencv_world440d.dll,重新启动一下电脑。

若仍未解决,将build\x64\vc15\bin下的同名dll拷贝到C:\Windows\System32下即可。

4、加载、显示、修改、保存图像

* 加载图像(cv::imread)

imread功能是加载图像文件成为一个Mat对象

​ 第一个参数:图像文件名称

​ 第二个参数:加载的图像类型

​ IMREAD_UNCHANGED(<0)表示加载原图,不做任何改变

​ IMREAD_GRAYSCALE(0)表示把原图作为灰度图像加载进来

​ IMREAD_COLOR(>0)表示把原图作为RGB图像加载进来

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

* 显示图像(cv::namedWindows与cv::imshow)

namedWindow功能是创建一个OpenCV窗口,它是由OpenCV自动创建与释放,无需销毁它

常见用法

namedWindow("Window Title", WINDOW_AUTOSIZE)

​ WINDOW_AUTOSIZE会自动根据图像大小,显示窗口大小,不能人为改变窗口大小

​ WINDOW_NORMAL,跟QT集成时使用,允许修改窗口大小

​ imshow根据窗口名称显示图像到指定的窗口上去,

​ 第一个参数:表示窗口名称

​ 第二个参数:表示Mat对象

* 修改图像

cv::cvtColor 修改色彩空间

cv::cvtColor的功能是把图像从一个彩色空间转换到另一个色彩空间,有三个参数

​ 第一个参数表示源图像

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

​ 第三个参数表示源和目标色彩空间,如:COLOR_RGB2HLS、COLOR_BGR2GRAY等

用法
cvtColor(image, gray_image, COLOR_BGR2GRAY);

修改图像的像素值

  • Mat::ptr 使用ptr指针访问像素时访问任意一行像素的首地址,特别方便图像的一行一行的横向访问
  • Mat::at 可以随意访问图像中任何一个像素
用法
// ptr
int nl = image.rows; //行数  
int nc = image.cols * image.channels();
for (int j = 0; j<nl; j++)
{
	uchar* data = image.ptr<uchar>(j);
	for (int i = 0; i<nc; i++)
	{
		data[i] = data[i] / div*div + div / 2;
	}
}


// at
for (int row = 0; row < height; row++) {
    for (int col = 0; col < width; col++) {
        if (nc == 1) { //单通道图像
            int gray = gray_src.at<uchar>(row, col);
            gray_src.at<uchar>(row, col) = 255 - gray;
        }
        else if(nc == 3) { //三通道图像
            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;
        }
    }
}
//上面at段代码的效果 等同于 bitwise_not(src, dst);

* 保存图像(cv::imwrite)

保存图像文件到指定的目录路径

只有8位、16位的PNG、JPG、TIFF文件的格式而且是单通道或者三通道的BGR的图像才可以通过这种方式保存

保存PNG格式的时候可以保存透明通道的图片

可以指定压缩参数

完整实例

#include 
#include 

using namespace cv;
int main()
{
	Mat src = imread("D:/2.jpg");

	if (src.empty()) {
		printf("Could not load image.");
		return -1;
	}

	namedWindow("test", WINDOW_AUTOSIZE);
	imshow("test", src);

	namedWindow("modify", WINDOW_AUTOSIZE);

	Mat output_image;
	cvtColor(src, output_image, COLOR_BGR2GRAY);

	imshow("modify", output_image);

	imwrite("D:/1.png", output_image);

	waitKey(0);
	return 0;

}

5、矩阵的掩膜操作

所谓掩膜其实就是一个矩阵,然后根据这个矩阵重新计算图片中像素的值。

掩膜主要有以下用途:

  • 提取感兴趣区,用预先制作的感兴趣区掩模与待处理图像相乘,得到感兴趣区图像,感兴趣区内图像值保持不变,而区外图像值都为0。
  • 屏蔽作用,用掩模对图像上某些区域作屏蔽,使其不参加处理或不参加处理参数的计算,或仅对屏蔽区作处理或统计。
  • 结构特征提取,用相似性变量或图像匹配方法检测和提取图像中与掩模相似的结构特征。
  • 特殊形状图像的制作。

获取图像像素指针

  • Mat.ptr(int i = 0)获取像素矩阵的指针,索引i表示第几行,从0开始计行数
  • 获得当前行指针const uchar* current = myImage.ptr(row);
  • 获取当前像素点P(row,col)的像素值p(row, col) = current[col]

像素范围处理

这个函数的功能是确保RGB值的范围在0~255之间

  • saturate_cast(-100),返回0
  • saturate_cast(288),返回255
  • saturate_cast(100),返回100

例子:通过掩膜操作实现图像对比度的提高

掩膜操作是指根据掩膜矩阵(掩膜mask,也称作核kernel)重新计算图像中每个像素的值,实现图像对比度提高。

下面这个公式表示用5倍当前像素的值减去该像素上、下、左、右四个像素值和,得到的结果赋值给当前像素。

I(i,j)=5∗I(i,j)−[I(i−1,j)+I(i+1,j)+I(i,j−1)+I(i,j+1)]

// 1_2OpencvMaskOperator.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
    Mat src, dst;
    src = imread("D:/1.jpg");
    if (!src.data)
        return -1;
    
    namedWindow("input image", WINDOW_AUTOSIZE);
    imshow("input image", src);

    /*
    int cols = (src.cols - 1) * src.channels();
    int offsetx = src.channels();
    int rows = src.rows;
    dst = Mat::zeros(src.size(), src.type());

    for (int row = 1; row < (rows - 1); row++) {
        const uchar* previous = src.ptr(row - 1);
        const uchar* current = src.ptr(row);
        const uchar* next = src.ptr(row + 1);
        uchar* output = dst.ptr(row);
        for (int col = offsetx; col < cols; col++) {
            output[col] = saturate_cast(5 * current[col] - (current[col - offsetx] + current[col + offsetx] + previous[col] + next[col]));
        }
    }
    */

    Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
    filter2D(src, dst, src.depth(), kernel);

    namedWindow("output image", WINDOW_AUTOSIZE);
    imshow("output image", dst);


    waitKey(0);
    return 0;

}

6、Mat对象

Mat对象与IplImage对象

  • Mat对象OpenCV2.0之后引进的图像数据结构、自动分配内存、不存在内存泄漏的问题,是面向对象的数据结构。分为头部数据部分。
  • IplImage是从2001年OpenCV发布之后就一直存在,是C语言风格的数据结构,需要开发者主机分配和管理内存。

Mat对象使用

Mat对象构造函数

Mat()

Mat(int rows, int cols, int type)

Mat(Size size, int type)

Mat(int rows, int cols, int type, const Scalar &s)

Mat(Size size, int type, const Scalar &s)

Mat(int ndims, const int * sizes, int type)

Mat(int ndims, const int * sizes, int type, const Scalar &s)

常用方法

void copyTo(Mat mat)

void convertTo(Mat dst, int type)

Mat clone()

int channels()

int depth()

bool empty()

uchar* ptr(i=0)

使用

  • 部分复制:一般情况下只会复制Mat对象的头和指针部分,不会复制数据部分
Mat A = imread(imgFilePath);
Mat B(A);
  • 完全复制:如果想把Mat对象的头部和数据部分一起复制,可以使用clone和copyTo这两个API
Mat B = A.clone();
Mat C;
A.copyTo(C);

Mat定义数组

Mat::create() 创建多维数组

等同于

int sz[3] = {2,2,2};

Mat L(3, sz, CV_8UC1,Scalar::all(0));

Mat::zeros() 创建一个全为0的矩阵

Mat::zeros(src.size(), src.type());

Mat::eye() 创建一个对角为1的矩阵

Mat::eye(2,2,CV_8UC1);

Mat_ 创建小数组

Mat kernel = (Mat_<char>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);

实例

// 1_3OpencvMat.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include 
#include 

using namespace std;
using namespace cv;

int main()
{
	Mat src;
	src = imread("D:/1.jpg");

	if (src.empty()) {
		cout << "could not load image." << endl;
		return -1;
	}

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

	/*Mat dst;
	dst = Mat(src.size(), src.type());
	dst = Scalar(127, 0, 255);*/

	//Mat dst = src.clone();
	Mat dst;
	//src.copyTo(dst);

	namedWindow("output", WINDOW_AUTOSIZE);

	/*cvtColor(src, dst, COLOR_BGR2GRAY);
	
	cout << "innput image channels : " << src.channels() << endl;
	cout << "output image channels : " << dst.channels() << endl;

	imshow("output", dst);

	int cols = dst.cols;
	int  rows = dst.rows;

	cout << "rows : " << rows << "  cols : " << cols << endl;
	const uchar* firstRow = dst.ptr(0);

	cout << "first pixel value : " << firstRow << endl;*/

    /*
    前两个参数 表示行(row)跟列(col)
    第三个参数 CV_8UC3,8表示每个通道占8位、U表示无符号、C表示char类型、3表示通道数目为3
    第四个参数 表示初始化每个像素值是多少,向量长度对应通道数目一致
    */
	/*Mat m(100, 100, CV_8UC3, Scalar(0, 0, 255));
	imshow("output", m);*/

	Mat m1;
	m1.create(src.size(), src.type());
	m1 = Scalar(0, 0, 255);

	imshow("output", m1);

	waitKey(0);
	return 0;
}

7、图像混合

线性混合

g(x) = (1 - α)∫₀(x) +α ∫₁(x)

其中α取值范围为0~1之间

API使用

addWeighted(InputArray src1, double alpha,InputArray src2,double beta, double gamma,OutputArray dst,int dtype=-1)

参数1:输入图像1

参数2:输入图像1的alpha值

参数3:输入图像2

参数4:输入图像2的alpha值

参数5:gamma值

参数6:输出混合图像

注意:两张图像的大小和类型必须一致

实例

// 1_5OpencvImageMixing.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
	Mat src1, src2, dst;
	src1 = imread("D:/2.jpg");
	src2 = imread("D:/3.jpg");

	if (!src1.data) {
		cout << "Could not load image 1." << endl;
		return -1;
	}
	if (!src2.data) {
		cout << "Could not load image 2." << endl;
		return -1;
	}

	double alpha = 0.5;
	if (src1.rows == src2.rows && src1.cols == src2.cols && src1.type() == src2.type()) {
		addWeighted(src1, alpha, src2, (1.0 - alpha), 0.0, dst);

		//multiply(src1, src2, dst, 1.0);

		imshow("src1", src1);
		imshow("src2", src2);

		namedWindow("Blend demo", WINDOW_AUTOSIZE);
		imshow("Blend demo", dst);
	}
	else {
		cout << "Could not blend images, the size of images is not same." << endl;
		return -1;
	}

	waitKey(0);
	return 0;
}

8、调整图像亮度和对比度

理论

图像变换可以看作

  • 像素变换-点操作
  • 邻域操作-区域

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

邻域操作,一般用于图像的卷积、图像整体特征的提取、图像梯度的计算等

公式

g(i, j) = α∫(i, j) + β

其中α > 0, β是增益变量

β调整亮度,α调整对比度

实例

// 1_6OpencvAdjustImageBrightnessAndContrast.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
	Mat src, dst;

	src = imread("D:/1.jpg");
	if (!src.data) {
		cout << "Could not load image. " << endl;
		return -1;
	}

	char input_win[] = "input image";
	namedWindow(input_win, WINDOW_AUTOSIZE);
	imshow(input_win, src);

	int height = src.rows;
	int width = src.cols;
	dst = Mat::zeros(src.size(), src.type());

	float alpha = 1.2;
	float beta = 30;

	//使用Vec3f时需要先将图像转换成对应的类型
	/*Mat m1;
	src.convertTo(m1, CV_32F);*/

	for (int row = 0; row < height; row++) {
		for (int col = 0; col < width; col++) {
			if (src.channels() == 3) {
				float b = src.at<Vec3b>(row, col)[0];
				float g = src.at<Vec3b>(row, col)[1];
				float r = src.at<Vec3b>(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) {
				float v = src.at<uchar>(row, col);

				dst.at<Vec3b>(row, col) = saturate_cast<uchar>(v * alpha + beta);
			}
		}
	}

	char output_title[] = "contrast and brightness change demo.";

	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(output_title, dst);

	waitKey(0);
	return 0;
}

9、绘制形状与文字

使用cv::Point与cv::Scalar

  • Point表示2D平面上的一个点x,y
Point p;
p.x = 10;
p.y = 8;
//or
p = Point(10,8);
  • Scalar表示四个元素的向量

Scalar(b,g,r); //表示BGR三个通道

绘制线、矩形、圆、椭圆等基本形状

  • 线 cv::line(LINE_4\LINE_8\LINE_AA) LINE_AA是反锯齿,看起来会比较平滑
  • 椭圆 cv::ellipse
  • 矩形 cv::rectangle
  • 圆 cv::circle
  • 填充 cv::fillPoly

实例

// 1_7OpencvDrawShapesAndText.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include 
#include 

using namespace std;
using namespace cv;

Mat bgImage;
const char* drawdemo_win = "draw shapes and text demo";
void MyLine();
void MyRectangle();
void MyEllipse();
void MyCircle();
void MyPolygon();
void RandomLineDemo();

int main()
{
	bgImage = imread("D:/1.jpg");
	if (!bgImage.data) {
		cout << "Could not load image." << endl;
		return -1;
	}

	/*MyLine();
	MyRectangle();
	MyEllipse();
	MyCircle();
	MyPolygon();

	putText(bgImage, "Hello OpenCV", Point(200, 300), FONT_ITALIC, 1.0, Scalar(255, 255, 200), 3, 8);

	RandomLineDemo();*/
	HeartDemo();

	namedWindow(drawdemo_win, WINDOW_AUTOSIZE);
	imshow(drawdemo_win, bgImage);

	waitKey(0);
	return 0;

}

void MyLine() {
	Point p1 = Point(20, 30);
	Point p2 = Point(200, 200);
	Scalar color = Scalar(0, 0, 255);
	line(bgImage, p1, p2, color, 1, LINE_8);
	//LINE_AA反锯齿
}

void MyRectangle() {
	Rect rect = Rect(200, 100, 100, 200);
	Scalar color = Scalar(255, 0, 0);
	rectangle(bgImage, rect, color, 2, LINE_8);
}

void MyEllipse() {
	Scalar color = Scalar(0, 255, 0);
	ellipse(bgImage, Point(bgImage.cols / 2, bgImage.rows / 2), Size(bgImage.cols / 4, bgImage.rows / 8), 90, 0, 360, color, 2, LINE_8);
}

void MyCircle(){
	Scalar color = Scalar(0, 255, 255);
	Point center = Point(bgImage.cols / 2, bgImage.rows / 2);
	circle(bgImage, center, 150, color, 2, LINE_8);
}

void MyPolygon() {
	Point pts[1][5];
	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(100, 100);

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

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

void RandomLineDemo() {
	RNG rng(12345);
	Point p1, p2;
	Mat bg = Mat::zeros(bgImage.size(), bgImage.type());
	namedWindow("random", WINDOW_AUTOSIZE);

	for (int i = 0; i < 100000; i++) {
		p1.x = rng.uniform(0, bgImage.cols);
		p2.x = rng.uniform(0, bgImage.cols);
		p1.y = rng.uniform(0, bgImage.rows);
		p2.y = rng.uniform(0, bgImage.rows);

		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));

		if (waitKey(50) > 0)
			break;

		line(bg, p1, p2, color, 1, LINE_8);

		imshow("random", bg);
	}
}

10、模糊图像

原理

  • Smooth/Blur是图像处理中最简单和常用的操作之一

  • 给图像预处理时减低噪声

  • Smooth/Blur操作卷积公式

    ​ g(i, j) = ∑∫(i+k, j+l) h(k,l)

  • 通常这些卷积算子计算都是线性操作,所以又叫线性滤波

模糊原理

* 归一化盒子滤波(均值滤波)

在这里插入图片描述

特点:均值滤波本身存在着固有的缺陷,即它不能很好地保护图像细节,在图像去噪的同时也破坏了图像的细节部分,从而使图像变得模糊,不能很好地去除噪声点。

* 高斯滤波

OpenCV基础(基于Opencv4.4+VS2019)_第14张图片

特点:图像大多数噪声均属于高斯噪声,因此高斯滤波器应用也较广泛。高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像去噪。

* 中值滤波

在这里插入图片描述

特点:中值滤波对脉冲噪声(椒盐噪声)有良好的滤除作用,特别是在滤除噪声的同时,能够保护信号的边缘,使之不被模糊。这些优良特性是线性滤波方法所不具有的。

* 双边滤波

OpenCV基础(基于Opencv4.4+VS2019)_第15张图片

OpenCV基础(基于Opencv4.4+VS2019)_第16张图片

OpenCV基础(基于Opencv4.4+VS2019)_第17张图片

特点:双边滤波器顾名思义比高斯滤波多了一个高斯方差在这里插入图片描述
,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会太多影响到边缘上的像素值,这样就保证了边缘附近像素值的保存。但是由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净的滤掉,只能够对于低频信息进行较好的滤波。双边滤波常用于美颜磨皮等应用中。

相关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),x,y必须是正数且是奇数

高斯模糊部分克服了边缘像素信息丢失缺陷,但无法完全避免,因为没有考虑像素值的不同

* 中值模糊

medianBlur(Mat src, Mat dest,int ksize)

ksize必须是大于1且必须是奇数

* 双边模糊

bilateralFilter(Mat src, Mat dest, d = 15, 150, 3)

15为计算的半径,半径之内的像素都会被纳入计算,如果为-1,则会根据sigma space 参数取值

150为sigma color 决定多少差值之内的像素会被计算

3 sigma space 如果d的值大于0则声明无效,否则根据它来计算d值

实例

// 1_8OpencvBlurredImage.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
	Mat src, dst;
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

	char input_title[] = "input image";
	char output_title[] = "blur image";

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);

	imshow(input_title, src);
	
    //均值模糊
	//blur(src, dst, Size(11, 11), Point(-1, 1));

	Mat gblur;
	//高斯模糊
	GaussianBlur(src, gblur, Size(11, 11), 11, 11);
	imshow("gaussian blur", gblur);
    
    //中值滤波可以去除椒盐噪声
	//medianBlur(src, dst, 3);

	//双边模糊可以美化
	bilateralFilter(src, dst, 15, 150, 3);
    
	imshow(output_title, dst);

	waitKey(0);
	return 0;
}

11、形态学操作

原理

形态学操作(morphology operators)

  • 图像形态学操作-基于形状的一系列图像处理操作的合集,主要是基于集合论基础上的形态学数据
  • 形态学有四个基本操作:腐蚀、膨胀、开、闭、形态学梯度、顶帽、黑帽
  • 一般用于消除噪声、边界提取、区域填充、连通分量提取、凸壳、细化、粗化等;分割出独立的图像元素,或者图像中相邻的元素;求取图像中明显的极大值区域和极小值区域;求取图像梯度

膨胀 erode

跟卷积操作类似, 假设有图像A和结构元素B,结构元素B在A上面移动,其中B定义其中心为描点,计算B覆盖下A的最大像素值用来替换描点的像素,其中B作为结构体可以是任意形状。

作用是在结构元素的约束下将与目标区域相接触的背景合并到该目标物中,使目标边界向外部扩张,物体的面积增大了相应数量的点。

腐蚀 dilate

腐蚀跟膨胀操作的做成类似,唯一不同的是以最小值替换描点重叠下图像的像素值

开 open

先腐蚀后膨胀

可以去掉小的对象,在纤细点处分离物体和平滑较大物体的边界而有不明显改变其面积和形状。假设对象是前景色,背景是黑色

闭 close

先膨胀后腐蚀

可以填充小的洞,将断开的邻近目标连接,在不明显改变物体面积和形状的情况下平滑其边界。假设对象是前景色,背景是黑色

形态学梯度 Morphological Gradient

  • 基本梯度

    ​ 膨胀减去腐蚀之后的得到的插值图像

  • 内部梯度

    ​ 原图像减去腐蚀之后的得到的插值图像

  • 外部梯度

    ​ 图像膨胀之后再减去原图像之后的得到的插值图像

  • 方向梯度

    ​ 使用X方向与Y方向的直线作为结构元素之后得到的图像梯度,X的结构元素分别膨胀与腐蚀得到图像之后求差值得到称为X方向梯度,用Y方向直线做结构分别膨胀与腐蚀之后得到图像求差值之后成为Y方向梯度。

顶帽 top hat

原图像与开操作之间的差值图像。开运算放大了裂缝或者局部低亮度的区域,所以,从原图中减去开运算后的图,得到的结果突出了比原图轮廓周围的区域更明亮的区域,这个操作与选择的核的大小有关。TopHat运算一般用来分离比邻近点亮一些的斑块,可以使用这个运算提取背景。

黑帽 black hat

闭操作图像与原图的差值图像。黑帽运算的结果突出了比原图轮廓周围区域更暗的区域,所以黑帽运算用来分离比邻近点暗一些的斑块。

相关API

getStructuringElement(int shape, Size ksize, Point anchor)

用于获取结构元素

第一个参数 MORPH_RECT\MORPH_CROSS\MORPH_ELLIPSE

第二个参数 大小,要求是奇数

第三个参数 描点,默认是Point(-1,-1),意思是中心像素

dilate( const Mat& src, Mat& dst, const Mat& element);

腐蚀

第一个参数 原图像

第二个参数 目标图像

第三个参数 腐蚀操作的内核,如果不指定,默认为一个简单的3X3矩阵,可以使用getStructuringElement()

第四个参数 默认为Point(-1,-1), 内核中心点

第五个参数 腐蚀次数,默认为1

第六个参数 推断边缘类型,默认为BORDER_DEFAULT

第七个参数 边缘值

erode( const Mat& src, Mat& dst, const Mat& element)

膨胀

参数与腐蚀一致

morphologyEx(src, dst, MORPH_BLACKHAT, kernel)

第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。图像位深应该为以下五种之一:CV_8U, CV_16U,CV_16S, CV_32F 或CV_64F。
第二个参数,OutputArray类型的dst,即目标图像,函数的输出参数,需要和源图片有一样的尺寸和类型。
第三个参数,int类型的op,表示形态学运算的类型,可以是如下之一的标识符:
MORPH_OPEN – 开运算(Opening operation):先腐蚀后膨胀,去掉小对象
MORPH_CLOSE – 闭运算(Closing operation):先膨胀后腐蚀,可以填充一些小的空洞(fill hole)
MORPH_GRADIENT - 形态学梯度(Morphological gradient):膨胀后的图减去腐蚀后的图(设置恰当的参数可以得到目标的大致边缘)
MORPH_TOPHAT - “顶帽”(“Top hat”):原图像与开操作之间的差值图像(可以用来观察开运算除去了哪些小目标)
MORPH_BLACKHAT - “黑帽”(“Black hat“):闭操作图像与源图像的差值图像(可以观察闭运算的效果)
第四个参数,结构元素即形态学运算的内核
第五个参数,Point类型的anchor,锚的位置,其有默认值( - 1, - 1),表示锚位于中心。
第六个参数,int类型的iterations,迭代使用函数的次数,默认值为1。
第七个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。注意它有默认值BORDER_ CONSTANT。
第八个参数,const Scalar&类型的borderValue,当边界为常数时的边界值,有默认值morphologyDefaultBorderValue(),一般我们不用去管他。需要用到它时,可以看官方文档

adaptiveThreshold(InputArray src, OutputArray dst, double maxValue, int adaptiveMethod, int thresholdType, int bolckSize, double C)

第一个参数,InputArray类型的src,输入图像,填单通道,单8位浮点类型Mat即可。
第二个参数,函数运算后的结果存放在这。即为输出图像(与输入图像同样的尺寸和类型)。
第三个参数,预设满足条件的最大值。
第四个参数,指定自适应阈值算法。可选择ADAPTIVE_THRESH_MEAN_C 或 ADAPTIVE_THRESH_GAUSSIAN_C两种。

​ ADAPTIVE_THRESH_MEAN_C,为局部邻域块的平均值,该算法是先求出块中的均值,再减去常数C。

​ ADAPTIVE_THRESH_GAUSSIAN_C,为局部邻域块的高斯加权和。该算法是在区域中(x, y)周围的像素根据高斯函数按照他们离中心点的距离进行加权计算,再减去常数C。

第五个参数,指定阈值类型。可选择THRESH_BINARY或者THRESH_BINARY_INV两种。(即二进制阈值或反二进制阈值)。
第六个参数,表示邻域块大小,用来计算区域阈值,一般选择为3、5、7…等。
第七个参数,参数C表示与算法有关的参数,它是一个从均值或加权均值提取的常数,可以是负数。(具体见下面的解释)。

createTrackbar(const String & trackbarname, const String windowsName, int * value, int count, Trackbarcallback func, void * userdata = 0);

第一个参数 trackbar的名称

第二个参数 需要显示trackbar的窗体名称

第三个参数 trackbar当前的值

第四个参数 trackbar的量程

第五个参数 需要调用的回调函数

实例

提取水平与垂直线

#include 
#include 

using namespace std;
using namespace cv;

int main()
{
	Mat src, dst;

    //输入图像彩色图像imread
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

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

	Mat gray_src;
    //转为灰度图像
	cvtColor(src, gray_src, COLOR_BGR2GRAY);

	Mat binary_src;
    //转为二值图像
	adaptiveThreshold(~gray_src, binary_src, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);

    //定义结构元素
	Mat hline = getStructuringElement(MORPH_RECT, Size(src.cols / 16, 1), Point(-1, -1));
	Mat vline = getStructuringElement(MORPH_RECT, Size(1, src.rows / 16), Point(-1, -1));

	Mat temp;
    //开操作提取水平与垂直线,下面注释代码片段等同于下面未注释的代码
	/*erode(binary_src, temp, hline);
	dilate(temp, dst, hline);*/
	morphologyEx(binary_src, dst, MORPH_OPEN, vline);
	bitwise_not(dst, dst);
	blur(dst, dst, Size(3, 3), Point(-1, -1));

	char output_title[] = "result image";
	namedWindow(output_title, WINDOW_AUTOSIZE);

	imshow(output_title, dst);

	waitKey(0);
	return 0;
}

11、图像金字塔-上采样与下采样

概念

图像金字塔是以多个分辨率来表示图像的一种有效且概念简单的结构,一个图像金字塔由一系列的图像组成,最底下一张是图像尺寸最大,最上方的图像尺寸最小,从空间上从上向下看,就像一个古代的金字塔。

高斯金字塔

  • 高斯金字塔是从底向上,逐层降采样得到。
  • 降采样之后图像大小是原图的MxN的M/2xN/2
  • 高斯金字塔的生成过程分为两步:
    • 对当前层进行高斯模糊
    • 删除当前层的偶数行与列

高斯不同(Difference of Gaussian/DOG)

  • 就是把同一张图像在不同的参数下做高斯模糊之后的结果相减, 得到的输出图像。
  • 高斯不同是图像的内在特征,在灰度图像增强、角点检测中经常用到。

相关API

pyrUp(Mat src, Mat dest, Size(src.cols * 2, src.rows * 2))

上采样:生成的图像是原图在宽与高各放大两倍

pyrDown(Mat src, Mat dest, Size(src.cols / 2, src.rows / 2))

降采样:生成的图像是原图在宽与高各缩小两倍

normalize(InputArry src,InputOutputArray dst,double alpha=1,double beta=0,int norm_type=NORM_L2,int dtype=-1,InputArray mark=noArry())

归一化数据。该函数分为范围归一化与数据值归一化。

第一个参数,输入数组;

第二个参数,输出数组,数组的大小和原数组一致;

第三个参数,1,用来规范值,2.规范范围,并且是下限;

第四个参数,只用来规范范围并且是上限;

第五个参数,归一化选择的数学公式类型;

​ 有NORM_INF, NORM_MINMAX,NORM_L1和NORM_L2四种。
​ 1、在 NORM_MINMAX 模式下,alpha表示归一化后的最小值,beta表示归一化后的最大值。
​ 2、在NORM_L1、NORM_L2、NORM_INF 模式下,alpha表示执行相应归一化后矩阵的范数值,beta不使 用。
​ 3、稀疏矩阵归一化仅支持非零像素

第六个参数,当为负,输出在大小深度通道数都等于输入,当为正,输出只在深度与输如不同,不同的地方游dtype决定;

第七个参数,掩码。选择感兴趣区域,选定后只能对该区域进行操作。

实例

#include 
#include 

using namespace std;
using namespace cv;

int main()
{
	Mat src, dst;

	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

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

	//上采样
	//pyrUp(src, dst, Size(src.cols * 2, src.rows * 2));

	//降采样
	pyrDown(src, dst, Size(src.cols / 2, src.rows / 2));

	char output_title[] = "result image";
	namedWindow(output_title, WINDOW_AUTOSIZE);

	imshow(output_title, dst);

	Mat gray_src, g1, g2, dogImg;
	cvtColor(src, gray_src, COLOR_BGR2GRAY);
	GaussianBlur(gray_src, 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);
	imshow("DOG Image", dogImg);

	waitKey(0);
	return 0;
}

12、基本阈值操作

概念

阈值类型-阈值二值化(threshold binary)

像素值大于阈值,=255
像素值小于阈值,=0

阈值类型-反阈值二值化(threshold binary inverted)

像素值小于阈值,=255
像素值大于阈值,=0

阈值类型-截断(truncate)

像素值大于阈值,=阈值
像素值小于阈值,保持不变

阈值类型-阈值取零(threshold to zero)

像素大于等于阈值,保持不变
像素小于阈值,=0

阈值类型-阈值反取零(threshold to zero inverted)

像素值小于阈值,=0
像素值大于阈值,保持不变

相关API

threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)

第一个参数 源图像

第二个参数 输出图像

第三个参数 阈值

第四个参数 输出图像中的最大值

第五个参数 阈值类型

编号 阈值类型枚举 注意
1 THRESH_BINARY
2 THRESH_BINARY_INV
3 THRESH_TRUNC
4 THRESH_TOZERO
5 THRESH_TOZERO_INV
6 THRESH_MASK 不支持
7 THRESH_OTSU 不支持32位
8 THRESH_TRIANGLE 不支持32位

实例

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst, gray_src;
int threshold_value = 127;
int threshold_max = 255;
int type_value = 2;
int type_max = 4;
char output_title[] = "result image";
void ThresHold_Demo(int, void*);

int main()
{
	src = imread("C:/Users/ThinkPad/Desktop/1.jpg");
	if (!src.data)
		return -1;

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

	createTrackbar("Threshold Value : ", output_title, &threshold_value, threshold_max, ThresHold_Demo);
	createTrackbar("Type Value : ", output_title, &type_value, type_max, ThresHold_Demo);
	ThresHold_Demo(0, 0);

	waitKey(0);
	return 0;
}

void ThresHold_Demo(int, void*) {
	cvtColor(src, gray_src, COLOR_BGR2GRAY);
	threshold(gray_src, dst, threshold_value, threshold_max, type_value);
	imshow(output_title, dst);
}

13、处理边缘

概念

卷积

是图像处理中一个操作,是kernel在图像的每个像素上的操作。

  • kernel本质上是一个固定大小的矩阵数组,其中心点称为描点(anchor point)
  • 把kernel放到像素数组之上,求描点周围覆盖的像素乘积之和(包括描点),用来替换描点覆盖下像素点值称为卷积处理。

Robert算子

任意一对互相垂直方向上的差分可以看成求梯度的近似方法

优缺点:

边缘定位精度较高,对于陡峭边缘且噪声低的图像效果较好,但没有进行平滑处理,没有抑制噪声能力

应用

具有陡峭的低噪声的图像处理效果较好

robert X方向

1 0
0 -1

robert Y方向

0 1
-1 0

Sobel算子

用来计算图像灰度的近似梯度,进行了平滑处理,对噪声具有一定的抑制能力,但容易出现多像素宽度。

应用

检测方法对灰度渐变和噪声较多的图像处理效果较好

sobel X方向

-1 0 1
-2 0 2
-1 0 1

sobel Y方向

-1 -2 -1
0 0 0
1 2 1

Laplacian算子

对噪声较为敏感,使噪声能力成分得到加强,容易丢失部分边缘方向信息,造成一些不连续的检测边缘,同时抗噪声能力较差。所以很少用该算子检测边缘

应用

用来判断边缘像素视为与图像的明区还是暗区。

0 -1 0
-1 4 -1
0 -1 0

Canny算子

最优化思想的边缘检测算子,同时采用高斯函数对图像进行平滑处理,但会造成将高频边缘平滑掉,造成边缘丢失,采用双阈值算法检测和链接边缘。

相关API

filter2D(src, ddepth, kernel, dst, anchor, delta, borderType)

利用内核实现对图像的卷积运算

第一个参数,表示输入图像

第二个参数,表示输出的边缘图,其大小和通道数与输入图像相同

第三个参数,表示目标图像所需的深度

第四个参数,表示卷积核,一个单通道浮点型矩阵

第五个参数,表示内核的基准点,其默认值为(-1,-1),位于中心位置

第六个参数,表示在储存目标图像前可选的添加到像素的值,默认值为0

第七个参数,表示边框模式

Sobel( InputArray src, OutputArray dst, int ddepth,int dx, int dy, int ksize = 3,double scale = 1, double delta = 0,int borderType = BORDER_DEFAULT );

第一个参数,8位的输入图像

第二个参数,输出图像

第三个参数,输出图像深度

第四个参数,x方向的几阶导数

第五个参数,y方向的几阶导数

第六个参数,kernel大小,必须是奇数

第七个参数,scale因子,是sobel过滤内核因子的缩放因子

第八个参数,delta,sobel边缘检测后在影像中加上delta

第九个参数,边界类型

Scharr(InputArray src, OutputArray dst,int ddepth ,int dx, int dy,double scale = 1,double delta = 0,int borderType=BORDER_DEFAULT)

第一个参数,8位的输入图像。

第二个参数,输出图像。

第三个参数,输出图像深度。

第四个参数,x方向上的差分阶数。

第五个参数,y方向上的差分阶数。

第六个参数,计算导数值时可选的缩放因子,默认值1,表示默认情况下没用应用缩放。

第七个参数,表示在结果存入输出图像之前可选的delta值,默认值0。

第八个参数,边界模式。

Laplacian( InputArray src, OutputArray dst, int ddepth,int dx, int dy, int ksize = 3,double scale = 1, double delta = 0,int borderType = BORDER_DEFAULT );

参数含义同sobel

Canny( InputArray image, OutputArray edges, double threshold1, double threshold2,int apertureSize = 3, bool L2gradient = false );

第一个参数,8位的输入图像

第二个参数,输出图像

第三个参数,表示设置的低阈值

第四个参数,表示设置的高阈值,一般设定位低阈值的3倍

第五个参数,sobel算子的大小,必须是奇数

第六个参数,如果为真,则使用更精确的L2范数进行计算(即两个方向的倒数的平方和再开方),否则使用L1范数(直接将两个方向导数的绝对值相加)

copyMakeBorder(Mat src, Mat dst, int top, int bottom, int left, int right, int borderType, Scalar value)

给图像添加边缘API

第一个参数,输入图像

第二个参数,添加边缘图像

第三/四/五/六个参数,边缘长度,一般上下左右都取相同值

第五个参数,边缘类型

第六个参数,颜色

实例

实例一:自定义线性滤波

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst;
char output_title[] = "result image";

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

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

	//Robert X direction
	//Mat kernel = (Mat_(2, 2) << 1, 0, 0, -1);
	//Robert Y direction
	//Mat kernel = (Mat_(2, 2) << 0, 1, -1, 0);

	//Sobel X direction
	//Mat kernel = (Mat_(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
	//Sobel Y direction
	//Mat kernel = (Mat_(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);

	//Laplace 
	//Mat kernel = (Mat_(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);

	//filter2D(src, dst, -1, kernel, Point(-1, -1), 0.0);

	int c = 0;
	int index = 0;
	int ksize = 0;

	while (true) {
		c = waitKey(500);
		if ((char)c == 27) //ESC
			break;

		ksize = 4 + (index % 5) * 2 + 1;
		Mat kernel = Mat::ones(Size(ksize, ksize), CV_32F / (float)(ksize * ksize));

		filter2D(src, dst, -1, kernel, Point(-1, -1));
		index++;
		imshow(output_title, dst);
	}

	return 0;
}

实例二:给图像添加边缘

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst;
char output_title[] = "result image";

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

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

	int top = (int)(0.05 * src.rows);
	int bottom = (int)(0.05 * src.rows);
	int left = (int)(0.05 * src.cols);
	int right = (int)(0.05 * src.cols);

	RNG rng(12345);
	int borderType = BORDER_DEFAULT;

	int c = 0;

	while (true) {
		c = waitKey(500);
		//ESC
		if ((char)c == 27)
			break;
		if ((char)c == 'r')
			borderType = BORDER_REPLICATE;
		else if ((char)c == 'w')
			borderType = BORDER_WRAP;
		else if ((char)c == 'c')
			borderType = BORDER_CONSTANT;

		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		copyMakeBorder(src, dst, top, bottom, left, right, borderType, color);
		imshow(output_title, dst);
	}

	waitKey(0);
	return 0;
}

实例三:sobel算子

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst;
char output_title[] = "result image";

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

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

	GaussianBlur(src, dst, Size(3, 3), 0, 0);
	Mat gray_src;
	cvtColor(dst, gray_src, COLOR_BGR2GRAY);
	imshow("gray image", gray_src);

	Mat xgrad, ygrad;
	/*Sobel(gray_src, xgrad, CV_16S, 1, 0, 3);
	Sobel(gray_src, ygrad, CV_16S, 0, 1, 3);*/
	Scharr(gray_src, xgrad, CV_16S, 1, 0);
	Scharr(gray_src, ygrad, CV_16S, 0, 1);
	//用于实现对整个图像数组中的每一个元素进行图像增强等相关操作
	convertScaleAbs(xgrad, xgrad);
	convertScaleAbs(ygrad, ygrad);
	imshow("xgrad", xgrad);
	imshow("ygrad", ygrad);

	/*Mat xygrad;
	addWeighted(xgrad, 0.5, ygrad, 0.5, 0, xygrad);
	imshow("Final image", xygrad);*/

	Mat xygrad = Mat(xgrad.size(), xgrad.type());
	int width = xgrad.cols;
	int height = xgrad.rows;

	for (int row = 0; row < height; row++) {
		for (int col = 0; col < width; col++) {
			int xg = xgrad.at<char>(row, col);
			int yg = ygrad.at<char>(row, col);
			int xy = xg + yg;
			xygrad.at<char>(row, col) = saturate_cast<uchar>(xy);
		}
	}
	imshow(output_title, xygrad);

	waitKey(0);
	return 0;
}

实例四:lalacian算子

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst;
char input_title[] = "input image";
char output_title[] = "result image";

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	Mat gray_src, edge_image;
	GaussianBlur(src, dst, Size(3, 3), 0, 0);
	cvtColor(dst, gray_src, COLOR_BGR2GRAY);

	Laplacian(gray_src, edge_image, CV_16S, 3);
	convertScaleAbs(edge_image, edge_image);

	threshold(edge_image, edge_image, 0, 255, THRESH_OTSU | THRESH_BINARY);

	imshow(output_title, edge_image);

	waitKey(0);
	return 0;
}

实例五:Canny算法

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst, gray_src;
char input_title[] = "input image";
char output_title[] = "result image";

int t1_value = 50;
int max_value = 255;

void Canny_Demo(int, void*);

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	//Canny算法步骤
	//1、高斯模糊 GaussianBlur
	//2、灰度转换 cvtColor
	//3、计算梯度 Sobel/Scharr
	//4、非最大信号抑制
	//5、高低阈值输出二值图像

	cvtColor(src, gray_src, COLOR_BGR2GRAY);

	createTrackbar("Threshold Value : ", output_title, &t1_value, max_value, Canny_Demo);
	Canny_Demo(0, 0);

	waitKey(0);
	return 0;
}


void Canny_Demo(int, void*) {
	Mat edge_output;
	blur(gray_src, gray_src, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
	Canny(gray_src, edge_output, t1_value, (t1_value * 2), 3, false);

	dst.create(src.size(), src.type());

	src.copyTo(dst, edge_output);

	imshow(output_title, dst);
}

霍夫变换

概念

霍夫直线变换

  • 用来做直线检测
  • 前提条件:边缘检测已经完成
  • 平面空间到极坐标空间转换

优点:

可以更好的减少噪声干扰

霍夫圆检测

圆周上任意三点所确定的圆,经Hough变换后在三维参数空间应对应一点。遍历圆周上所有点,任意三个点所确定的候选圆进行投票。遍历结束后,得票数最高点(理论上圆周上任意三点确定的圆在Hough变换后均对应三维参数空间中的同一点)所确定的圆即为该圆周上,绝大多数点所确定的圆(以下称为当选圆),即绝大多数点均在该当选圆的圆周上,以此确定该圆。

  • 因为霍夫圆检测对噪声比较敏感,所以首先要对图像做中值滤波。
  • 基于效率考虑,Opencv中实现的霍夫变换圆检测是基于图像梯度的实现,分为两步:
    1. 检测边缘,发现可能的圆心
    2. 基于第一步的基础上从候选圆心开始计算最佳半径大小

相关API

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 )

第一个参数,输入图像,必须是8位的灰度图像

第二个参数,输出的极坐标来表示直线

第三个参数,生成极坐标时的像素扫描步长

第四个参数,生成极坐标时的角度步长,一般取值CV_PI/180

第五个参数,阈值,只有获得足够焦点的极坐标才被看作是直线

第六个参数,double类型的srn,有默认值0。对于多尺度的霍夫变换,这是第三个参数进步尺寸rho的除数距离。粗略的累加器进步尺寸直接是第三个参数rho,而精确的累加器进步尺寸为rho/srn。

第七个参数,double类型的stn,有默认值0,对于多尺度霍夫变换,srn表示第四个参数进步尺寸的单位角度theta的除数距离。且如果srn和stn同时为0,就表示使用经典的霍夫变换。否则,这两个参数应该都为正数。

第八个参数,表示角度扫描范围0~180之间

第九个参数

HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0 )

此函数在HoughLines的基础上末尾加了一个代表Probabilistic(概率)的P,表明它可以采用累计概率霍夫变换(PPHT)来找出二值图像中的直线。

第一个参数,InputArray类型的image,输入图像,即源图像,需为8位的单通道二进制图像,可以将任意的源图载入进来后由函数修改成此格式后,再填在这里。

第二个参数,InputArray类型的lines,经过调用HoughLinesP函数后后存储了检测到的线条的输出矢量,每一条线由具有四个元素的矢量(x_1,y_1, x_2, y_2) 表示,其中,(x_1, y_1)和(x_2, y_2) 是是每个检测到的线段的结束点。

第三个参数,double类型的rho,以像素为单位的距离精度。另一种形容方式是直线搜索时的进步尺寸的单位半径。

第四个参数,double类型的theta,以弧度为单位的角度精度。另一种形容方式是直线搜索时的进步尺寸的单位角度。

第五个参数,int类型的threshold,累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才可以被检测通过并返回到结果中。

第六个参数,double类型的minLineLength,有默认值0,表示最低线段的长度,比这个设定参数短的线段就不能被显现出来。

第七个参数,double类型的maxLineGap,有默认值0,允许将同一行点与点之间连接起来的最大的距离。

HoughCircles( InputArray image, OutputArray circles, Int method, Double dp,Double mindist,Double param1, Double param2, Int minradius,Int maxradius)

第一个参数,输入图像,必须是8位的单通道灰度图像

第二个参数,用于存储类型是vector****;存储着圆心X,Y,和半径R

第三个参数,方法,一般是梯度,CV_HOUGH_GRADIENT

第四个参数,检测内侧圆心的累加器图像的分辨率于输入图像之比的倒数,如dp=1,累加器和输入图像具有相同的分辨率,如果dp=2,累计器便有输入图像一半那么大的宽度和高度,一般不需要改变就是1

第五个参数,两个圆心相聚的最小距离可以认为是两个圆

第六个参数,Canny边缘检测的高阈值

第七个参数,中心点累加阈值-候选圆心,它表示在检测阶段圆心的累加器阈值,它越小,就越可以检测到更多根本不存在的圆,而它越大的话,能通过检测的圆就更加接近完美的圆形了

第八个参数,最小半径

第九个参数,最大半径

实例

实例一:霍夫直线变换

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst, gray_src;
char input_title[] = "input image";
char output_title[] = "result image";

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	//提取边缘
	Canny(src, gray_src, 100, 200);
	cvtColor(gray_src, dst, COLOR_GRAY2BGR);
	imshow("edge image", gray_src);

	vector<Vec4f> plines;
	HoughLinesP(gray_src, 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(dst, Point(hline[0], hline[1]), Point(hline[2], hline[3]), color, 3, LINE_AA);
	}

	imshow(output_title, dst);

	waitKey(0);
	return 0;
}

实例二:霍夫圆检测

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst, gray_src;
char input_title[] = "input image";
char output_title[] = "result image";

int main()
{
	src = imread("C:/Users/ThinkPad/Desktop/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	//中值滤波
	Mat m;
	medianBlur(src, m, 3);
	cvtColor(m, m, COLOR_BGR2GRAY);

	//霍夫圆检测
	vector<Vec3f> pcircles;
	HoughCircles(m, pcircles, HOUGH_GRADIENT, 1, 10, 100, 30, 5, 50);

	src.copyTo(dst);
	for (size_t i = 0; i < pcircles.size(); i++) {
		Vec3f cc = pcircles[i];
		circle(dst, Point(cc[0], cc[1]), cc[2], Scalar(0, 0, 255), 2, LINE_AA);
		circle(dst, Point(cc[0], cc[1]), cc[2], Scalar(100, 0, 200), 2, LINE_AA);
	}

	imshow(output_title, dst);

	waitKey(0);
	return 0;
}

14、像素重映射

概念

把输入图像中各个像素按照一定的规则映射到另外一张图像的对应位置上去,形成一张新的图像。

相关API

remap (InputArray src,OutputArray dst,InputArray map1,InputArray map2,int interpolation,int borderMode = BORDER_CONSTANT,const Scalar borderValue = Scalar())

第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位或者浮点型图像。

第二个参数,OutputArray类型的dst,函数调用后的运算结果存在这里,即这个参数用于存放函数调用后的输出结果,需和源图片有一样的尺寸和类型。

第三个参数,InputArray类型的map1,它有两种可能的表示对象。
(1).表示点(x,y)的第一个映射。
(2).表示CV_16SC2 , CV_32FC1 或CV_32FC2类型的X值。

第四个参数,InputArray类型的map2,同样,它也有两种可能的表示对象,而且他是根据map1来确定表示哪种对象。
(1).若map1表示点(x,y)时。这个参数不代表任何值。
(2).表示CV_16UC1 , CV_32FC1类型的Y值(第二个值)。

第五个参数,int类型的interpolation,插值方式,之前的resize( )函数中有讲到,需要注意,resize(
)函数中提到的INTER_AREA插值方式在这里是不支持的,所以可选的插值方式如下:
(1)INTER_NEAREST——最近邻插值
(2)INTER_LINEAR——双线性插值(默认)
(3)INTER_CUBIC——双三样条插值(逾4×4像素邻域内的双三次插值)
(4)INTER_LANCZOS4——lanczos插值(逾8×8像素邻域的Lanczos插值)

第六个参数,int类型的borderMode,边界模式,有默认值BORDER_CONSTANT,表示目标图像中“离群点(outliers)”的像素值不会被此函数修改。

第七个参数,const Scalar&类型的borderValue,当有常数边界时使用的值,其有默认值Scalar( ),即默认值为0。

实例

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst, map_x, map_y;
char input_title[] = "input image";
char output_title[] = "result image";
int index = 0;

void update_map();

int main()
{
	src = imread("C:/Users/ThinkPad/Desktop/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	map_x.create(src.size(), CV_32FC1);
	map_y.create(src.size(), CV_32FC1);
	
	int c = 0;
	while (true) {
		c = waitKey(500);
		if ((char)c == 27) {
			break;
		}
		index = c % 4;

		update_map();

		remap(src, dst, map_x, map_y, INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 255, 255));

		imshow(output_title, dst);
	}

	waitKey(0);
	return 0;
}

void update_map() {
	for (int row = 0; row < src.rows; row++) {
		for (int col = 0; col < src.cols; col++) {
			switch (index)
			{
			case 0:
				if (col > (src.cols * 0.25) && col < (src.cols * 0.75) && row >(src.rows * 0.25) && row < (src.rows * 0.75)) {
					map_x.at<float>(row, col) = 2 * (col - (src.cols * 0.25));
					map_y.at<float>(row, col) = 2 * (row - (src.rows * 0.25));
				}
				else {
					map_x.at<float>(row, col) = 0;
					map_y.at<float>(row, col) = 0;
				}
				break;
			case 1:
				map_x.at<float>(row, col) = src.cols - col - 1;
				map_y.at<float>(row, col) = row;
				break;
			case 2:
				map_x.at<float>(row, col) = col;
				map_y.at<float>(row, col) = src.rows - row -1;
				break;
			case 3:
				map_x.at<float>(row, col) = src.cols - col - 1;
				map_y.at<float>(row, col) = src.rows - row - 1;
				break;
			}
		}
	}
}

15、直方图

概念

直方图

对于图像梯度、每个像素的角度、等一切图像的属性值,都可以建立直方图。

常见属性:

dims:表示维度,对于灰度图像来说只有一个通道dims=1

bins:表示在维度中子区域大小划分,bins=256,划分256个级别

range:表示值的范围,灰度值范围为[0~255]之间

图像直方图

图像直方图,是指整个图像在灰度范围内的像素值(0~255)统计出现频率次数,据此生成的直方图,称为图像直方图。直方图反映了图像灰度的分布情况,是图像的统计学特征。

直方图均衡化

直方图均衡化是一种简单有效的图像增强技术,通过改变图像的直方图来改变图像中各像素的灰度,主要用于增强动态范围偏小的图像的对比度

直方图比较

对输入的两站图像计算得到直方图H1与H2,归一化到相同的尺度空间,然后可以通过计算H1与H2的之间的距离得到两个直方图的相似程度进而比较图像本身的相似程度。

比较方法:

Correlation:相关性比较

Chi-Square:卡方比较

Inersection:十字交叉性

Bhattacharyya distance:巴氏距离

反向投影

反向投影是反映直方图模型在目标图像中的分布情况

简单点说就是用直方图模型去目标图像中寻找是否有相似的对象。通常用HSV色彩空间的HS两个通道直方图模型

相关API

equalizaeHist(InputArray src,OutputArray dst)

第一个参数,输入图像,必须是8位的灰度图像

第二个参数,输出结果

split(const Mat & src, Mat * mvbegin)

把多通道图像分为多个单通道图像

第一个参数,输入图像

第二个参数,输出的多通道图像数组

calcHist(const Mat* images,int images,const int* channels,InputArray mask,OutputArray hist,int dims,const int* histsize,const float* ranges,bool uniform,bool accumulate)

第一个参数,输入图像指针

第二个参数,图像数目

第三个参数,通道数,要计算的通道数的下标,可以传一个数组 {0, 1} 表示计算第0通道与第1通道的直方图,此数组长度要与histsize ranges 数组长度一致

第四个参数,输入mask,可选。如有,则表示只计算mask元素值为255的位置的直方图

第五个参数,输出的直方图数据

第六个参数,维数

第七个参数,直方图级数,对应bins

第八个参数,值域范围

第九个参数,是否归一化到0-1之间

第十个参数,累积标识符,有默认值false,若为true,直方图再分配阶段不会清零。此功能主要是允许从多个阵列中计算单个直方图或者用于再特定的时间更新直方图.

compareHist(InputArray h1, InputArray h2, int method)

第一/二个参数,直方图数据

第三个参数,比较方法

void cv::calcBackProject ( const Mat * images, int nimages,const int * channels,InputArray hist,OutputArray backProject,const float ** ranges,double scale = 1,bool uniform = true)

第一个参数,输入图像,图像深度必须位CV_8U,CV_16U或CV_32F中的一种,尺寸相同,每一幅图像都可以有任意的通道数
第二个参数,输入图像的数量
第三个参数,用于计算反向投影的通道列表,通道数必须与直方图维度相匹配,第一个数组的通道是从0到image[0].channels()-1,第二个数组通道从图像image[0].channels()到image[0].channels()+image[1].channels()-1计数
第四个参数,输入的直方图,直方图的bin可以是密集(dense)或稀疏(sparse)
第五个参数,目标反向投影输出图像,是一个单通道图像,与原图像有相同的尺寸和深度
第六个参数,直方图中每个维度bin的取值范围
第七个参数,可选输出反向投影的比例因子
第八个参数,直方图是否均匀分布(uniform)的标识符,有默认值true

实例

实例一:直方图均衡化

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst;
char input_title[] = "input image";
char output_title[] = "result image";

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);

	cvtColor(src, src, COLOR_BGR2GRAY);
	imshow(input_title, src);
	//原图必须是8位
	equalizeHist(src, dst);
	imshow(output_title, dst);

	waitKey(0);
	return 0;
}

实例二:直方图计算

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst;
char input_title[] = "input image";
char output_title[] = "result image";

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	//分通道显示
	vector<Mat> bgr_planes;
	//转成单通道
	split(src, bgr_planes);
	//imshow("single image", bgr_planes[0]);

	//计算直方图
	int histSize = 256;
	float range[] = { 0, 256 };
	const float* histRanges = { range };
	Mat b_hist, g_hist, r_hist;
	calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRanges, true, false);
	calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRanges, true, false);
	calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRanges, true, false);

	//归一化
	int hist_h = 400;
	int hist_w = 512;
	int bin_w = hist_w / histSize;
	Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
	normalize(b_hist, b_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
	normalize(g_hist, g_hist, 0, hist_h, NORM_MINMAX, -1, Mat());
	normalize(r_hist, r_hist, 0, hist_h, NORM_MINMAX, -1, Mat());

	for (int i = 0; i < histSize; i++) {
		line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(b_hist.at<float>(i))), Point((i)*bin_w, hist_h - cvRound(b_hist.at<float>(i))), Scalar(255, 0, 0), 2, LINE_AA);
		line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(g_hist.at<float>(i))), Point((i)*bin_w, hist_h - cvRound(g_hist.at<float>(i))), Scalar(0, 255, 0), 2, LINE_AA);
		line(histImage, Point((i - 1) * bin_w, hist_h - cvRound(r_hist.at<float>(i))), Point((i)*bin_w, hist_h - cvRound(r_hist.at<float>(i))), Scalar(0, 0, 255), 2, LINE_AA);
	}
	
	imshow(output_title, histImage);

	waitKey(0);
	return 0;
}

实例三:直方图比较

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst;
char input_title[] = "input image";
char output_title[] = "result image";

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	cvtColor(src, src, COLOR_BGR2HSV);

	int h_bins = 50;
	int s_bins = 60;
	int histSize[] = { h_bins, s_bins };
	float h_ranges[] = { 0,180 };
	float s_ranges[] = { 0,256 };
	const float* ranges[] = { h_ranges, s_ranges };
	int channels[] = { 0, 1 };
	MatND hist_base;

	calcHist(&src, 1, channels, Mat(), hist_base, 2, histSize, ranges, true, false);
	normalize(hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat());
	double base = compareHist(hist_base, hist_base, HISTCMP_CORREL);
	
	cout << "value is " << base << endl;

	waitKey(0);
	return 0;
}

实例四:直方图反向投影

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst;
char input_title[] = "input image";
char output_title[] = "result image";

Mat hsv, hue;
int bins = 12;
void Hist_And_Backprojection(int, void*);

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	cvtColor(src, hsv, COLOR_BGR2HSV);
	hue.create(hsv.size(), hsv.depth());
	int nchannels[] = { 0,0 };
	mixChannels(&hsv, 1, &hue, 1, nchannels, 1);

	createTrackbar("Histogram Bins : ", input_title, &bins, 180, Hist_And_Backprojection);
	Hist_And_Backprojection(0, 0);

	waitKey(0);
	return 0;
}

void Hist_And_Backprojection(int, void*) {
	float range[] = { 0, 180 };
	const float* histRanges = { range };
	Mat h_hist;
    if (bins == 0)
        return;
	calcHist(&hue, 1, 0, Mat(), h_hist, 1, &bins, &histRanges, true, false);
	normalize(h_hist, h_hist, 0, 255, NORM_MINMAX, -1, Mat());

	Mat backProjImage;
	calcBackProject(&hue, 1, 0, h_hist, backProjImage, &histRanges, 1, true);

	imshow(output_title, backProjImage);

	int hist_h = 400, hist_w = 400;
	Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));

	int bin_w = hist_w / bins;

	for (int i = 0; i < bins; i++) {
		rectangle(histImage,
			Point((i - 1) * bin_w, hist_h - h_hist.at<float>(i) * (400 / 255)),
			//Point((i)*bin_w, (hist_h - cvRound(h_hist.at(i) * (400 / 255)))), 
			Point(i * bin_w, hist_h),
				Scalar(0, 0, 255), -1);
	}

	imshow("Histogram ", histImage);
}

16、模板匹配

概念

模板匹配(TemplateMatching)就是在一幅图像中寻找和模板图像(template)最相似的区域,该方法原理简单计算速度快,能够应用于目标识别,目标跟踪等多个领域。

匹配算法

  • 计算平方不同

  • 计算相关性

  • 计算相关系数

相关API

matchTemplate(InputArray image, InputArray templ, OutputArray result, int method)

第一个参数,输入图像。必须为8位或者32位的浮点型。

第二个参数,用于搜索的模板图像。必须小于输入图像并且是一样的数据类型。

第三个参数,匹配结果图像。必须是单通道32位浮点型,且大小是(W-w+1)*(H-h+1),其中W,H分别为输入图像的宽和高,w,h分别为模板图像的宽和高。

第四个参数,相似度衡量的方法

实例

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst, temp;
char input_title[] = "input image";
char output_title[] = "result image";
char match_title[] = "template match-demo";
int match_method = TM_SQDIFF;
int max_track = 5;
void Match_Demo(int, void*);

int main()
{
	//待检测图像
	src = imread("D:/1.jpg");
	//模板图像
	temp = imread("D:/2.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	namedWindow(match_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	const char* trackbar_title = "Match Algo Type : ";
	createTrackbar(trackbar_title, output_title, &match_method, max_track, Match_Demo);
	Match_Demo(0, 0);


	waitKey(0);
	return 0;
}

void Match_Demo(int, void*) {
	int width = src.cols - temp.cols + 1;
	int height = src.rows - temp.rows + 1;
	Mat result(width, height, CV_32FC1);

	matchTemplate(src, temp, result, match_method, Mat());
	normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());

	Point minLoc;
	Point maxLoc;
	double min, max;
	src.copyTo(dst);
	Point tempLoc;
	minMaxLoc(result, &min, &max, &minLoc, &maxLoc, Mat());

	if (match_method == TM_SQDIFF || match_method == TM_SQDIFF_NORMED) {
		tempLoc = minLoc;
	}
	else {
		tempLoc = maxLoc;
	}

	rectangle(dst, Rect(tempLoc.x, tempLoc.y, temp.cols, temp.rows), Scalar(0, 0, 255), 2, 8);
	rectangle(result, Rect(tempLoc.x, tempLoc.y, temp.cols, temp.rows), Scalar(0, 0, 255), 2, 8);

	imshow(output_title, result);
	imshow(match_title, dst);
}

17、轮廓发现

概念

轮廓发现是基于图像边缘提取的基础寻找对象轮廓的方法,所以边缘提取的阈值选定会影响最终轮廓发现结果。

凸包

在一个多边形边缘或者内部任意两个点的连线都包含在多边形边界或内部

Graham扫描算法

  • 首先选择Y方向最低的点作为起始点p0
  • 从p0开始极坐标扫描,依次添加p1…pn(排序顺序时根据极坐标的角度大小,逆时针方向)
  • 对每个点pi来说,如果添加pi点到凸包中导致一个左转向(逆时针方向)则添加该点到凸包,反之如果导致一个右转向(顺时针方向)删除该点从凸包中

相关API

indContours( InputOutputArray binImg, OutputArrayOfArrays contours,OutputArray hierachy, int mode, int method,Point offset=Point())

第一个参数,输入图像,非0的像素被看成1,0的像素值保持不变,8-bit

第二个参数,全部发现的轮廓对象

第三个参数,图像的拓扑结构,可选,该轮廓发现算法正是基于图像拓扑结构实现

第四个参数,轮廓返回的模式

第五个参数,发现方法

第六个参数,轮廓像素的位移,默认(0, 0)没有位移

drawContours( InputOutputArray binImg, OutputArrayOfArrays contours, Int contourIdx,const Scalar & color, int thickness, int lineType , InputArray hierarchy, int maxlevel, Point offset=Point())

第一个参数,输出图像

第二个参数,全部发现的轮廓对象

第三个参数,轮廓索引号

第四个参数,绘制时候颜色

第五个参数,绘制线宽

第六个参数,线的类型LINE_8

第七个参数,拓扑结构图

第八个参数,最大层数,0只绘制当前的,1表示绘制绘制当前及其内嵌的轮廓

第九个参数,轮廓位移,可选

convexHull(InputArray points, OutputArray hull, bool clockwise, bool returnPoints)

第一个参数,输入候选点,来自fingdContours

第二个参数,凸包

第三个参数,true为顺时针方向

第四个参数,true表示返回点个数,如果第二个参数是vector则自动忽略

approxPolyDP( InputArray curve,OutputArray approxCurve,double epsilon, bool closed )

基于RDP算法实现,目的是减少多边形轮廓点数

第一个参数,输入曲线,数据类型可以为vector。

第二个参数,输出折线,数据类型可以为vector。

第三个参数,判断点到相对应的line segment 的距离的阈值。(距离大于此阈值则舍弃,小于此阈值则保留,epsilon越小,折线的形状越“接近”曲线。)

第四个参数,曲线是否闭合的标志位。

boundingRect(InputArray array)

计算并返回指定点集或灰度图像的非零像素的右边界/最小垂直边界矩形。

参数,输入存储在vector或Mat中的灰度图像或2D点集

minAreaRect(InputArray points)

计算并返回指定点集的最小区域边界矩形(可能已旋转)。

参数,二维点的输入向量,存储在vector或Mat中

实例一:轮廓发现

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst;
char input_title[] = "input image";
char output_title[] = "result image";
int threshold_value = 100;
int threshold_max = 255;
void Demo_contours(int, void*);

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	cvtColor(src, src, COLOR_BGR2GRAY);
	createTrackbar("Threshold Value : ", output_title, &threshold_value, threshold_max, Demo_contours);
	Demo_contours(0, 0);

	waitKey(0);
	return 0;
}

void Demo_contours(int, void*) {
	Mat canny_output;
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	Canny(src, canny_output, threshold_value, threshold_value * 2, 3, false);
	findContours(canny_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));

	dst = Mat::zeros(src.size(), CV_8UC3);
	RNG rng(12345);
	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, contours, i, color, 2, 8, hierachy, 0, Point(0, 0));
	}
	imshow(output_title, dst);
}

实例二:凸包

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst, src_gray;
char input_title[] = "input image";
char output_title[] = "result image";

int threshold_value = 100;
int threshold_max = 255;
RNG rng(12345);
void Threshold_Callback(int, void*);

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	const char* trackbar_label = "Threshold : ";

	cvtColor(src, src_gray, COLOR_BGR2GRAY);
	blur(src_gray, src_gray, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);

	createTrackbar(trackbar_label, output_title, &threshold_value, threshold_max, Threshold_Callback);
	Threshold_Callback(0, 0);
	
	waitKey(0);
	return 0;
}

void Threshold_Callback(int, void*) {
	Mat bin_output;
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	threshold(src_gray, bin_output, threshold_value, threshold_max, THRESH_BINARY);
	findContours(bin_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
	vector<vector<Point>> convexhulls(contours.size());

	for (size_t i = 0; i < contours.size(); i++) {
		convexHull(contours[i], convexhulls[i], false, true);
	}

	dst = Mat::zeros(src.size(), CV_8UC3);;

	for (size_t k = 0; k < contours.size(); k++) {
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		drawContours(dst, contours, k, color, 2, LINE_8, hierachy, 0, Point(0, 0));
		drawContours(dst, convexhulls, k, color, 2, LINE_8, Mat(), 0, Point(0, 0));
	}

	imshow(output_title, dst);
}

实例三:轮廓周围绘制矩形

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst, src_gray, draw_img;
char input_title[] = "input image";
char output_title[] = "result image";

int threshold_value = 100;
int threshold_max = 255;
RNG rng(12345);
void Threshold_Callback(int, void*);

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	const char* trackbar_label = "Threshold : ";

	cvtColor(src, src_gray, COLOR_BGR2GRAY);
	blur(src_gray, src_gray, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);

	createTrackbar(trackbar_label, output_title, &threshold_value, threshold_max, Threshold_Callback);
	Threshold_Callback(0, 0);

	waitKey(0);
	return 0;
}

void Threshold_Callback(int, void*) {
	Mat binary_output;
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	threshold(src_gray, binary_output, threshold_value, threshold_max, THRESH_BINARY);
	imshow("binary image", binary_output);
	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> minRects(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[i].size() > 5) {
			myellipse[i] = fitEllipse(contours_ploy[i]);
			minRects[i] = minAreaRect(contours_ploy[i]);
		}
	}

	src.copyTo(draw_img);
	Point2f pts[4];
	for (size_t t = 0; t < contours.size(); t++) {
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		rectangle(draw_img, ploy_rects[t], color, 2, 8);
		circle(draw_img, ccs[t], radius[t], color, 2, 8);
		if (contours_ploy[t].size() > 5) {
			ellipse(draw_img, myellipse[t], color, 1, 8);
			minRects[t].points(pts);
			for (int r = 0; r < 4; r++) {
				line(draw_img, pts[r], pts[(r + 1) % 4], color, 1, 8);
			}
		}
	}
	
	imshow(output_title, draw_img);
}

18、图像矩

概念

通常描述了该图像形状的全局特征,并提供了大量的关于该图像不同类型的几何特性信息,比如大小、位置、方向及形状等。一阶矩与形状有关,二阶矩显示曲线围绕直线平均值的扩展程度,三阶矩则是关于平均值的对称性的测量。由二阶矩和三阶矩可以导出一组共7个不变矩。而不变矩是图像的统计特性,满足平移、伸缩、旋转均不变的不变性,在图像识别领域得到了广泛的应用。

相关API

moments(InputArray array, bool binaryImage=false)

矩的计算

第一个参数,输入数据

第二个参数,是否为二值图像

contourArea(InputArray contour, bool oriented)

计算轮廓面积

第一个参数,输入轮廓数据

第二个参数,默认false,返回绝对值

arcLength(InputArray curve, bool closed)

计算轮廓长度

第一个参数,输入曲线数据

第二个参数,是否是封闭曲线

实例

#include 
#include 

using namespace std;
using namespace cv;

Mat src, dst, src_gray;
char input_title[] = "input image";
char output_title[] = "result image";

int threshold_value = 80;
int threshold_max = 255;
RNG rng(12345);
void Threshold_Callback(int, void*);

int main()
{
	src = imread("D:/1.jpg");
	if (!src.data)
		return -1;

	namedWindow(input_title, WINDOW_AUTOSIZE);
	namedWindow(output_title, WINDOW_AUTOSIZE);
	imshow(input_title, src);

	const char* trackbar_label = "Threshold : ";

	cvtColor(src, src_gray, COLOR_BGR2GRAY);
	blur(src_gray, src_gray, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);

	createTrackbar(trackbar_label, output_title, &threshold_value, threshold_max, Threshold_Callback);
	Threshold_Callback(0, 0);

	waitKey(0);
	return 0;
}

void Threshold_Callback(int, void*) {
	Mat canny_output;
	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;

	Canny(src_gray, canny_output, threshold_value, threshold_value * 2, 3, false);
	findContours(canny_output, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));

	vector<Moments> contours_moments(contours.size());
	vector<Point2f> ccs(contours.size());

	for (size_t i = 0; i < contours.size(); i++) {
		contours_moments[i] = moments(contours[i]);
		ccs[i] = Point(static_cast<float>(contours_moments[i].m10 / contours_moments[i].m00), static_cast<float>(contours_moments[i].m01 / contours_moments[i].m00));
	}

	Mat drawImg;
	src.copyTo(drawImg);
	for (size_t i = 0; i < contours.size(); i++) {
		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
		cout << "center point x : " << ccs[i].x << " y : " << ccs[i].y << endl;
		cout << "contours " << i << " area : " << contourArea(contours[i]) << " arc length : " << arcLength(contours[i], true) << endl;
		drawContours(drawImg, contours, i, color, 2, 8, hierachy, 0, Point(0, 0));
		circle(drawImg, ccs[i], 2, color, 2, 8);
	}

	imshow(output_title, drawImg);
 }

19、点多边形测试

概念

测试一个点是否在给定的多边形内部、边缘或外部

相关API

pointPolygonTest(InputArray contour, Point2f pt, bool measureDist)

第一个参数,输入的轮廓

第二个参数,测试点

第三个参数,是否返回距离值,如果是false,1表示在内面,0表示在边界上,-1表示在外部,true返回实际距离,返回类型是double类型

实例

#include 
#include 

using namespace std;
using namespace cv;
int main()
{
	const int r = 100;
	Mat src = Mat::zeros(r * 4, r * 4, CV_8UC1);
	vector<Point2f> vert(6);
	vert[0] = Point(3 * r / 2, static_cast<int>(1.34 * r));
	vert[1] = Point(1 * r, 2 * r);
	vert[2] = Point(3 * r / 2, static_cast<int>(2.866 * r));
	vert[3] = Point(5 * r / 2, static_cast<int>(2.866 * r));
	vert[4] = Point(3 * r, 2 * r);
	vert[5] = Point(5 * r / 2, static_cast<int>(1.34 * r));

	for (int i = 0; i < 6; i++) {
		line(src, vert[i], vert[(i + 1) % 6], Scalar(255), 3, 8, 0);
	}

	vector<vector<Point>> contours;
	vector<Vec4i> hierachy;
	Mat csrc;
	src.copyTo(csrc);
	findContours(csrc, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
	Mat raw_dist = Mat::zeros(csrc.size(), CV_32FC1);

	for (int row = 0; row < raw_dist.rows; row++) {
		for (int col = 0; col < raw_dist.cols; col++) {
			double dist = pointPolygonTest(contours[0], Point2f(static_cast<float>(col), static_cast<float>(row)), true);
			raw_dist.at<float>(row, col) = static_cast<float>(dist);
		}
	}

	double minValue, maxValue;
	minMaxLoc(raw_dist, &minValue, &maxValue, 0, 0, Mat());
	Mat drawImg = Mat::zeros(src.size(), CV_8UC3);

	for (int row = 0; row < drawImg.rows; row++) {
		for (int col = 0; col < drawImg.cols; col++) {
			float dist = raw_dist.at<float>(row, col);
			if (dist > 0) {
				drawImg.at<Vec3b>(row, col)[0] = (uchar)(abs(1.0 - dist / maxValue) * 255);
			}
			else if (dist < 0) {
				drawImg.at<Vec3b>(row, col)[2] = (uchar)(abs(1.0 - dist / minValue) * 255);
			}
			else {
				drawImg.at<Vec3b>(row, col)[0] = (uchar)(abs(255 - dist));
				drawImg.at<Vec3b>(row, col)[1] = (uchar)(abs(255 - dist));
				drawImg.at<Vec3b>(row, col)[2] = (uchar)(abs(255 - dist));
			}
		}
	}

	namedWindow("input_title", WINDOW_AUTOSIZE);
	namedWindow("output_title", WINDOW_AUTOSIZE);

	imshow("input_title", src);
	imshow("output_title", drawImg);

	waitKey(0);
	return 0;
}

20、基于距离变换与分水岭的图像分割

概念

图像分割

图像分割的目的是将图像中像素根据一定的规则分为若干个cluster集合,每个集合包含一类像素

算法分类

监督学习方法

无监督学习方法:图像分割算法多数是无监督学习方法-KMeans

距离变换

基于倒角距离,计算图像中像素点到最近零像素点的距离,也就是零像素点的最短距离。

分水岭变换

基于浸泡理论实现

相关API

distanceTransform(InputArray src, OutputArray dst, OutputArray labels, int distanceType, int maskSize, int labelType=DIST_LABEL_CCOMP )

第一个参数,必须是一个8bit 单通道的图像

第二个参数,输出图像

第三个参数,可选的输出2D标签数组(离散Voronoi图)。它的类型为 CV_32SC1,大小与src相同

第四个参数,距离的类型。它可以是 CV_DIST_L1, CV_DIST_L2或 CV_DIST_C。

第五个参数,距离变换蒙版的大小。它可以是3、5或 CV_DIST_MASK_PRECISE(仅第一个函数支持后一个选项)。在CV_DIST_L1 或 CV_DIST_C 距离类型的情况下,该参数被强制为3

第六个参数,构建的标签数组的类型。如果labelType == DIST_LABEL_CCOMP,则src中的`每个零连接分量(以及所有最接近连接分量的非零像素)将被分配相同的标签。如果labelType == DIST_LABEL_PIXEL,则每个零像素(以及所有与其最接近的非零像素)将获得自己的标签。

watershed( InputArray image, InputOutputArray markers );

第一个参数 ,必须是一个8bit 3通道彩色图像矩阵序列

第二个参数 ,单通道整形(IPL_DEPTH_32S),具有相同维数(x,y)的图像

实例

#include 
#include 
#include 

using namespace std;
using namespace cv;

int main()
{
	char input_win[] = "input image";
	char watershed_win[] = "watershed segmentation demo";
	Mat src = imread("D:/1.jpg");
	// Mat src = imread("D:/kuaidi.jpg");
	if (src.empty()) {
		printf("could not load image...\n");
		return -1;
	}
	namedWindow(input_win, WINDOW_AUTOSIZE);
	imshow(input_win, src);
	// 1. change background
	for (int row = 0; row < src.rows; row++) {
		for (int col = 0; col < src.cols; col++) {
			if (src.at<Vec3b>(row, col) == Vec3b(255, 255, 255)) {
				src.at<Vec3b>(row, col)[0] = 0;
				src.at<Vec3b>(row, col)[1] = 0;
				src.at<Vec3b>(row, col)[2] = 0;
			}
		}
	}
	namedWindow("black background", WINDOW_AUTOSIZE);
	imshow("black background", src);

	// sharpen
	Mat kernel = (Mat_<float>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);
	Mat imgLaplance;
	Mat sharpenImg = src;
	filter2D(src, imgLaplance, CV_32F, kernel, Point(-1, -1), 0, BORDER_DEFAULT);
	src.convertTo(sharpenImg, CV_32F);
	Mat resultImg = sharpenImg - imgLaplance;

	resultImg.convertTo(resultImg, CV_8UC3);
	imgLaplance.convertTo(imgLaplance, CV_8UC3);
	imshow("sharpen image", resultImg);
	// src = resultImg; // copy back

	// convert to binary
	Mat binaryImg;
	cvtColor(src, resultImg, COLOR_BGR2GRAY);
	threshold(resultImg, binaryImg, 40, 255, THRESH_BINARY | THRESH_OTSU);
	imshow("binary image", binaryImg);

	Mat distImg;
	distanceTransform(binaryImg, distImg, DIST_L1, 3, 5);
	normalize(distImg, distImg, 0, 1, NORM_MINMAX);
	imshow("distance result", distImg);

	// binary again
	threshold(distImg, distImg, .4, 1, THRESH_BINARY);
	Mat k1 = Mat::ones(13, 13, CV_8UC1);
	erode(distImg, distImg, k1, Point(-1, -1));
	imshow("distance binary image", distImg);

	// markers 
	Mat dist_8u;
	distImg.convertTo(dist_8u, CV_8U);
	vector<vector<Point>> contours;
	findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, Point(0, 0));

	// create makers
	Mat markers = Mat::zeros(src.size(),CV_32SC1);
	cout << markers.type() << endl;
	for (size_t i = 0; i < contours.size(); i++) {
		drawContours(markers, contours, static_cast<int>(i), Scalar::all(static_cast<int>(i) + 1), -1);
	}
	circle(markers, Point(5, 5), 3, Scalar(255, 255, 255), -1);
	//imshow只能对像素值处于0-255范围内的图像进行存储和显示,所以CV_32S格式需要转换成CV_8U才能进行操作
	//imshow("my markers", markers * 1000);

	// perform watershed
	watershed(src, markers);
	Mat mark = Mat::zeros(markers.size(), CV_8UC1);
	markers.convertTo(mark, CV_8UC1);
	bitwise_not(mark, mark, Mat());
	imshow("watershed image", mark);

	// generate random color
	vector<Vec3b> colors;
	for (size_t i = 0; i < contours.size(); i++) {
		int r = theRNG().uniform(0, 255);
		int g = theRNG().uniform(0, 255);
		int b = theRNG().uniform(0, 255);
		colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
	}

	// fill with color and display final result
	Mat dst = Mat::zeros(markers.size(), CV_8UC3);
	for (int row = 0; row < markers.rows; row++) {
		for (int col = 0; col < markers.cols; col++) {
			int index = markers.at<int>(row, col);
			if (index > 0 && index <= static_cast<int>(contours.size())) {
				dst.at<Vec3b>(row, col) = colors[index - 1];
			}
			else {
				dst.at<Vec3b>(row, col) = Vec3b(0, 0, 0);
			}
		}
	}
	imshow("Final Result", dst);

	waitKey(0);
	return 0;
}

你可能感兴趣的:(Opencv,图像识别)