系列博文第二篇,关于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 valuescreateTrackbar("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 framesimshow("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函数会多加一个整张图的中心,因此得出结果时需要减1vector<Vec3b> colors(num_labels);// background colorcolors[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 resultMat 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 framesimshow("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 framesimshow("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 framesimshow("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或0int 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 framesimshow("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 valuescreateTrackbar("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 ValuescvtColor(frame, frame, COLOR_BGR2HSV);inRange(frame, Scalar(low_V, low_S, low_H), Scalar(high_V, high_S, high_H), frame_threshold);//-- Show the framesimshow("HSV SHAPE Video Capture", frame);cvtColor(frame, frame, COLOR_HSV2BGR);height = frame.rows - 10, width = frame.rows - 10;//摄像头读入为480x640,横着640,竖着480for (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的各种操作。学无止境。

OpenCV基础入门系列基本操作——贰相关推荐

  1. 【JAVA零基础入门系列】Day2 Java集成开发环境IDEA

    [JAVA零基础入门系列](已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day ...

  2. 【JAVA零基础入门系列】Day14 Java对象的克隆

    [JAVA零基础入门系列](已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day ...

  3. saltstack之基础入门系列文章简介

    使用saltstack已有一段时间,最近由于各种原因,特来整理了saltstack基础入门系列文章,已备后续不断查阅(俗话说好记性不如烂笔头),也算是使用此工具的一个总结. saltstack的前六篇 ...

  4. OpenCV基础入门【C++及python语言】

    OpenCV基础入门[C++语言] OpenCV-Python 中文教程 OpenCV官方教程中文版(For Python) OpenCV2-Python-Tutorials 部分文件参考: http ...

  5. 结构化数据丨Python爬虫基础入门系列(7)

    提示:文末有福利!最新Python爬虫资料/学习指南>>戳我直达 文章目录 前言 JSON 1. json.loads() 2. json.dumps() 3. json.dump() 4 ...

  6. Urllib2库丨Python爬虫基础入门系列(12)

    提示:文末有福利!最新Python爬虫资料/学习指南>>戳我直达 文章目录 前言 Urllib2库 学习目的 urlopen GET请求方式 利用urllib2.Request类,添加He ...

  7. Civil3D,CAD零基础入门系列1.MgdFbg的下载及安装

    Civil3D,CAD零基础入门系列1.MgdFbg的下载及安装 本教程讲述如何下载及配置生成MgdFbg插件的dll,并在CAD中进行使用. .MgdFbg可以在GItHub上进行下载,下载地址为: ...

  8. 安卓开发基础入门系列教程

    第一章:环境搭建与开发相关(已完结 10/10) https://blog.csdn.net/coder_pig/article/details/50000773 Android基础入门教程--1.1 ...

  9. 【Qt Quick】零基础入门系列之安装与卸载(一)

    [Qt Quick]概述 |本文大概阅读时间8分钟. |版权说明:原创文章,如需转载,请标明文章出处.https://blog.csdn.net/weixin_40192195/article/det ...

最新文章

  1. OSChina 周六乱弹 ——生日快乐 @落落酱
  2. 通过代码获取Bitmap图片资源
  3. python编程自学能学会吗-Python能自学成功吗?
  4. OnlineJudge在线判题系统
  5. JVM之强引用、软引用、弱引用、虚引用
  6. 深入理解TCP/IP协议-TCP建立与终止连接
  7. 计算机涉及数学知识点,初二数学知识点归纳
  8. 人类为什么没有尾巴?这个跳跃基因抹去了人类的尾巴,并带来了额外风险
  9. (44)System Verilog数组逻辑运算
  10. 不服气不行,同样是码农,字节程序员的年薪居然达247万
  11. 大数据技术是“地球的神经系统”
  12. Picasso源码的简单解析(二)
  13. Django 配置App特定类的富文本编辑器
  14. 嵌入式 博客导航大牛群集
  15. 服务器自带软件怎么样卸载,如何安装和卸载远程服务器管理工具
  16. 半导体器件物理【7】固体量子 —— 统计力学
  17. 微积分-求导必背公式
  18. C++友元(友元函数)
  19. 划词翻译脚本--AutoHotkey
  20. 安科瑞变电站综合自动化系统 在安庆市静脉产业园应用

热门文章

  1. 防范ARP欺骗病毒攻击
  2. Scrum的七宗罪和其他的敏捷反模式
  3. D3D11 MD5骨骼动画模型的加载
  4. 半导体物理-Threshold Voltage Model for FinFET
  5. CUDA学习资源整合
  6. 10个java调试技巧
  7. 如何搭建一套指标体系?
  8. CMake加入第三方库
  9. 2023养老展,中福协养老展,中国国际养老服务业博览会
  10. 真实生活的记录:我三年的外企生涯(10) 出处:天涯虚拟社区