OpenCV基础入门系列基本操作——贰

7 篇文章 0 订阅
6 篇文章 4 订阅

系列博文第二篇,关于OpenCV4的一些基本操作和使用。
博文主要以实例展示不同的函数使用方法。

OpenCV基础入门系列基本操作——壹

前言

下述为本博文需要用到的各类头文件以及全局变量等
读者可根据具体使用进行修改

#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include<opencv2/imgproc.hpp>
#include <iostream>
#include <string>
#include <cmath>
#include <stdlib.h>
#include <vector>
#include <ctime>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/calib3d/calib3d.hpp> 

//色相
int hmin = 0;
int hmin_Max = 360;
int hmax = 360;
int hmax_Max = 360;
//饱和度
int smin = 0;
int smin_Max = 255;
int smax = 255;
int smax_Max = 255;
//亮度
int vmin = 106;
int vmin_Max = 255;
int vmax = 250;
int vmax_Max = 255;
/*
在 HSV 颜色空间下,比 BGR 更容易跟踪某种颜色的物体,常用于分割指定颜色的物体。
HSV 表达彩色图像的方式由三个部分组成:
H S V - θ ρ z
1、Hue(色调、色相)	Hue 用角度度量,理论上取值范围为0(red)~360°,从红色开始,逆时针旋转,表示色彩信息,即所处的光谱颜色的位置。
	opencv的0从蓝色开始
2、Saturation(饱和度、色彩纯净度)
3、Value(明度)
*/
using namespace cv;
using namespace std;
const path = "D:\\Desktop\\1.jpg"
//此处读入的图片路径可以自定义更改并传入不同的函数,产生不同的效果

1.HSV 通道的二值化

利用不同的物体在HSV 色彩空间上的不同色域,实现目标像素的提取。
主要利用iRange 函数。
需要注意的是opencv 中h 的定义与理论值不同。
我们能够查到一般HSV的范围是
H: [0,360]
S: [0,100]
V: [0,100]

在openCV中,HSV的范围是
H: [0,180]
S: [0,255]
V: [0,255]

在这里插入图片描述
为实现对颜色通道的适时调整,这里我们使用opencv的GUI库,
首先创建进度条,并实现参数回调

//此处的代码用于将进度条置于初始位置,读者可自定义初始位置
int low_H = 30, low_S = 30, low_V = 30;
int high_H = 100, high_S = 100, high_V = 100;

void on_low_H_thresh_trackbar(int, void*)
{
	low_H = min(high_H - 1, low_H);
	setTrackbarPos("Low R", "Object Detection", low_H);
}
void on_high_H_thresh_trackbar(int, void*)
{
	high_H = max(high_H, low_H + 1);
	setTrackbarPos("High R", "Object Detection", high_H);
}
void on_low_S_thresh_trackbar(int, void*)
{
	low_S = min(high_S - 1, low_S);
	setTrackbarPos("Low G", "Object Detection", low_S);
}
void on_high_S_thresh_trackbar(int, void*)
{
	high_S = max(high_S, low_S + 1);
	setTrackbarPos("High G", "Object Detection", high_S);
}
void on_low_V_thresh_trackbar(int, void*)
{
	low_V = min(high_V - 1, low_V);
	setTrackbarPos("Low B", "Object Detection", low_V);
}
void on_high_V_thresh_trackbar(int, void*)
{
	high_V = max(high_V, low_V + 1);
	setTrackbarPos("High B", "Object Detection", high_V);
}

随后,利用inrange函数,实现对读入图像二值化,期间需要先将摄像头读入的BGR图像转换为HSV图像,然后再使用inRange()
官方文档给出的inRange的原型:
void cv::inRange(frame, Scalar(low_V, low_S, low_H), Scalar(high_V, high_S, high_H), frame_threshold);
参数1:输入要处理的图像,可以为单通道或多通道。
参数2:包含下边界的数组或标量。
参数3:包含上边界数组或标量。
参数4:输出图像,与输入图像src 尺寸相同。

//利用摄像头,实时处理RGB至HSV通道,并利用inrange二值化处理
void test(int i)
{
	Mat frame, frame_threshold;
	VideoCapture cap(i);
	namedWindow("Video Capture", WINDOW_NORMAL);
	namedWindow("Object Detection", WINDOW_NORMAL);
	//-- Trackbars to set thresholds for RGB values

	
	createTrackbar("Low H", "Object Detection", &low_H, 180, on_low_H_thresh_trackbar);
	createTrackbar("High H", "Object Detection", &high_H, 180, on_high_H_thresh_trackbar);
	createTrackbar("Low S", "Object Detection", &low_S, 255, on_low_S_thresh_trackbar);
	createTrackbar("High S", "Object Detection", &high_S, 255, on_high_S_thresh_trackbar);
	createTrackbar("Low V", "Object Detection", &low_V, 255, on_low_V_thresh_trackbar);
	createTrackbar("High V", "Object Detection", &high_V, 255, on_high_V_thresh_trackbar);
	
	while ((char)waitKey(1) != 'q') {
		cap >> frame;
		if (frame.empty())
			break;
		//将BGR图像转换为HSV图像,便于后续inRange函数二值化操作
		cvtColor(frame, frame_threshold, COLOR_BGR2HSV);
		inRange(frame, Scalar(low_V, low_S, low_H), Scalar(high_V, high_S, high_H), frame_threshold);
		//-- Show the frames
		imshow("Video Capture", frame);
		imshow("Object Detection", frame_threshold);
	}
	return ;
}

