OpenCV常见库函数(下):轮廓、变换、video类、输入输出
寻找/画出轮廓
floodFill( )
漫水法,常用于寻找轮廓的预处理操作,和“画图”软件中“油漆桶”工具有相同的效果
seedPoint 起始像素点
newVal 重绘像素区域的新的填充值(颜色)
rect 设置floodFill函数将要重绘区域的最小边界矩形区域
loDiff 当前选定像素与其连通区中相邻像素中的一个像素,或者与加入该连通区的一个seedPoint像素,二者之间的最大下行差异值。
upDiff 当前选定像素与其连通区中相邻像素中的一个像素,或者与加入该连通区的一个seedPoint像素,二者之间的最大上行差异值。
flags 操作标志符,此参数包含三个部分
eg
cv::floodFill(src, Point(50,300), Scalar(155, 255,55), &ccomp, Scalar(20, 20, 20),Scalar(20, 20, 20));
OpenCV中,轮廓是由STL风格的vector<>模板对象表示的,其中vector中的每个元素都编码了曲线上,下一点的位置信息。
findContour()
寻找二值图中的轮廓,并保存为一组点,算法类似于漫水法,遍历所有像素并查找相邻像素
void findContours(InputOutputArray image,OutputArrayOfArrays contours,int mode,int method,Point offset = Point());
contours:std::vector<std::vector<cv::Point>> contours
,是一个向量,并且是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。有多少轮廓,向量contours就有多少元素。
mode:int类型,定义轮廓的检索模式:
CV_RETR_EXTERNAL 只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略;
CV_RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1
CV_RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层;
CV_RETR_TREE 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
method:int类型,定义轮廓的近似方法
CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内
CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留;
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
eg
//获取轮廓点
std::vector<std::vector<cv::Point>> contours;
cv::findContours(binary_img,contours,cv::RETR_EXTERNAL,cv::CHAIN_APPROX_NONE);
drawContour():根据一组点画出轮廓
void cv::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())
contourIdx:轮廓索引 i,i > 0时,i是几就画 i 所对应的轮廓,如果 i < 0 就画出所有轮廓
color:轮廓线的颜色 用Scalar()存
thickness:轮廓线厚度,如果这个值为负值(例如:thickness=-1),则填充轮廓内部
lineType:轮廓线的连接方式,参见线条连接样式列表
hierarchy:关于层次结构的可选信息。仅当您只想绘制部分轮廓时,才需要此选项
maxLevel: 绘制轮廓的最大级别。如果为0,则仅绘制指定的轮廓。如果为1,函数将绘制该轮廓和所有嵌套轮廓。如果为2,则函数绘制轮廓、所有嵌套轮廓、所有的嵌套到嵌套轮廓等。此参数仅在有可用hierarchy时考虑
offset:可选轮廓偏移参数。将所有绘制的轮廓移动指定的偏移量=(dx,dy)
关于hierarchy 层级容器
contours 边界容器
将轮廓点转换为更容易处理的形状对象
minAreaRect(InputArray points)
//通过轮廓点,拟合出最小面积的RotatedRect
//返回值是RotatedRect类型
【RotatedRect类型】
三个重要属性 center、size、angle
boundingRect()
//通过轮廓点,找到其外接矩形Rect(水平)
fitEllipse()
//通过轮廓点,用最小二乘法拟合出一个外接椭圆,函数会返回椭圆的内接旋转矩形RotatedRect
minEnclosingCircle( )
//通过轮廓点,找到最小面积的包含圆(注意不是外接圆)
eg
int main() {
// 加载图像,转换为灰度图,应用阈值处理以获取二值图像
cv::Mat src = cv::imread("path_to_image.jpg", cv::IMREAD_GRAYSCALE);
cv::Mat binary;
cv::threshold(src, binary, 127, 255, cv::THRESH_BINARY);
// 查找轮廓
std::vector<std::vector<cv::Point>> contours;
cv::findContours(binary, contours, cv::RETR_EXTERNAL,cv::CHAIN_APPROX_SIMPLE);
// 绘制边界矩形:在别的图像上绘制
cv::Mat drawing = cv::Mat::zeros(binary.size(), CV_8UC3);
for (int i = 0; i < contours.size(); i++) {
cv::Rect rect = cv::boundingRect(contours[i]);
cv::rectangle(drawing, rect, cv::Scalar(0, 255, 0), 2, 8, 0);
}
// 显示结果
cv::imshow("Bounding Rectangles", drawing);
cv::waitKey(0);
return 0;
}
投影变换
一般用于把图像根据变换关系转化成正视图以便进行模板匹配、SVM匹配等操作
将二维图像转换为具有三维空间显示效果的图像,如全景拼接、图像透视矫正等
重映射:把一幅图像中某位置的像素放置到另一个图片指定位置的过程
//根据给定的映射(函数)改变图像中每个像素点的位置
remap(InputArray src, OutputArraydst, InputArray map1, InputArray map2,int interpolation)
//进行透视变换
void warpPerspective( InputArray src, OutputArray dst,
InputArray M, Size dsize,
int flags = INTER_LINEAR,
int borderMode = BORDER_CONSTANT,
const Scalar& borderValue = Scalar());
M:3x3的变换矩阵,Mat类型 需要使用getPerspectiveTransform()函数获取
dsize:输出图像的大小 Size(double,double)
//获得透视变换所需的矩阵(4个点)
getPerspectiveTransform(const Point2f src[], const Point2f dst[], int solveMethod = DECOMP_LU)
Point2f src[4] = {Point2f(160,300),Point2f(931,253),Point2f(105,787),Point2f(1000,859)};
Point2f dst[4] = {Point2f(0,0),Point2f(380,0),Point2f(0,360),Point2f(380,360)};
仿射变换
它保持了二维图形的平直性(直线经过变换之后依然是直线)和平行性(二维图形之间的相对位置关系保持不变,平行线依然是平行线,且直线上点的位置顺序不变)。
主要用于二维坐标到二维坐标之间的线性变换,如平移、旋转、缩放等
warpAffine(InputArray src, OutputArray dst, InputArray mat, Size dsize = Size(), int flags = INTER_LINEAR, int borderMode = BORDER_CONSTANT, Scalar borderValue = Scalar())//进行仿射变换
mat:2x3的变换矩阵 从getAffineTransform()
中获得
dsize:输出图像的大小,如果这个参数为 Size() ,则输出图像的大小将与输入图像相同。
//获得仿射变换所需的矩阵
getAffineTransform(InputArray src, OutputArray dst)
只需要三个点 线性变换+平移变换
只需确定6个参数
图像输入输出
imread( ) //根据路径读取一张图片
imwrite( ) //向对应路径写入一张图像
imreadmulti( ) //一次读取多张图片
Video视频模块
class VideoCapture():读取视频
构建一个视频捕获类,捕获的视频可以以每一帧图像的形式保存到Mat中
这个类能够通过get(),set()方法获取和设置一些相机参数
int main(){
//从文件中读取视频
cv::VideoCapture capture(const string& filename);
//filename:视频位置
//打开摄像头 索引0
cv::VideoCapture cap(0);
//检查是否成功打开摄像头
if(!cap.isOpened()){
std::cerr<<"Error..."<<std::endl;//cerr无缓冲输出,适合输出错误消息
return -1;
}
//获取并打印帧率
double fps=cap.get(cv::CAP_PROP_FPS);
std::cout<<"fps:"<<fps<<std::endl;
//设置帧率:不是所有摄像头都支持
cap.set(cv::CAP_PROP_FPS,30.0);
//创建窗口来显示视频帧
cv::namedWindow("Video Capture",cv::WINDOW_AUTOSIZE);
cv::Mat frame;
while(true){
//读取下一帧
cap>>frame;
//检查是否成功读取帧
if(frame.empty()){
std::cerr<<"Error..."<<std::endl;//cerr无缓冲输出,适合输出错误消息
break;
}
//显示帧
cv::imshow("Video Capture",frame);
//按下ESC结束程序
if(cv::waitKey(2)==27){
break;
}
}
//释放摄像头,关闭窗口
cap.release();
cv::destroyAllWindows();
return 0;
}
在使用 VideoCapture 类时,确保在程序结束时释放摄像头资源(使用 release() 方法)
并关闭所有OpenCV窗口(使用 destroyAllWindows() 方法)
class VideoWriter():录制视频
构建一个视频保存类,能够方便地保存视频,并且提供各种格式
在实验室时无法模拟赛场的光线环境,常常在比赛时录制相机第一视角的视频,以供之后测试使用
也可以把检测完的每一帧图片连成视频,保存下来,之后根据这个视频来查找问题、改进算法
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
//保存avi视频文件
void main(){
VideoCapture cap;
cap.open("d:\\input.avi");
Size sizeReturn = Size(cap.get(CV_CAP_PROP_FRAME_WIDTH),cap.get(CV_CAP_PROP_FRAME_HEIGHT));
VideoWriter writer("d:\\output.avi",cv::VideoWriter::fourcc('M', 'J', 'P', 'G'),cap.get(CV_CAP_PROP_FPS),sizeReturn,false);
if(!cap.isOpened())
return;
Mat frame;
while(true)
{
cap>>frame;
if(frame.empty())
break;
writer<<frame;//等同于writer.write(frame);
imshow("video", frame);
if(waitKey(20)>0)
break;
}
cout<<"write end!";
cap.release();
destroyAllWindows();
}
highgui模块
imshow()
指定窗口显示一张图片
waitKey(int delay=0)
等待用户按键输入
key默认为0:程序会一直等待用户按键输入,直到用户手动关闭窗口为止
if (waitKey(0) == 27) { // 如果用户按下 ESC 键,退出循环
break;
}
waitKey(500);//每500秒显示一次图像