函数说明
1. void findContours
1.1 概述
void findContours//提取轮廓,用于提取图像的轮廓
(
InputOutputArray image,//输入图像,必须是8位单通道图像,并且应该转化成二值的
OutputArrayOfArrays contours,//检测到的轮廓,每个轮廓被表示成一个point向量
OutputArray hierarchy,//可选的输出向量,包含图像的拓扑信息。其中元素的个数和检测到的轮廓的数量相等
int mode,//说明需要的轮廓类型和希望的返回值方式
int method,//轮廓近似方法
Point offset = Point()
)
1.2 参数mode的意义
第二个参数:contours,定义为“vector<vector<Point>> contours”,是一个向量,并且是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。有多少轮廓,向量contours就有多少元素。
第三个参数:hierarchy,定义为“vector<Vec4i> hierarchy”,先来看一下Vec4i的定义:
typedef Vec<int, 4> Vec4i;
Vec4i是Vec<int,4>的别名,定义了一个“向量内每一个元素包含了4个int型变量”的向量。
所以从定义上看,hierarchy也是一个向量,向量内每个元素保存了一个包含4个int整型的数组。
向量hiararchy内的元素和轮廓向量contours内的元素是一一对应的,向量的容量相同。
hierarchy向量内每一个元素的4个int型变量——hierarchy[i][0] ~hierarchy[i][3],分别表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。如果当前轮廓没有对应的后一个轮廓、前一个轮廓、父轮廓或内嵌轮廓的话,则hierarchy[i][0] ~hierarchy[i][3]的相应位被设置为默认值-1。
第四个参数:int型的mode,定义轮廓的检索模式:
取值一:CV_RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略
取值二:CV_RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1,具体下文会讲到
取值三:CV_RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层
取值四:CV_RETR_TREE, 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
第五个参数:int型的method,定义轮廓的近似方法:
取值一:CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内
取值二:CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留
取值三和四:CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
mode的值决定把找到的轮廓如何挂到轮廓树节点变量(h_prev, h_next, v_prev, v_next)上。图2展示了四种可能的mode值所得到的结果的拓扑结构。
图2 轮廓连接方法每种情况下,结构都可以看成是被横向连接(h_prev, h_next)联系和被纵向连接(v_prev, v_next)不同层次。
CV_RETR_EXTERNAL 只检测出最外轮廓即c0。图2中第一个轮廓指向最外的序列,除此之外没有别的连接。
CV_RETR_LIST 检测出所有的轮廓并将他们保存到表(list)中,图2中描绘了这个表,被找到的9条轮廓相互之间由h_prev和h_next连接。这里并没有表达出纵向的连接关系,没有使用v_prev和v_next.
CV_RETR_COMP 检测出所有的轮廓并将他们组织成双层的结构,第一层是外部轮廓边界,第二层边界是孔的边界。从图2可以看到5个轮廓的边界,其中3个包含孔。最外层边界c0有两个孔,c0之间的所有孔相互间由h_prev和h_next指针连接。
CV_RETR_TREE 检测出所有轮廓并且重新建立网状的轮廓结构。图2中,根节点是最外层的边界c0,c0之下是孔h00,在同一层中与另一个孔h01相连接。同理,每个孔都有子节点(相对于c000和c010),这些子节点和父节点被垂直连接起来。这个步骤一直持续到图像最内层的轮廓,这些轮廓会成为树叶节点。
1.3 method的五个值
CV_CHAIN_CODE 用freeman链码输出轮廓,其他方法输出多边形(顶点的序列)。
CV_CHAIN_APPROX_NONE将链码编码中的所有点转换为点。
CV_CHAIN_APPROX_SIMPLE压缩水平,垂直或斜的部分,只保存最后一个点。
CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_QPPROX_TC89_KCOS使用Teh-Chin链逼近算法中的一个。
CV_LINK_RUNS与上述的算法完全不同,连接所有的水平层次的轮廓。
2. void drawContours
2.1 概述
void drawContours//绘制轮廓,用于绘制找到的图像轮廓
(
InputOutputArray image,//要绘制轮廓的图像
InputArrayOfArrays contours,//所有输入的轮廓,每个轮廓被保存成一个point向量
int contourIdx,//指定要绘制轮廓的编号,如果是负数,则绘制所有的轮廓
const Scalar& color,//绘制轮廓所用的颜色
int thickness = 1, //绘制轮廓的线的粗细,如果是负数,则轮廓内部被填充
int lineType = 8, /绘制轮廓的线的连通性
nputArray hierarchy = noArray(),//关于层级的可选参数,只有绘制部分轮廓时才会用到
int maxLevel = INT_MAX,//绘制轮廓的最高级别,这个参数只有hierarchy有效的时候才有效
//maxLevel=0,绘制与输入轮廓属于同一等级的所有轮廓即输入轮廓和与其相邻的轮廓
//maxLevel=1, 绘制与输入轮廓同一等级的所有轮廓与其子节点。
//maxLevel=2,绘制与输入轮廓同一等级的所有轮廓与其子节点以及子节点的子节点
Point offset = Point()
)
3. getAffineTransform
3.1 概述
Mat getAffineTransform
(
InputArray src,//表示输入的三个点
InputArray dst//表示输出的三个点
)
该函数主要用于生成仿射变换矩阵
- 一个任意的仿射变换都能表示为 乘以一个矩阵(线性变换) 接着再 加上一个向量 (平移).
- 综上所述, 我们能够用仿射变换来表示:
- 旋转 (线性变换)
- 平移 (向量加)
- 缩放操作 (线性变换)
- 你现在可以知道, 事实上, 仿射变换代表的是两幅图之间的关系
- 我们通常使用 2×3 的矩阵来表示仿射变换.
考虑到我们要使用矩阵 A 和 B 对二维向量做变换, 所以也能表示为下列形式:
或者
这里补充说明下:getAffineTransform 函数的输入参数是两个三点数组,输出是一个两行三列的变换矩阵,因此我们可以通过间接定义三点对应关系的方式通过函数得到变换矩阵,也可以通过直接设计变换矩阵。
简单说明下如果直接定义变换矩阵(平移)的方式,通过分别指定x方向和y方向上的平移量tx和ty,平移矩阵的形式如下:
映射关系三角形:
4. getRotationMatrix2D
4.1 概述
Mat getRotationMatrix2D//主要用于获得图像绕着 某一点的旋转矩阵
(
Point2f center,//表示旋转的中心点
double angle,//表示旋转的角度
double scale//图像缩放因子
)
图像的旋转具体实现分为两步:先根据旋转角度和旋转中心获取旋转矩阵;然后根据旋转矩阵进行仿射变换,即可实现任意角度和任意中心的旋转效果。旋转矩阵的形式如下:
其中,
5. warpAffine
5.1 概述
void warpAffine//主要用于获得图像绕着 某一点的旋转矩阵
(
InputArray src,//输入的图像
OutputArray dst,//输出的图像
InputArray M,//透视变换的矩阵
Size dsize,//输出图像的大小
int flags,//输出图像的插值算法标识符,有默认值INTER_LINEAR
INTER_NEAREST = 0, //最近邻插值
INTER_LINEAR = 1, //双线性插值
INTER_CUBIC = 2, //双三次插值
INTER_AREA = 3, //区域插值,使用象素关系重采样。当图像缩小时候,该方法可以避免波纹出现。当图像放大时,类似于 INTER_NEAREST 方法
INTER_LANCZOS4 = 4, //Lanczos插值(超过8×8像素邻域的Lanczos插值)
INTER_MAX = 7, // 最大值插值
WARP_FILL_OUTLIERS = 8, //填充所有输出图像的象素
WARP_INVERSE_MAP = 16 //逆变换
int borderMode,//图像边界的处理方式:abcdefgh是原图数据,|是图像边界,为原图加边
BORDER_CONSTANT = 0, //!< `iiiiii|abcdefgh|iiiiiii` with some specified i 常量
BORDER_REPLICATE = 1, //!< `aaaaaa|abcdefgh|hhhhhhh` 重复
BORDER_REFLECT = 2, //!< `fedcba|abcdefgh|hgfedcb` 反射
BORDER_WRAP = 3, //!< `cdefgh|abcdefgh|abcdefg` 外包装
BORDER_REFLECT_101 = 4, //!< `gfedcb|abcdefgh|gfedcba`
BORDER_TRANSPARENT = 5, //!< `uvwxyz|absdefgh|ijklmno`
BORDER_REFLECT101 = BORDER_REFLECT_101, //!< same as BORDER_REFLECT_101
BORDER_DEFAULT = BORDER_REFLECT_101, //!< same as BORDER_REFLECT_101
BORDER_ISOLATED = 16 //!< do not look outside of ROI
const Scalar & borderValue//边界的颜色设置,一般默认是0
)
代码部分
1. 仿射变换
#include <opencv2/opencv.hpp>
#include <stdio.h>
#include <stdlib.h>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
//获取图像
Mat img =imread("C.jpg",-1);
//压缩下
pyrDown(img,img,Size(img.cols/2, img.rows/2),4);
//getAffineTransform-----通过间接定义三点关系的方式得到变换矩阵
Point2f img_point[3];
Point2f out_point[3];
Mat changeMat1( 2, 3, CV_32FC1 ,Scalar::all(0));
Mat out1, out2, out3, out4;
img_point[0] = Point2f( 0,0 );
img_point[1] = Point2f( img.cols, 0 );
img_point[2] = Point2f( 0, img.rows );
out_point[0] = Point2f( img.cols*0.0, img.rows*0.33 );
out_point[1] = Point2f( img.cols*0.85, img.rows*0.25 );
out_point[2] = Point2f( img.cols*0.15, img.rows*0.7 );
changeMat1 = getAffineTransform( img_point, out_point );
// 对源图像应用上面求得的仿射变换
warpAffine( img, out1, changeMat1, img.size(),INTER_LINEAR,BORDER_REFLECT );//双线性插值,反射
warpAffine( img, out2, changeMat1, img.size(),INTER_LINEAR );//双线性插值,默认填充黑色
imshow("s",out1);imwrite("反射.jpg",out1);
imshow("sw",out2);imwrite("黑边.jpg",out2);
//getAffineTransform-----通过直接定义三点关系的方式得到变换矩阵
Mat changeMat2( 2, 3, CV_32FC1 ,Scalar::all(0));
changeMat2.at<float>(0, 0) = 1;
changeMat2.at<float>(0, 2) = 200; //水平平移量
changeMat2.at<float>(1, 1) = 1;
changeMat2.at<float>(1, 2) = 100; //竖直平移量
warpAffine( img, out3, changeMat2, img.size(),INTER_LINEAR );//双线性插值,默认填充黑色
imshow("ssd",out3);imwrite("平移.jpg",out3);
//getRotationMatrix2D
//旋转角度
double angle = 45;
//旋转中心
cv::Point2f center(img.cols/2., img.cols/2.);
Mat changeMat3 = getRotationMatrix2D(center, angle, 1);
warpAffine(img, out4, changeMat3, img.size());
imshow("sdasd",out4);imwrite("旋转.jpg",out4);
waitKey();
}
原图
反射.jpg
黑边.jpg
平移.jpg
旋转.jpg
2. RotatedRect区域矫正及获取
#include <opencv2/opencv.hpp>
#include <stdio.h>
#include <stdlib.h>
using namespace cv;
using namespace std;
char file[] = "1.jpg";
int main(int argc, char** argv)
{
//获取图像
Mat img =imread(file,-1);
Mat bin;
cvtColor(img,bin,CV_BGR2GRAY);
threshold(bin,bin,10,250,THRESH_OTSU + THRESH_BINARY);
//找到初始轮廓,并画出来。绿色
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;//储存各轮廓的继承父子逻辑关系
findContours(bin, contours, hierarchy, RETR_LIST, CHAIN_APPROX_SIMPLE, Point(0, 0));
drawContours(img,contours,-1,Scalar(0,255,0),1,8,hierarchy);
imshow("contours",img);imwrite("contours.jpg",img);
//通过 contourArea 函数将面积大于额定的轮廓复制出来。
//这里注意下,复制时 hierarchy 参数就算了,如果有他最终绘制结果会出错。
double area;
vector<vector<Point> > contours2;
vector<Vec4i> hierarchy2;//为了对比效果还是加上了
int k = 0;
for (unsigned int i=0;i<contours.size();i++)
{
area = contourArea(contours[i]);
if (area < 500)
{
continue;
}
contours2.push_back(contours[i]);
hierarchy2.push_back(hierarchy[i]);
}
//新轮廓为白线部分
drawContours(img,contours2,-1,Scalar(255,255,255),1,8);
imshow("contours2_1",img);imwrite("contours2—1.jpg",img);
//错误轮廓为黑色斑点部分,我猜是因为逻辑的继承关系错乱导致的吧
drawContours(img,contours2,-1,Scalar(0,0,0),CV_FILLED,8,hierarchy2);
imshow("contours2_2",img);imwrite("contours2—2.jpg",img);
//用 boundingRect 找到最贴合正矩形
vector<Rect> rects;
for (unsigned int i =0;i<contours2.size();i++)
{
Rect rect = boundingRect(contours2[i]);
rects.push_back(rect);
}
//用 rectangle 函数绘制矩形
for each (Rect rect in rects)
{
rectangle(img, rect, Scalar(0,0,255));
}
imshow("rectangle",img);imwrite("rectangle.jpg",img);
//用 RotatedRect 找到最贴合旋转矩形
vector<RotatedRect> rotate_rects;
for (unsigned int i =0;i<contours2.size();i++)
{
RotatedRect rotate_rect = minAreaRect(contours2[i]);
rotate_rects.push_back(rotate_rect);
}
//用 line 函数圈出 RotatedRect 区域
for each (RotatedRect rotate_rect in rotate_rects)
{
Point2f vertices[4];
rotate_rect.points(vertices);
for (int i = 0; i < 4; i++)
line(img, vertices[i], vertices[(i+1)%4], Scalar(255,0,0));
}
imshow("RotatedRect",img);imwrite("RotatedRect.jpg",img);
//旋转矩形两种方法校正:1.是项目上用的整个图片校正,剪切;2.剪切出有用部分,再校正,再剪切。
//其实两种方法差不多,就当练习吧
//第一种方法
Mat img2 = imread(file,-1);//重新读取一遍原图,用于切割
for each (RotatedRect rotate_rect in rotate_rects)
{
//构造 mask 和输出 out1
Mat mask1 = Mat::zeros(img.size(), CV_8UC1);
Mat out;
获取四个顶点坐标,然后放到容器中,其实就是构造一个伪“contours”
Point2f vertices[4];
rotate_rect.points(vertices);
vector< vector<Point> > co_ordinates;
co_ordinates.push_back(vector<Point>());
co_ordinates[0].push_back(vertices[0]);
co_ordinates[0].push_back(vertices[1]);
co_ordinates[0].push_back(vertices[2]);
co_ordinates[0].push_back(vertices[3]);
//图片区域绘制成白色的
drawContours( mask1,co_ordinates,0, Scalar(255),CV_FILLED, 8 );
//通过模板复制,其实网上有很多方法,我觉得这个最简单。
img2.copyTo(out,mask1);
//图片的旋转中心以及旋转角度
Point2f center = rotate_rect.center;
float angle = rotate_rect.angle;
//旋转角度调整
if(90 <= angle && angle < 45)
{
angle -= 90;
}else if(-90 <= angle && angle <-45)
{
angle += 90;
}
//得到变换矩阵
Mat rotate_matrix = getRotationMatrix2D(center,angle,1);
//旋转图像
warpAffine(out, out, rotate_matrix, out.size(),1, 0, Scalar(0));//仿射变换
//剪切矫正部分
vector<vector<Point>> contours01;
vector<Vec4i> hierarchy01;
Mat gray_temp;
cvtColor(out,gray_temp,CV_BGR2GRAY);
findContours(gray_temp,contours01,hierarchy01,RETR_LIST,CHAIN_APPROX_SIMPLE);
for (unsigned int i=0;i<contours01.size();i++)
{
//过滤小的内部轮廓
if (contourArea(contours01[i])<450)
{
continue;
}
//获取剪切矩形
Rect rect = boundingRect(contours01[i]);
Mat result = out(rect);
long double name = rand()*100;
imwrite("01---"+to_string(name)+".jpg", result);
}
}
//第二种方法
for each (RotatedRect rotate_rect in rotate_rects)
{
Mat mask2(img.rows, img.cols, CV_8UC1, cv::Scalar(0));
Mat out;
//获取四个顶点坐标,然后放到容器中,其实就是构造一个伪“contours”
Point2f vertices[4];
rotate_rect.points(vertices);
vector< vector<Point> > co_ordinates;
co_ordinates.push_back(vector<Point>());
co_ordinates[0].push_back(vertices[0]);
co_ordinates[0].push_back(vertices[1]);
co_ordinates[0].push_back(vertices[2]);
co_ordinates[0].push_back(vertices[3]);
//图片区域绘制成白色的
drawContours( mask2,co_ordinates,0, Scalar(255),CV_FILLED, 8 );
//复制过来
img2.copyTo(out,mask2);
//得到正最小矩形区域
Rect rect = rotate_rect.boundingRect();
out = out(rect);
//图片的旋转中心以及旋转角度
Point2f center = Point2f(out.cols/2, out.rows/2);
float angle = rotate_rect.angle;
//旋转角度调整
if(90 <= angle && angle < 45)
{
angle -= 90;
}else if(-90 <= angle && angle <-45)
{
angle += 90;
}
//得到变换矩阵
Mat rotate_matrix = getRotationMatrix2D(center,angle,1);
//旋转图像
warpAffine(out, out, rotate_matrix, out.size(),1, 0, Scalar(0));//仿射变换
//剪切矫正部分
vector<vector<Point>> contours02;
vector<Vec4i> hierarchy02;
Mat gray_temp;
cvtColor(out,gray_temp,CV_BGR2GRAY);
findContours(gray_temp,contours02,hierarchy02,RETR_LIST,CHAIN_APPROX_SIMPLE);
for (unsigned int i=0;i<contours02.size();i++)
{
//过滤小的内部轮廓
if (contourArea(contours02[i])<450)
{
continue;
}
//获取剪切矩形
Rect rect = boundingRect(contours02[i]);
Mat result = out(rect);
long double name = rand()*100;
imwrite("02---"+to_string(name)+".jpg", result);
}
}
waitKey();
}
123.jpg
contours.jpg
contours2—1.jpg
contours2—2.jpg
rectangle.jpg
RotatedRect.jpg
两次截取的面部图像,没有区别