经过实验,下述参数可以分辨出人体肤色,在不同的光照条件下和不同的摄像头下,参数有一些区别,下述为博主实验时得到的参数。
在这里插入图片描述

2、 对于灰度图的二值化

本例实现调用两种二值化的函数,实现二值化。
虽然opencv 的二值化函数是可以对彩色图像进行处理的,但是我们一般不会对彩色图像进行二值化,所以在调用二值化函数之前,先将原图转为灰度图。
1、利用inRange()函数实现二值化

void test2_1(const string& path)
{
	/*	BGR to HSV */
	Mat src = imread(path);
	Mat dst = src.clone(), dst2 = src.clone();
	//src.convertTo(dst, CV_32FC3, 1.0 / 255.0); //加一步——对像素的BGR进行归一化
	imshow("src", src);
	Mat hsv;
	cvtColor(dst, hsv, COLOR_BGR2HSV);
	/*
	设置参数为CV_BGR2HSV,此处为COLOR_BGR2HSV
	那么所得的H、S、V值范围分别是[0,180),[0,255),[0,255),而非[0,360],[0,1],[0,1];
	*/
	imshow("HSV", hsv);
	//cout << hsv << endl;
	//waitKey(0);
	
	inRange(hsv, Scalar(hmin , 0.2, 0.2), Scalar(hmin_Max - 100, 0.9, 0.9), dst);
	imshow("dst", dst);

	inRange(hsv, Scalar(hmin, 30, 60), Scalar(hmin + 40, 190, 180), dst2);
	imshow("dst2", dst2);

	waitKey(0);
	return;
}

2、利用threshold (),二值化处理图像

//利用threshold ,二值化处理图像
void test2_2(const string& path)
{
	Mat src = imread(path,IMREAD_GRAYSCALE);
	Mat gray = src.clone();
	//cvtColor(src, gray, COLOR_RGB2GRAY);
	Mat res = gray.clone();
	threshold(gray, res, 100, 200, THRESH_BINARY);
	//threshold(gray, res, 0, 255, THRESH_OTSU); //大津法
	imshow("SRC", src);
	imshow("GRAY", gray);
	imshow("RES", res);
	waitKey(0);
	return;
}

3、利用自适应算法,二值化图像

void test2_2(const string& path)
{
	Mat src = imread(path);
	Mat res1 = src.clone(), res2 = src.clone(), gray = src.clone(), res11 = src.clone(), res22 = src.clone();
	cvtColor(res1, gray, COLOR_BGR2GRAY);
	cvtColor(res2, gray, COLOR_BGR2GRAY);
	cvtColor(res11, gray, COLOR_BGR2GRAY);
	cvtColor(res22, gray, COLOR_BGR2GRAY);
	imshow("SRC IMAGE", src);

	namedWindow("ADAPTIVE_THRESH_MEAN_C positive", WINDOW_NORMAL);
	namedWindow("ADAPTIVE_THRESH_GAUSSIAN_C positive", WINDOW_NORMAL);
	namedWindow("ADAPTIVE_THRESH_MEAN_C INV", WINDOW_NORMAL);
	namedWindow("ADAPTIVE_THRESH_GAUSSIAN_C INV", WINDOW_NORMAL);

	//自适应阈值算法
	adaptiveThreshold(gray, res1, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 5, 10);
	adaptiveThreshold(gray, res2, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 5, 10);
	adaptiveThreshold(gray, res11, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 5, 10);
	adaptiveThreshold(gray, res22, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, 5, 10);

	imshow("ADAPTIVE_THRESH_MEAN_C positive", res1);
	imshow("ADAPTIVE_THRESH_GAUSSIAN_C positive", res2);
	imshow("ADAPTIVE_THRESH_MEAN_C INV", res11);
	imshow("ADAPTIVE_THRESH_GAUSSIAN_C INV", res22);
	waitKey(0);
	return;
}

3、实现gamma矫正回调

代码如下

