OpenCV轮廓相关操作 C++_opencv hierarchyc++_cold暖的博客-CSDN博客

图像处理马老师写的文档超级详细

vscode使用

cmake

cmake常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cmake_minimum_required(VERSION 3.15)//用于指示最低cmake的版本

project(demo)


set(OpenCV_DIR /home/xing/opencv-4.5.3/build)//将4.5.3的路径添加到opencvdir中,以便于下面的findpackage进行搜索
# find_package(OpenCV REQUIRED)
find_package(OpenCV 4.5.3 REQUIRED)

#添加头文件
include_directories(${OpenCV_INCLUDE_DIRS})

add_executable(zuoye ${PROJECT_SOURCE_DIR}/zuoye.cpp)

#链接库文件
target_link_libraries(zuoye ${OpenCV_LIBS})


和opencv进行链接

【cmake学习】find_package 详解_仲夏夜之梦~的博客-CSDN博客

这块需要深入理解以下find_package

opencv常用函数

split

用于将多通道图片转换成单通道图片

实际上是bgr

1
void split(InputArray m, OutputArrayOfArrays mv);

InputArray:输入数组,也就是输入的图片

OutputArrayOfArrays:填函数的输出数组或者输出的vector容器。也就是有多个相同类型的容器

vector channels;

比如三通道的图片,容器channels中就有了三个类型为Mat的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

using namespace cv;
using namespace std;

void main(){
vector<Mat> channels;
Mat imageBlueChannel;
Mat imageGreenChannel;
Mat imageRedChannel;
Mat srcImage4 = imread("mogu.jpg");
imshow("【原始图】", srcImage4);

//把一个3通道图像转换成3个单通道图像
split(srcImage4, channels);
imageBlueChannel = channels.at(0);
imageGreenChannel = channels.at(1);
imageRedChannel = channels.at(2);

//显示单通道图像
imshow("【BlueChannel】", imageBlueChannel);
imshow("【GreenChannel】", imageGreenChannel);
imshow("【RedChannel】", imageRedChannel);

waitKey();
}

这里区分以下两种访问vector的方法

.at()

最主要的区别就是.at()会额外检查访问是否越界,如果越界,会抛出exception,所以使用.at()程序运行速度较慢

二者优点

  • 一般来说用效率更高,尤其是需要对索引值进行复杂的计算或者单纯不需要检查是否越界时。
  • 更好的做法是默认用.at(),这样代码尽管很慢但不会产生bug;更适合对程序效率要求比较高的场景。

缺点

索引值越界时程序不会报错,但会一路莽下去,在向量非空的情况下,即使下标越界,也有可能对应的内存是可读写的,至于读到的是什么内容,或者写到什么地方,就是随机事件了。

由于不做边界检查,哪怕越界了也会返回一个引用,当然这个引用是错误的引用,如何不小心调用了这个引用对象的方法,会直接导致应用退出。

threshold

Opencv中的Threshold用法(二值化图像)_子翊寒的博客-CSDN博客

1
retVal, thresh = cv2.threshold(需要处理的图像,阈值,分配的值,阈值处理模式选择)

输入

参数1,需要处理的图像:注意,需要处理的图像需要被转成灰度图像(后面小试一下部分会讲解)

参数2,阈值:设定阈值,分界值

参数3,分配的值:如果一个像素的灰度值,大于或者小于(取决于参数4的选择)阈值,会被赋予分配的值

参数4,阈值处理模式选择:(下面讲解)

cv2.THRESH_BINARY

cv2.THRESH_BINARY_INV

cv2.THRESH_TRUNC

cv2.THRESH_TOZERO

cv2.THRESH_TOZERO_INV

输出

retVal,在这些用法中,可以简单理解为返回输入参数中的阈值。

thresh,返回处理后的图像。

阈值处理模式:

  1. THRESH—BINARY
    1. 如果像素值大于阈值,像素值就会被设为参数3
    2. 小于等于阈值,设定为0
  2. cv2.THRESH_BINARY_INV
    1. 这个是上面一种情况的反转
    2. 如果像素值大于阈值,像素值为0
    3. 小于等于阈值,设定为参数3
  3. cv2.THRESH_TRUNC
    1. 如果像素大于阈值,设定为阈值
    2. 小于等于阈值,保持原像素值
  4. cv2.THRESH_TOZERO
    1. 大于阈值,保持原像素值
    2. 小于等于,设定为0
  5. cv2.THRESH_TOZERO_INV
    1. 与上一种相反
    2. 大于阈值,设定为0
    3. 小于等于,保持原像素值