int gamma_min = 0, gamma = 2;
void gamma_set_tracker(int, void*);
void test3_2(const string& path)//实现回调函数
{
	Mat gtest, ori = imread(path);
	gtest = ori.clone();
	int height = gtest.rows; int width = gtest.cols;
	float vg[256];
	uchar average;
	namedWindow("After Gamma Correct", WINDOW_NORMAL);
	createTrackbar("Gamma value", "After Gamma Correct", &gamma_min, 50, gamma_set_tracker);
	float pre_gamma = 0.1;
	while(1){
		pre_gamma = float(gamma) / 50;
		cout << pre_gamma << endl;
		for (int i = 0; i < 256; ++i) {
			vg[i] = 256 * pow((float)i / 256, pre_gamma);
			//生成参数表,快速对应
		}
		for (int j = 0; j < height; ++j) {
			for (int i = 0; i < width; ++i) {
				gtest.at<Vec3b>(j, i)[0] = vg[gtest.at<Vec3b>(j, i)[0]];
				gtest.at<Vec3b>(j, i)[1] = vg[gtest.at<Vec3b>(j, i)[1]];
				gtest.at<Vec3b>(j, i)[2] = vg[gtest.at<Vec3b>(j, i)[2]];

			}
		}
		imshow("After Gamma Correct", gtest);
		gtest = ori.clone();
		waitKey(1);
	}
	return;
}
void gamma_set_tracker(int, void*)
{
	gamma = getTrackbarPos("Gamma value", "After Gamma Correct");
	setTrackbarPos("Gamma value", "After Gamma Correct", gamma);
}

运行结果如下所示
在这里插入图片描述

4、实现回调函数

1)实现对图像二值化的回调

int bin_max_shold = 255, bin_min_shold = 0;
//调整阈值范围
void binary_min_tracker(int, void*)
{
	bin_min_shold = min(bin_min_shold, bin_min_shold - 1);
	setTrackbarPos("Binary Shold", "Binary Image", bin_min_shold);
}
void binary_max_tracker(int, void*)
{
	bin_max_shold = max(bin_max_shold, bin_max_shold + 1);
	setTrackbarPos("Binary Shold","Binary Image", bin_max_shold);
}
//实现回调函数
void test3_1(const string& path) 
{
	Mat src = imread(path,0), dst = src.clone();
	namedWindow("Binary Image", WINDOW_NORMAL);
	createTrackbar("Binary Shold min", "Binary Image", &bin_min_shold, 254, binary_min_tracker);
	createTrackbar("Binary Shold max", "Binary Image", &bin_max_shold, 255, binary_max_tracker);
	while ((char)waitKey(1) != 'q') 
	{
		threshold(src, dst, bin_min_shold, bin_max_shold, THRESH_BINARY);
		imshow("Binary Image", dst);
	}
	return;
}

5、图像形态学基础操作

图像形态学的理论基础为集合论 。
图像中的集合代表二值图像或者灰度图像的形状。如二值图像的前景像素集合。
图像形态学的作用是简化图像数据,保持基本形状特性,去除不相干的结构等。
基本运算包括,膨胀、腐蚀、开运算、闭运算、顶帽运算和底帽运算等。

1)对图片先进行二值化,然后分别进行腐蚀、膨胀、开运算和闭运算。
图像处理分为多种,对于不同的图像腐蚀和膨胀的定义不同。

1、形态学图像处理是在图像中移动一个结构元素,然后将结构元素与下面的二值图像进行交、并等集合运算;先腐蚀后膨胀的过程称为开运算。
它具有消除细小物体,在纤细处分离物体和平滑较大物体边界的作用。先膨胀后腐蚀的过程称为闭运算。它具有填充物体内细小空洞,连接邻近物体和平滑边界的作用。

2、对灰度图像的膨胀(或腐蚀)操作有两类效果:
(1)如果结构元素的值都为正的,则输出图像会比输入图像亮(或暗);
(2)根据输入图像中暗(或亮)细节的灰度值以及它们的形状相对于结构元素的关系,它们在运算中或被消减或被除掉。
腐蚀就是使用算法,将图像的边缘腐蚀掉。作用就是将目标的边缘的“毛刺”踢除掉。

膨胀就是使用算法,将图像的边缘扩大些。作用就是将目标的边缘或者是内部的坑填掉。使用相同次数的腐蚀与膨胀,可以使目标表面更平滑。

1、膨胀运算,利用结构元素(卷积核),以锚点作为中心,逐步遍历待处理的图像元素。
结构元素可以为任意形状,下图选用的是3x3的结构元素(结构元素一般为奇数边的正方形)。一般选取中心位置为锚点。对结构元素包含的地方,如出现1,则整个结构元素所占的全部位置 置1
在这里插入图片描述
2、腐蚀运算,同理,腐蚀运算则是利用锚点遍历整个图像,结构元素覆盖的范围如果出现 0 ,则被处理的像素置0(即整个结构元素所占位置全部置0)
腐蚀运算可以去除一些黏连像素。以及去除噪声。

先利用相关操作自己写了一个腐蚀运算和膨胀运算的例子,加深对相关运算的理解和底层操作。

//图像形态学基础操作
void test44_1(const string& path)
{
	Mat src = imread(path, 0), expand_binImg = src.clone(), corrode_binImg = src.clone();
	threshold(src, expand_binImg, 100, 255, THRESH_BINARY);
	threshold(src, corrode_binImg, 100, 255, THRESH_BINARY);
	int height = src.cols, width = src.rows, x, y, i, j;

	int corrode_size = 1;  //此处设置腐蚀区域的大小,防止数组越界操作
	//腐蚀运算
	for (i = corrode_size; i < height - corrode_size; ++i) {
		for (j = corrode_size; j < width - corrode_size; ++j) {

			//遍历结构元素覆盖区域,伺机腐蚀
			for (y = i - corrode_size; y < i + corrode_size; ++y) {
				for (x = j - corrode_size; x < j + corrode_size; ++x) {
					if (src.at<uchar>(y, x) == 0) {
						for (y = i - corrode_size; y < i + corrode_size; ++y) {
							for (x = x; x < j + corrode_size; ++x) {
								corrode_binImg.at<uchar>(y, x) = 255;
							}
						}
						goto outloop;
					}
					else {
						continue;
					}
				}
			}

		outloop:	;
		}
	}
	//膨胀运算
	for (i = 0; i < height; ++i) {
		for (j = 0; j < width; ++j) {
			//膨胀操作
			if (src.at<uchar>(i, j) == 0) {
				for (y = i - 2; y < i + 1; ++y) {
					for (x = j - 2; x < j + 1; ++x) {
						expand_binImg.at<uchar>(y, x) = 0;
					}
				}
			}
		}
	}

	imshow("SRC", src);
	imshow("corrode binImg", corrode_binImg);
	imshow("expand binImg", expand_binImg);
	waitKey(0);
	return;
}

运行结果如下所示,从左到右依次为 原图,腐蚀运算后,膨胀运算 后的结果。
可以明显看到腐蚀运算使得很多微小的图像信息丢失了,同时这一点也使得其可以处理噪声,保留主要的信息。相反,膨胀运算则使得图像的联通加强,颜色更深。
在这里插入图片描述

3、开运算
开运算 = 先腐蚀运算,再膨胀运算(看上去把细微连在一起的两块目标分开了)
开运算的效果图如下图所示:
在这里插入图片描述
开运算总结:
(1)开运算能够除去孤立的小点,毛刺和小桥,而总的位置和形状不便。
(2)开运算是一个基于几何运算的滤波器。
(3)结构元素大小的不同将导致滤波效果的不同。
(4)不同的结构元素的选择导致了不同的分割,即提取出不同的特征。

4、闭运算
闭运算 = 先膨胀运算,再腐蚀运算(看上去将两个细微连接的图块封闭在一起)
闭运算的效果图如下图所示:
在这里插入图片描述

  • 闭运算总结:
    (1)闭运算能够填平小湖(即小孔),弥合小裂缝,而总的位置和形状不变。
    (2)闭运算是通过填充图像的凹角来滤波图像的。
    (3)结构元素大小的不同将导致滤波效果的不同。
    (4)不同结构元素的选择导致了不同的分割。
Mat test44_1_corrode(const Mat& src, int corrode_size = 1)  //此处设置腐蚀区域的大小,防止数组越界操作
{
	Mat corrode_binImg = src.clone();
	//cvtColor(src, corrode_binImg, COLOR_BGR2GRAY);
	//threshold(corrode_binImg, corrode_binImg, 100, 255, THRESH_BINARY);
	threshold(src, corrode_binImg, 100, 255, THRESH_BINARY);
	int height = src.cols, width = src.rows, x, y, i, j;

	for (i = corrode_size; i < height - corrode_size; ++i) {
		for (j = corrode_size; j < width - corrode_size; ++j) {

			//遍历结构元素覆盖区域,伺机腐蚀
			for (y = i - corrode_size; y < i + corrode_size; ++y) {
				for (x = j - corrode_size; x < j + corrode_size; ++x) {
					if (src.at<uchar>(y, x) == 0) {
						for (y = i - corrode_size; y < i + corrode_size; ++y) {
							for (x = x; x < j + corrode_size; ++x) {
								corrode_binImg.at<uchar>(y, x) = 255;
							}
						}
						goto outloop;
					}
					else {
						continue;
					}
				}
			}

		outloop:;
		}
	}
	return corrode_binImg;
}

Mat test44_1_expand(const Mat& src,int expand_size = 1)  //此处设置膨胀区域的大小,防止数组越界操作
{
	Mat expand_binImg = src.clone();
	//cvtColor(src, expand_binImg, COLOR_BGR2GRAY);
	//threshold(expand_binImg, expand_binImg, 100, 255, THRESH_BINARY);
	threshold(src, expand_binImg, 100, 255, THRESH_BINARY);
	int height = src.cols, width = src.rows, x, y, i, j;
	
	for (i = expand_size; i < height - expand_size; ++i) {
		for (j = expand_size; j < width - expand_size; ++j) {

			//遍历结构元素覆盖区域,伺机膨胀
			if (src.at<uchar>(i, j) == 0) {
				for (y = i - expand_size; y < i + expand_size; ++y) {
					for (x = j - expand_size; x < j + expand_size; ++x) {
						expand_binImg.at<uchar>(y, x) = 0;
					}
				}
			}
		}
	}
	return expand_binImg;
}