像素之间的几何运算and/or/not

OpenCV:像素之间的几何运算(and/or/not)_opencv 两张图数值求or_长大的小蚂蚁的博客-CSDN博客

getStructuringElement

用于创建卷积核

卷积就是图像的每个像素点按卷积核进行的赋值,核

1
2
3
4
5
6
7
8
kernel = cv2.getStructuringElement(a,b,c): # 得到一个结构元素(卷积核)。主要用于后续的腐蚀、膨胀、开、闭等运算。
因为这些运算都是依赖于卷积核的,不同的卷积核(形状、大小)对图形的腐蚀、膨胀操作效果不一样

输入参数:
a设定卷积核的形状、b设定卷积核的大小、c表示描点的位置,一般 c = 1,表示描点位于中心。
返回值:
返回指定形状和尺寸的结构元素(一般是返回一个矩形)、也就是腐蚀/膨胀用的核的大小。

c表示矛点也就是卷积核的中心。

如果 anchor 设置为 (1, 1),则表示结构元素的锚点位于中心,而如果设置为 (0, 0),则表示锚点位于结构元素的左上角。

它会移动,覆盖图片中所有的像素

数字图像处理:理解什么是卷积(滤波)、卷积核以及相关参考资料-CSDN博客

morphologyEx

文档参考:OpenCV morphologyEx、erode、dilate、getStructuringElement (形态学算子)_opencv的getstructuingelement函数的作用_MechMaster的博客-CSDN博客

OpenCV: Image Filtering

强大的opencv库

可以直接进行八种图像形态学操作

它可以通过调整参数 op 完成八种操作:腐蚀、膨胀、开运算、闭运算、顶帽、黑帽、梯度、击中击不中。

1
2
3
4
5
6
7
8
9
10
11
void morphologyEx(
InputArray src,
OutputArray dst,
int op,
InputArray kernel,
Point anchor = Point(-1, -1),//意思是以内核的中心点为茅点。
int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue()
);

1
2
//腐蚀
morphologyEx(m_SrcImg2, m_DstImg, MORPH_ERODE, elementEllipse, Point(-1, -1), m_nIterations);

.get()

Videocapture类下面的成员函数

findcontours

OpenCV轮廓相关操作 C++_opencv hierarchyc++_cold暖的博客-CSDN博客

1
2
3
4
5
6
7
8
9

void cv::findContours (
InputOutputArray image,
OutputArrayOfArrays contours,
OutputArray hierarchy,
int mode,
int method,
Point offset = Point()
)

第三个参数表示层级每个轮廓对应的属性(例如拓扑信息)

mode表示轮廓的检索模式

method表示寻找轮廓时使用的近似算法

drawContours

画图用的

1
2
3
4
5
6
void drawContours( InputOutputArray image, InputArrayOfArrays contours,
int contourIdx, const Scalar& color,
int thickness = 1, int lineType = LINE_8,
InputArray hierarchy = noArray(),
int maxLevel = INT_MAX, Point offset = Point() );

  1. image是输入输出图片;
  2. contours是cv::findContours()检测到的所有轮廓;
  3. contourIdx是取第几个轮廓;
  4. color是轮廓的颜色;
  5. thickness是轮廓的线宽(注意这个参数为-1时会填充整个轮廓);
  6. lineType是线的形式;
  7. hierarchy为关于层级的可选参数,只有绘制部分轮廓时才会用到;
  8. maxLevel为绘制轮廓的最高级别,这个参数只有hierarchy有效的时候才有效(maxLevel=0,绘制与输入轮廓属于同一等级的所有轮廓即输入轮廓和与其相邻的轮廓、maxLevel=1, 绘制与输入轮廓同一等级的所有轮廓与其子节点、maxLevel=2,绘制与输入轮廓同一等级的所有轮廓与其子节点以及子节点的子节点);
  9. offset为轮廓偏移量,配合ROI使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

int main() {
cv::Mat src = cv::imread("1_0.png", -1);

std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); //只找最外层轮廓

cv::Mat dst;
cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);

for (int i = 0; i < contours.size(); ++i) { //绘制所有轮廓
cv::drawContours(dst, contours, i, cv::Scalar(0, 255, 0), -1); //thickness为-1时为填充整个轮廓
}

cv::imshow("dst", dst);
cv::waitKey();

cv::destroyAllWindows();
return 0;
}

countourArea()

获取轮廓面积的

1
2
3
double contourArea( InputArray contour, bool oriented = false );
double arcLength( InputArray curve, bool closed );

contour表示单个轮廓构成的点集;

oriented为面积的方向性,true表示面积具有方向性,false表示不具有方向性。

minAreaRect

RotatedRect这个类包含了很多成员函数

用于获取最小外接矩形的四个角点坐标,还包含这个图形相对于x轴的旋转角度

1
2
3
4
RotatedRect minAreaRect( InputArray points );
minAreaRects[i].angle/表示角度
minAreaRects[i].points(pts[])//返回四个角点,存储在pts数组中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

int main() {
cv::Mat src = cv::imread("1_0.png", -1);

std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE); //只找最外层轮廓

cv::Mat dst; //用于绘制结果
cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);

std::vector<cv::RotatedRect> minAreaRects(contours.size()); //存储轮廓的最小外接矩形

cv::Point2f ps[4]; //外接矩形四个端点的集合
for (int i = 0; i < contours.size(); ++i) { //遍历所有轮廓
minAreaRects[i] = cv::minAreaRect(contours[i]); //获取轮廓的最小外接矩形
minAreaRects[i].points(ps); //将最小外接矩形的四个端点复制给ps数组

for (int j = 0; j < 4; j++) { //绘制最小外接轮廓的四条边
line(dst, cv::Point(ps[j]), cv::Point(ps[(j + 1) % 4]), cv::Scalar(0, 255, 0), 2);
cv::putText(dst, std::to_string(j), ps[j], 0, 1, cv::Scalar(0, 0, 255), 2); //将点集按顺序显示在图上
}

printf("minAreaRects[%d]的旋转角度为%f\n", i, minAreaRects[i].angle); //输出旋转角度
}

cv::imshow("dst", dst);
cv::waitKey();

cv::destroyAllWindows();
return 0;
}

输出:
minAreaRects[0]的旋转角度为90.000000
minAreaRects[1]的旋转角度为52.678967

boundingRect

返回一个矩形变量

1
Rect boundingRect( InputArray array );

滤波器

图像形态学滤波

图像处理 写得很详细很易懂

腐蚀

getStructuring

腐蚀的步骤就是用卷积核B的描点(此处就是中心点),来对齐A中的每一个小方格,然后选取卷积核B的方格中的数据的最小值,意思就是当B的描点对齐A的边界方格的时候,那么B的其他四个方格可能位于A图像中的0像素点,那么最小值就是0,那么就把卷积核B的描点对应的A中的小方格设为0,这就导致使用腐蚀操作后,我我们能看到的白色区域减少的原因。

膨胀

原理与腐蚀操作一样,只不过是取最大像素值,其他地方没差别。

开闭运算

开运算:先腐蚀,再膨胀,可清除一些亮的像素点,放大局部低亮度的区域,在纤细点处分离物体,并且在平滑较大物体的边界的同时不明显改变其面积。

闭运算:先膨胀,再腐蚀,可清除一些暗的像素点,即排除小型黑洞(黑色区域)。

基于边缘的分割方式

基于边缘的分割方法_边缘分割_大大Cameo的博客-CSDN博客

Canny

它通过计算图像的梯度来检测边缘。

Sobel

其原理是将一个二维的图像与一个Sobel算子进行卷积,从而得到图像的边缘信息。

Roberts

它是基于交叉差分的梯度算法,通过局部差分计算检测边缘线条。

Prewitt

对于复杂的图像,采用Roberts算子不能较好地得到图像的边缘,需要采用更加复杂的3X3的算子,如Prewitt算子

Log

该算子是通过先对图像进行高斯滤波,然后计算拉普拉斯算子来检测图像中的边缘。

总结

Prewitt算子和Sobel算子:Prewitt算子和Sobel算子都是基于梯度的边缘检测算子,它们的模板非常相似,只是在模板中的权值不同。Prewitt算子的模板是由3x3的权值矩阵组成,而Sobel算子的模板则是由5x5的权值矩阵组成。此外,Sobel算子在计算梯度时采用的是加权平均值,因此在图像处理中更为常用。