void test44_1_open(const string& path)
{
	Mat res = test44_1_expand( test44_1_corrode(imread(path, 0)) );  //开操作,先腐蚀后膨胀
	imshow("After open operation", res);
	waitKey(0);
	return;
}

void test44_1_close(const string& path)
{
	Mat res = test44_1_corrode(test44_1_expand(imread(path, 0), 3));   //闭操作,先膨胀后腐蚀
	imshow("After close operation", res);
	waitKey(0);
	return;
}

基于前述自写的腐蚀运算和膨胀运算,对之前的腐蚀运算和膨胀运算进行微调,我们可以组合出一个开运算的函数。

void test44_1_open(const string& path)

同理,我们还可以组合出一个闭运算的函数。

void test44_1_close(const string& path)

分别执行,得到自编写的开运算函数处理结果,以及闭运算函数处理结果
在这里插入图片描述
在这里插入图片描述

5、调用opencv带有的形态学操作函数,便捷实现形态学相关操作。
erode为腐蚀运算,dilate为膨胀运算。
下面这段代码用于生成使用者想要的结构元素。

Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));

此处调用腐蚀运算,膨胀运算,组合实现闭运算和开运算。

void test4_APIclose(const string& path)
{
	Mat src = imread(path, 0), temp1, temp2;
	threshold(src, temp1, 100, 255, THRESH_BINARY);
	Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));
	erode(temp1, temp2, element);
	dilate(src, temp1, element);
	imshow("OPEN result", temp2);
	waitKey(0);
	return;
}

void test4_APIopen(const string& path)
{
	Mat src = imread(path, 0);
	threshold(src, src, 100, 255, THRESH_BINARY);
	Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));
	dilate(src, src, element);
	erode(src, src, element);
	imshow("OPEN result", src);
	waitKey(0);
	return;

}

也可直接调用morphologyEx()函数实现不同的操作。

void test4_morphologyEx_test(const string& path)
{
	enum MorphTypes {
		MORPH_ERODE = 0, //腐蚀
		MORPH_DILATE = 1, //膨胀
		MORPH_OPEN = 2, //开操作
		MORPH_CLOSE = 3, //闭操作
		MORPH_GRADIENT = 4, //梯度操作
		MORPH_TOPHAT = 5, //顶帽操作
		MORPH_BLACKHAT = 6, //黑帽操作
		MORPH_HITMISS = 7
	};
	
	Mat src = imread(path, 0), dst[8];
	Mat ele = getStructuringElement(MORPH_RECT, Size(3, 3));
	for (int i = 0; i < 8; ++i)
	{
		morphologyEx(src, dst[i], i, ele, Point(-1, -1), 1, 0);
		string title = "OPERATION " + to_string(i);
		imshow(title, dst[i]);
	}
	waitKey(0);
	return;
}

下图为运行结果:
在这里插入图片描述

2)在了解过这些图像形态学基本操作后,我们便可以实现一些功能了,下面这个例子用于统计下图中硬币的个数。
在这里插入图片描述
代码如下,首先读入图片后,进行腐蚀操作,腐蚀操作,去除部分噪点,减少误差,再利用connectedComponentsWithStats()函数可以对黑白二值图进行连通域标记。

int nccomps = cv::connectedComponentsWithStats(
		dst,			//1、二值图像
		labels,     //2、和原图一样大的标记图
		stats,			//3、nccomps×5的矩阵 表示每个连通区域的外接矩形和面积(pixel)
		centroids,		//4、nccomps×2的矩阵 表示每个连通区域的质心
		4,				//5、使用8领域或4邻域
		CV_16U			//6、输出标签的数据类型,可选CV_32U或者CV_16U
	);

完整代码如下:

void connected_component_stats_coin_count_demo(const string& path) {
	Mat image = imread(path);
	// 二值化
	Mat gray, binary;
	cvtColor(image, gray, COLOR_BGR2GRAY);
	threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
	// 形态学操作
	Mat ele = getStructuringElement(MORPH_RECT, Size(3, 3));
	dilate(binary, binary, ele);
	imshow("binary", binary);
	Mat labels = Mat::zeros(image.size(), CV_32S);
	Mat stats, centroids;
	int num_labels = connectedComponentsWithStats(binary, labels, stats, centroids, 8, 4);
	cout << "总连通块数量:" << (num_labels - 1) << endl << endl;
	//connectedComponentsWithStats函数会多加一个整张图的中心,因此得出结果时需要减1
	vector<Vec3b> colors(num_labels);
	// background color
	colors[0] = Vec3b(0, 0, 0);
	int b, g, r;
	for (int i = 1; i < num_labels; i++) {
		b = sin(i + 1) * cos(i + 1) * 255;
		g = cos(i + 2) * 255;
		r = sin(i + 3) * 255;
		colors[i] = Vec3b(b, g, r);
	}
	// render result
	Mat dst = Mat::zeros(image.size(), image.type());
	int w = image.cols;
	int h = image.rows;
	for (int row = 0; row < h; row++) {
		for (int col = 0; col < w; col++) {
			int label = labels.at<int>(row, col);
			if (label == 0) continue;
			dst.at<Vec3b>(row, col) = colors[label];
		}
	}

	for (int i = 1; i < num_labels; i++) {
		Vec2d pt = centroids.at<Vec2d>(i, 0);
		int x = stats.at<int>(i, CC_STAT_LEFT);
		int y = stats.at<int>(i, CC_STAT_TOP);
		int width = stats.at<int>(i, CC_STAT_WIDTH);
		int height = stats.at<int>(i, CC_STAT_HEIGHT);
		int area = stats.at<int>(i, CC_STAT_AREA);
		printf("area : %d, center point(%.2f, %.2f)\n", area, pt[0], pt[1]);
		circle(dst, Point(pt[0], pt[1]), 2, Scalar(0, 0, 255), -1, 8, 0);  //画出连通域的质心
		rectangle(dst, Rect(x, y, width, height), Scalar(255, 0, 255), 1, 8, 0);
	}
	imshow("ccla-demo", dst);
	waitKey(0);
	return;
}

运行结果如下:
在这里插入图片描述

6、分割下图,并对其进行连通域标记,利用图像形态学中所学的知识实现自动计算圆点个数。

1)下图是一个PCB板的图片,统计节点的数目。
在这里插入图片描述
代码如下,执行相关操作后即可:

void test6_PCB_hole_count(const string& path)
{
	Mat src = imread(path, 0), dst, labels, stats, centroids;
	threshold(src, src, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
	Mat ele = getStructuringElement(MORPH_RECT, Size(13, 13));
	morphologyEx(src, dst, 2, ele, Point(0, 0), 1, 0);
	imshow("SCR IMAGE", src);
	imshow("AFTER OPEN OPERATION", dst);
	int num_labels = connectedComponentsWithStats(dst, labels, stats, centroids, 8, 4);
	cout << "The holes number in the PCB :"<<(num_labels - 1) << endl;
	waitKey(0);
	return;
}

处理结果如下所示,即可统计洞洞的数量。
在这里插入图片描述
在这里插入图片描述
2)统计下图回形针的数量,由于图像比较特殊,因此需要利用图像形态学的操作,过滤部分噪点,减少统计误差。
在这里插入图片描述
完整代码如下

void test6_PAPER_CLIP_count(const string& path)
{
	Mat src = imread(path, 0), dst, labels, stats, centroids;
	threshold(src, src, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
	Mat ele = getStructuringElement(MORPH_RECT, Size(3, 3));
	morphologyEx(src, dst, MORPH_ERODE, ele, Point(0, 0), 1, 0);
	namedWindow("SCR IMAGE", WINDOW_NORMAL);
	namedWindow("AFTER OPERATION", WINDOW_NORMAL);
	imshow("SCR IMAGE", src);
	imshow("AFTER OPERATION", dst);
	int num_labels = connectedComponentsWithStats(dst, labels, stats, centroids, 8, 4);
	//根据连通域的面积大小过滤掉噪点和异常区域
	int count = 0;
	for (int i = 0 ; i < num_labels; ++i) {
		if (stats.at<int>(i, CC_STAT_AREA) < 7000 && stats.at<int>(i, CC_STAT_AREA) > 5000) {
			++count;
		}
	}
	cout << "The clips number on paper :" << count << endl;
	//cout << stats << endl;
	waitKey(0);
	return;
}

该图比较特殊,加入了一些噪声,虽然左右两图看起来一样,但是运用连通域统计得到的数量有所差异。左边是直接对读入图片二值化,而右边则是消除了部分高噪声,使得部分无效的连通域不会被统计。
在这里插入图片描述
统计结果如下
在这里插入图片描述

7. 读取摄像头图像,并对摄像头图像进行中值滤波、均值滤波、高斯滤波

分别调用opencv的库函数,实现图像的滤波。
1)中值滤波

void test7_MIDDLE_FLITTER(int i=0)
{
	Mat frame, frame_flitter;
	VideoCapture cap(i);
	namedWindow("Video Capture", WINDOW_NORMAL);
	namedWindow("Object Detection", WINDOW_NORMAL);
	while ((char)waitKey(1) != 'q') {
		cap >> frame;
		if (frame.empty())
			break;
		medianBlur(frame, frame_flitter, 5);
		/*
		void medianBlur( InputArray src, OutputArray dst,int ksize);
		src — 输入图像
		dst — 输出图像, 必须与 src 相同类型
		ksize — 内核大小 (只需一个值,因为使用正方形窗口),必须为奇数。
		*/
		//-- Show the frames
		imshow("Video Capture", frame);
		imshow("Object Detection", frame_flitter);
	}
	return;
}

2)均值滤波