Roberts算子:Roberts算子是一种比较简单的边缘检测算子,其模板是由2x2的权值矩阵组成,比Prewitt和Sobel算子的模板更小。它的计算速度很快,但是它对噪声比较敏感,检测到的边缘也比较粗糙。

Canny算子:Canny算子是一种非常经典的边缘检测算法,它在检测到边缘时具有较高的准确性和稳定性。与其他算子不同,Canny算子包括多个步骤,包括高斯平滑、计算梯度、非极大值抑制、双阈值处理和边缘链接等。

LOG算子:LOG算子是一种基于高斯滤波的边缘检测算子,它先对图像进行高斯平滑,然后使用拉普拉斯算子计算边缘。与其他算子不同,LOG算子能够检测到更细微的边缘,但是由于使用了高斯滤波,它可能会丢失一些细节信息。

总的来说,不同的边缘检测算子适用于不同的场景和需求。选择合适的算子需要根据具体的情况进行选择和调整,以得到最佳的边缘检测效果。

基于边缘的分割方法_边缘分割_大大Cameo的博客-CSDN博客

常用几何变换

(opencv)图像几何变换——缩放_opencv缩放图像-CSDN博客

缩放

官方文档

OpenCV 图像缩放:cv.resize() 函数详解_cv::resize_零度蛋花粥的博客-CSDN博客

1
2
3
void resize( InputArray src, OutputArray dst,
Size dsize, double fx = 0, double fy = 0,
int interpolation = INTER_LINEAR );

参数说明

  1. InputArray类型的src,输入图像,如Mat类型。
  2. OutputArray类型的dst,输出图像,其尺寸由第三个参数dsize决定。
  3. Size类型的dsize,输出图像的大小。如果它等于,则大小等于Size(round(fxsrc.cols),round(fysrc.rows))。
  4. double类型的fx,沿水平轴的缩放系数,默认值为0,当其等于0时,其数值由该式计算:(double)dsize.width/src.cols;
  5. double类型的fy,沿垂直轴的缩放系数,默认值为0,当其等于0时,其数值由该式计算:(double)dsize.height/src.rows;
  6. int类型的interpolation,表示不同的插值方式,默认为INTER_LINEAR(线性插值)。

其中,可选的插值方式有:

INTER_NEAREST,最邻近插值。

INTER_LINEAR,线性插值。

INTER_CUBIC,三次样条插值,适合放大图像用。

INTER_AREA,区域插值,适合缩小图像用。

INTER_LANCZOS4,Lancazos插值。

INTER_LINEAR_EXACT ,位精确双线性插值。

INTER_MAX,内插码掩模。

WARP_FILL_OUTLIERS,官方解释:flag, fills all of the destination image pixels. If some of them correspond to outliers in the source image, they are set to zero 。

WARP_INVERSE_MAP,官方解释:flag, inverse transf4ormation。

1
resize(frame,rframe,Size(0, 0),0.5,0.5,INTER_AREA);

平移

旋转

仿射

常用操作

循环播放视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

int main() {

cv::Mat frame;
cv::VideoCapture cap("001.mp4");

int frame_counter = 0;
while (true)
{
cap >> frame;
if (!frame.data)
{
printf("Image not loaded");
return -1;
}

frame_counter += 1;
if (frame_counter == int(cap.get(cv::CAP_PROP_FRAME_COUNT))){
frame_counter = 0;
cap.set(cv::CAP_PROP_POS_FRAMES, 0);
}

cv::imshow("demo", frame);
char(key)=(char)cv::waitKey(1);
if(key==27)
break;
}


return 0;
}

1
2
3
4
5
frame_counter += 1;
if (frame_counter == int(cap.get(cv::CAP_PROP_FRAME_COUNT))){
frame_counter = 0;
cap.set(cv::CAP_PROP_POS_FRAMES, 0);
}

这里需要了解Videocapture类的成员函数。如set get

官方文档

cap.get(cv::CAP_PROP_FRAME_COUNT)是来获得视频的最大帧数。也就是说如果当前帧率达到最大帧,就会把counter归零,同时把cap 置回第0帧的位置

通过外接轮廓获取面积 进行图像筛选

使用api

findContours用于获取轮廓点集

1
2
3
4
5
6
7
void findContours( InputArray image, OutputArrayOfArrays contours,
OutputArray hierarchy, int mode,
int method, Point offset = Point());