void test7_MEAN_FLITTER(int i=0)
{
	Mat frame, frame_flitter;
	VideoCapture cap(i);
	namedWindow("Video Capture", WINDOW_NORMAL);
	namedWindow("Object Detection", WINDOW_NORMAL);
	while ((char)waitKey(1) != 'q') {
		cap >> frame;
		if (frame.empty())
			break;
		blur(frame, frame_flitter, Size(5, 5), Point(-1, -1));
		/*
		void blur(InputArray src, OutputArray dst,Size ksize, Point anchor=Point(-1,-1),int borderType=BORDER_DEFAULT );
		InputArray:输入图像
		OutputArray:输出图像
		Size:内核大小
		Point:锚点(被平滑的点)。默认值为Point(-1,-1)表示是中心点。
		borderType:边界模式,默认值BORDER_DEFAULT 。我们一般不管它。
		*/
		//-- Show the frames
		imshow("Video Capture", frame);
		imshow("Object Detection", frame_flitter);
	}
	return;
}

3)高斯滤波

void test7_GAUSS_FLITTER(int i=0)
{
	Mat frame, frame_flitter;
	VideoCapture cap(i);
	namedWindow("Video Capture", WINDOW_NORMAL);
	namedWindow("Object Detection", WINDOW_NORMAL);
	while ((char)waitKey(1) != 'q') {
		cap >> frame;
		if (frame.empty())
			break;
		GaussianBlur(frame, frame_flitter, Size(5, 5), 1);
		/*
		void GaussianBlur( InputArray src,OutputArray dst,Size ksize,double sigmaX,double sigmaY=0,int borderType=BORDER_DEFAULT );
		InputArray:输入图像
		OutputArray:输出图像
		Size:内核大小,Size(w,h)其中w,h必须为正数,而且必须为奇数。w表示像素宽度,h表示高度。
		sigmaX:高斯核函数在X方向的标准偏差
		sigmaY:高斯核函数在Y方向的标准偏差
		borderType:边界模式,默认值BORDER_DEFAULT 。我们一般不管它。
		*/
		//-- Show the frames
		imshow("Video Capture", frame);
		imshow("Object Detection", frame_flitter);
	}
	return;
}

该操作对于CPU要求比较高,后面可以考虑改进为CUDA结合OpenCV实现滤波,利用GPU加速处理。

8、读取摄像头图像,并对摄像头图像进行边缘提取,分别提取 x,y 方向上的边缘,观察结果有何区别。

void test8_Sobel_Operator(int i = 0)
{
	Mat frame, frame_Sobel_X, frame_Sobel_Y, frame_Sobel_XY;
	VideoCapture cap(i);
	namedWindow("Video Capture", WINDOW_NORMAL);
	namedWindow("Sobel Operation at X dim", WINDOW_NORMAL);
	namedWindow("Sobel Operation at Y dim", WINDOW_NORMAL);
	namedWindow("Sobel Operation at X-Y dim", WINDOW_NORMAL);
	while ((char)waitKey(1) != 'q') {
		cap >> frame;
		if (frame.empty())
			break;
		Sobel(frame, frame_Sobel_X, -1, 1, 0, 3);
		Sobel(frame, frame_Sobel_Y, -1, 0, 1, 3);
		Sobel(frame, frame_Sobel_XY, -1, 1, 1, 3);
		/*
void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT )
InputArray src:输入的原图像,Mat类型
OutputArray dst:输出的边缘检测结果图像,Mat型,大小与原图像相同。
int ddepth:输出图像的深度,针对不同的输入图像,输出目标图像有不同的深度,具体组合如下:
- 若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F
- 若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F
- 若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F
- 若src.depth() = CV_64F, 取ddepth = -1/CV_64F
	注:ddepth =-1时,代表输出图像与输入图像相同的深度。
	int dx:int类型dx,x 方向上的差分阶数,1或0
	int dy:int类型dy,y 方向上的差分阶数,1或0
	其中,dx=1,dy=0,表示计算X方向的导数,检测出的是垂直方向上的边缘;dx=0,dy=1,表示计算Y方向的导数,检测出的是水平方向上的边缘。
	int ksize:为进行边缘检测时的模板大小为ksize*ksize,取值为1、3、5和7,其中默认值为3。特殊情况:ksize=1时,采用的模板为3*1或1*3。*/
		//-- Show the frames
		imshow("Video Capture", frame);
		imshow("Sobel Operation at X dim", frame_Sobel_X);
		imshow("Sobel Operation at Y dim", frame_Sobel_Y);
		imshow("Sobel Operation at X-Y dim", frame_Sobel_XY);
	}
	return;
}

9、使用已经学过的算法,实现一个简单的磨皮程序,即人物皮肤的部分显得光滑。(mask,卷积,HSV 范围限制)

1)均值滤波实现光滑,并实现范围限制,利用HSV提取人物皮肤部分,以该部分作为均值滤波的作用范围,对每一个像素点(人物的)周围的结构区域均值滤波。