void findContours( InputArray image, OutputArrayOfArrays contours,
int mode, int method, Point offset = Point());

boundRect[i]=minAreaRect(contours[i]);

boundRect[i].points(ps);

获取到最小外接矩形

不过这个类比较特殊,他无法直接通过rectangle画出

他必须使用四个角点分别画出

1
2
3
4
for (int j = 0; j <4 ; j++)
{
line(rframe,ps[j],ps[(j+1)%4],Scalar(0,255,255),2);//绘制矩形
}

不过面积可以直接通过这个类下面的一个成员函数直接获得

视频文件读写

VideoWriter类

1
VideoWriter(const String& filename, int fourcc, double fps, Size frameSize, bool isColor = true);
  • filename:要保存的视频文件的名称。
  • fourcc:FourCC(Four Character Code)是一个四字符的编解码器标识码,用于指定视频编码器的类型。例如,cv::VideoWriter::fourcc(‘M’, ‘J’, ‘P’, ‘G’) 表示使用MJPG编码器。
  • fps:视频的帧率(帧每秒)。
  • frameSize:视频帧的大小,通常使用 cv::Size(width, height) 来指定宽度和高度。
  • isColor:一个布尔值,指定视频是否为彩色。默认为 true,表示彩色视频。

方法:

  • open:打开视频文件以准备写入帧。
  • write:将一帧图像写入视频文件。
  • release:完成写入操作并关闭视频文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <opencv2/opencv.hpp>

int main() {
cv::VideoCapture cap(0); // 打开摄像头
if (!cap.isOpened()) {
std::cerr << "Error: Camera not found!" << std::endl;
return -1;
}

int fourcc = cv::VideoWriter::fourcc('M', 'J', 'P', 'G'); // MJPG编码器
double fps = 30.0;
cv::Size frameSize(cap.get(cv::CAP_PROP_FRAME_WIDTH), cap.get(cv::CAP_PROP_FRAME_HEIGHT));
cv::VideoWriter writer("output.avi", fourcc, fps, frameSize, true); // 打开视频文件以写入

if (!writer.isOpened()) {
std::cerr << "Error: Could not open video file for writing!" << std::endl;
return -1;
}

cv::Mat frame;
while (true) {
cap >> frame; // 从摄像头读取一帧
if (frame.empty()) {
break;
}

writer.write(frame); // 将帧写入视频文件

cv::imshow("Video", frame);
if (cv::waitKey(30) == 27) {
break; // 按下ESC键退出循环
}
}

writer.release(); // 完成写入并关闭视频文件
return 0;
}

滑动框

1
2
3
4
5
6
7
namedWindow("trackbar", WINDOW_AUTOSIZE);
createTrackbar("flag_gray", "trackbar", &flag_gray, 255);
while(True)
{
threshold(redminblue, out,flag, 255, THRESH_BINARY);

}
1
2
3
void cv::namedWindow	(	const String & 	winname,
int flags = WINDOW_AUTOSIZE
)

OpenCV: High-level GUI

flags Flags of the window. The supported flags are: (cv::WindowFlags
)

1
2
3
4
5
6
7
8
9

int cv::createTrackbar (
const String & trackbarname,
const String & winname,
int * value,
int count,
TrackbarCallback onChange = 0,
void * userdata = 0
)

参数

跟踪栏名称 创建的跟踪栏的名称。
温名 将用作所创建跟踪栏的父级的窗口的名称。
价值 指向整数变量的可选指针,其值反映滑块的位置。创建时,滑块位置由此变量定义。
计数 滑块的最大位置。最小位置始终为 0。
在更改 指向每次滑块更改位置时要调用的函数的指针。这个函数应该原型化为 void Foo(int,void*);,其中第一个参数是跟踪栏位置,第二个参数是用户数据(请参阅下一个参数)。如果回调是 NULL 指针,则不调用回调,只更新值。
用户数据 按原样传递到回调的用户数据。它可用于处理跟踪栏事件,而无需使用全局变量。

问题

我在c_cpp_properties中设置本地opencv的路径,但是最后编译过程中还是使用cmakelist中写好的版本

1
2
3
4
5
6
"includePath": [
"${workspaceFolder}/**",
// "/home/xing/opencv-4.5.3",
// "/home/xing/opencv-4.5.3/include",//头文件变成红色
"/usr/include/opencv4"//只有这个会让opencv的头文件变成白色
],