void test9_demo2(int c = 0)
{
	Mat frame, frame2, frame_threshold;
	VideoCapture cap(c);
	namedWindow("HSV SHAPE Video Capture", WINDOW_NORMAL);
	namedWindow("frame_threshold", WINDOW_NORMAL);
	//-- Trackbars to set thresholds for RGB values
	createTrackbar("Low H", "frame_threshold", &low_H, 360, on_low_H_thresh_trackbar);
	createTrackbar("High H", "frame_threshold", &high_H, 360, on_high_H_thresh_trackbar);
	createTrackbar("Low S", "frame_threshold", &low_S, 255, on_low_S_thresh_trackbar);
	createTrackbar("High S", "frame_threshold", &high_S, 255, on_high_S_thresh_trackbar);
	createTrackbar("Low V", "frame_threshold", &low_V, 255, on_low_V_thresh_trackbar);
	createTrackbar("High V", "frame_threshold", &high_V, 255, on_high_S_thresh_trackbar);
	int i, j, height, width,channels;
	while ((char)waitKey(1) != 'q') {
		cap >> frame;
		imshow("BGR SHAPE Video Capture", frame);
		if (frame.empty())		break;
		//-- Detect the object based on HSV Range Values
		cvtColor(frame, frame, COLOR_BGR2HSV);
		inRange(frame, Scalar(low_V, low_S, low_H), Scalar(high_V, high_S, high_H), frame_threshold);
		//-- Show the frames
		imshow("HSV SHAPE Video Capture", frame);
		cvtColor(frame, frame, COLOR_HSV2BGR);
		height = frame.rows - 10, width = frame.rows - 10;
		//摄像头读入为480x640,横着640,竖着480
		for (i = 10; i < height; ++i) {
			for (j = 10; j < width; ++j) {
				if (frame_threshold.at<uchar>(i, j) > 0) {
					for (channels = 0; channels < 3; ++channels) {
						frame.at<Vec3b>(i, j)[channels] = (
							frame.at<Vec3b>(i - 1, j - 1)[channels] + frame.at<Vec3b>(i - 1, j)[channels] + frame.at<Vec3b>(i - 1, j + 1)[channels]
							+ frame.at<Vec3b>(i + 1, j - 1)[channels] + frame.at<Vec3b>(i + 1, j)[channels] + frame.at<Vec3b>(i + 1, j + 1)[channels]
							+ frame.at<Vec3b>(i, j - 1)[channels] + frame.at<Vec3b>(i, j)[channels] + frame.at<Vec3b>(i, j + 1)[channels]
							) / 9;
					}

				}
			}
		}
		imshow("BGR Video Capture", frame);
		imshow("frame_threshold", frame_threshold);
	}
	return;
}

2)双边滤波实现光滑,直接调用库函数

void test9_clean_skin_demo(const string& path)
{
	//双边滤波
	Mat src = imread(path), dst = src.clone(), temp = src.clone(), EDFFilter_src = src.clone(), res = src.clone();
	//.InputArray src : 输入图像,可以是Mat类型,图像必须是8位或浮点型单通道、三通道的图像。
	//.OutputArray dst : 输出图像,和原图像有相同的尺寸和类型。
	//.int d : 表示在过滤过程中每个像素邻域的直径范围。如果这个值是非正数,则函数会从第五个参数sigmaSpace计算该值。
	//.double sigmaColor : 颜色空间过滤器的sigma值,这个参数的值月大,表明该像素邻域内有月宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
	//.double sigmaSpace : 坐标空间中滤波器的sigma值,如果该值较大,则意味着颜色相近的较远的像素将相互影响,
	//	从而使更大的区域中足够相似的颜色获取相同的颜色。当d > 0时,d指定了邻域大小且与sigmaSpace五官,否则d正比于sigmaSpace.
	//.int borderType = BORDER_DEFAULT: 用于推断图像外部像素的某种边界模式,有默认值BORDER_DEFAULT.
	bilateralFilter(src, EDFFilter_src, 50, 30, 50, 4);

	//Dest =(Src * (100 - Opacity) + (Src + 2 * GuassBlur(EPFFilter(Src) - Src + 128) - 256) * Opacity) /100 ;
	GaussianBlur((EDFFilter_src - src + 128), dst, Size(55, 55), 3);
	int Opacity = 90;
	res = (src * Opacity + (src + 2 * dst - 256) * Opacity) / 100;
	namedWindow("SRC", WINDOW_NORMAL);
	namedWindow("RES", WINDOW_NORMAL);
	imshow("SRC", src);
	imshow("RES", res);
	waitKey(0);
}

10.使用canny 算子

void test10_API_Canny(int i = 0)
{
	VideoCapture cap(i);
	Mat frame, dst;
	while ((char)waitKey(1) != 'q')
	{
		cap >> frame;
		Canny(frame, dst, 40, 80);
		imshow("Original frame", frame);
		imshow("Canny operation", dst);
	}
	return;
}

后续还会继续更新OpenCV的各种操作。学无止境。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值