#include
#include
#include
#include
#include
#include
#include
using namespace std;
using namespace cv;
static void help(char** argv)
{
cout
<< "这是 AffineFeature 检测器/提取器的示例用法。\n"
<< "这是 samples/python/asift.py 的 C++ 版本\n"
<< "Usage: " << argv[0] << "\n"
<< " [ --feature= ] # Feature to use.\n"
<< " [ --flann ] # use Flann-based matcher instead of bruteforce.\n"
<< " [ --maxlines= ] # The maximum number of lines in visualizing the matching result.\n"
<< " [ --image1= ]\n"
<< " [ --image2= ] # Path to images to compare."
<< endl;
}
static double timer()
{
return getTickCount() / getTickFrequency();
}
int main(int argc, char** argv)
{
vector fileName;//文件名矢量
cv::CommandLineParser parser(argc, argv,
"{help h ||}"
"{feature|brisk|}"
"{flann||}"
"{maxlines|50|}"
"{image1|aero1.jpg|}{image2|aero3.jpg|}");
if (parser.has("help"))
{
help(argv);
return 0;
}
string feature = parser.get("feature");//特征类型: 默认brisk
bool useFlann = parser.has("flann");//使用 快速最近邻搜索包
int maxlines = parser.get("maxlines");//最多显示50条映射线条
fileName.push_back(samples::findFile(parser.get("image1")));
fileName.push_back(samples::findFile(parser.get("image2")));
if (!parser.check())//解释错误
{
parser.printErrors();
cout << "See --help (or missing '=' between argument name and value?)" << endl;//在参数名和值之间是否缺少等号
return 1;
}
Mat img1 = imread(fileName[0], IMREAD_GRAYSCALE);//读取灰度图1
Mat img2 = imread(fileName[1], IMREAD_GRAYSCALE);//灰度图2
if (img1.empty())
{
cerr << "Image " << fileName[0] << " is empty or cannot be found" << endl;
return 1;
}
if (img2.empty())
{
cerr << "Image " << fileName[1] << " is empty or cannot be found" << endl;
return 1;
}
Ptr backend;//特征检测器
Ptr matcher;//描述子匹配器
if (feature == "sift")
{
backend = SIFT::create();//创建sift特征检测器
if (useFlann)
matcher = DescriptorMatcher::create("FlannBased");
else
matcher = DescriptorMatcher::create("BruteForce");
}
else if (feature == "orb")
{
backend = ORB::create();
if (useFlann)
matcher = makePtr(makePtr(6, 12, 1));
else
matcher = DescriptorMatcher::create("BruteForce-Hamming");
}
else if (feature == "brisk")
{
backend = BRISK::create();
if (useFlann)
matcher = makePtr(makePtr(6, 12, 1));
else
matcher = DescriptorMatcher::create("BruteForce-Hamming");
}
else
{
cerr << feature << " is not supported. See --help" << endl;
return 1;
}
cout << "extracting with " << feature << "..." << endl;//正在提取特征……
Ptr ext = AffineFeature::create(backend); //AffineFeature用于实现使检测器和提取器具有仿射不变的包装器的类
vector kp1, kp2;//关键点向量
Mat desc1, desc2;//描述子
ext->detectAndCompute(img1, Mat(), kp1, desc1);//检测特征点和计算描述子
ext->detectAndCompute(img2, Mat(), kp2, desc2);
cout << "img1 - " << kp1.size() << " features, "
<< "img2 - " << kp2.size() << " features"
<< endl;
cout << "matching with " << (useFlann ? "flann" : "bruteforce") << "..." << endl;//特征匹配……
double start = timer();
// match and draw 匹配和绘制
vector< vector > rawMatches;
vector p1, p2;//匹配点?
vector distances;//点距离矢量
matcher->knnMatch(desc1, desc2, rawMatches, 2);//描述子匹配器 匹配两个图像提取的描述子。为每个查询描述子找到 2 个最佳匹配项(按距离递增的顺序)
// filter_matches 过滤匹配对象
for (size_t i = 0; i < rawMatches.size(); i++)
{
const vector& m = rawMatches[i];
if (m.size() == 2 && m[0].distance < m[1].distance * 0.75)//best匹配比better匹配距离更近
{
p1.push_back(kp1[m[0].queryIdx].pt);//描述子1匹配点矢量
p2.push_back(kp2[m[0].trainIdx].pt);//描述子2匹配点矢量
distances.push_back(m[0].distance);//最佳匹配距离
}
}
vector status;
vector< pair > pointPairs;
Mat H = findHomography(p1, p2, status, RANSAC);// 计算多个二维点对之间的最优单映射变换矩阵 H(3行x3列)
//裁掉status[i] 为零的点对距离?
int inliers = 0;
for (size_t i = 0; i < status.size(); i++)
{
if (status[i])
{
pointPairs.push_back(make_pair(p1[i], p2[i]));//点对 集合
distances[inliers] = distances[i];//距离矢量
// CV_Assert(inliers <= (int)i);
inliers++;
}
}
distances.resize(inliers);//裁掉status[i] 为零的点对距离?
cout << "execution time: " << fixed << setprecision(2) << (timer()-start)*1000 << " ms" << endl;//执行时间
cout << inliers << " / " << status.size() << " inliers/matched" << endl;
cout << "visualizing..." << endl;
vector indices(inliers);//索引矢量
cv::sortIdx(distances, indices, SORT_EVERY_ROW+SORT_ASCENDING);//根据距离重新排列 索引矢量 每行升序
// explore_match 探索匹配
int h1 = img1.size().height;
int w1 = img1.size().width;
int h2 = img2.size().height;
int w2 = img2.size().width;
Mat vis = Mat::zeros(max(h1, h2), w1+w2, CV_8U);//拼接图初始化
img1.copyTo(Mat(vis, Rect(0, 0, w1, h1)));
img2.copyTo(Mat(vis, Rect(w1, 0, w2, h2)));
cvtColor(vis, vis, COLOR_GRAY2BGR);//灰度图转BGR
vector corners(4);//4个角点
corners[0] = Point2f(0, 0);
corners[1] = Point2f((float)w1, 0);
corners[2] = Point2f((float)w1, (float)h1);
corners[3] = Point2f(0, (float)h1);
vector icorners;
perspectiveTransform(corners, corners, H);//映射4个角点
transform(corners, corners, Matx23f(1,0,(float)w1,0,1,0));//平移w1宽度
Mat(corners).convertTo(icorners, CV_32S);//浮点数 转 整型
polylines(vis, icorners, true, Scalar(255,255,255));//绘制四个角点 多边形
//在拼接视图上绘制映射线条
for (int i = 0; i < min(inliers, maxlines); i++)//遍历点对
{
int idx = indices[i];//点对索引
const Point2f& pi1 = pointPairs[idx].first;//第一幅图的点
const Point2f& pi2 = pointPairs[idx].second;//第二幅图的点
circle(vis, pi1, 2, Scalar(0,255,0), -1);//绘制圆点
circle(vis, pi2 + Point2f((float)w1,0), 2, Scalar(0,255,0), -1);
line(vis, pi1, pi2 + Point2f((float)w1,0), Scalar(0,255,0));//在拼接视图上绘制直线
}
if (inliers > maxlines)
cout << "only " << maxlines << " inliers are visualized" << endl;
imshow("affine find_obj", vis);//显示拼接视图
// Mat vis2 = Mat::zeros(max(h1, h2), w1+w2, CV_8U);
// Mat warp1;
// warpPerspective(img1, warp1, H, Size(w1, h1));
// warp1.copyTo(Mat(vis2, Rect(0, 0, w1, h1)));
// img2.copyTo(Mat(vis2, Rect(w1, 0, w2, h2)));
// imshow("warped", vis2);
waitKey();
cout << "done" << endl;
return 0;
}
笔记:
一、. OpenCV学习笔记-FLANN匹配器
https://blog.csdn.net/qq_36387683/article/details/80578480
FLANN是快速最近邻搜索包(Fast_Library_for_Approximate_Nearest_Neighbors)的简称。它是一个对大数据集和高维特征进行最近邻搜索的算法的集合,而且这些算法都已经被优化过了。在面对大数据集是它的效果要好于BFMatcher。
使用FLANN匹配,我们需要传入两个字典作为参数。这两个用来确定要使用的算法和其他相关参数等。
第一个是indexParams。配置我们要使用的算法
1、 随机k-d树算法(The Randomized k-d TreeAlgorithm)
a. Classick-d tree
找出数据集中方差最高的维度,利用这个维度的数值将数据划分为两个部分,对每个子集重复相同的过程。
参考http://www.cnblogs.com/eyeszjwang/articles/2429382.html。
b. Randomizedk-d tree
建立多棵随机k-d树,从具有最高方差的N_d维中随机选取若干维度,用来做划分。在对随机k-d森林进行搜索时候,所有的随机k-d树将共享一个优先队列。
增加树的数量能加快搜索速度,但由于内存负载的问题,树的数量只能控制在一定范围内,比如20,如果超过一定范围,那么搜索速度不会增加甚至会减慢
2、 优先搜索k-means树算法(The Priority Search K-MeansTree Algorithm)
随机k-d森林在许多情形下都很有效,但是对于需要高精度的情形,优先搜索k-means树更加有效。 K-means tree 利用了数据固有的结构信息,它根据数据的所有维度进行聚类,而随机k-d tree一次只利用了一个维度进行划分。
2.1 算法描述
步骤1 建立优先搜索k-means tree:
(1) 建立一个层次化的k-means 树;
(2) 每个层次的聚类中心,作为树的节点;
(3) 当某个cluster内的点数量小于K时,那么这些数据节点将做为叶子节点。
步骤2 在优先搜索k-means tree中进行搜索:
(1) 从根节点N开始检索;
(2) 如果是N叶子节点则将同层次的叶子节点都加入到搜索结果中,count += |N|;
(3) 如果N不是叶子节点,则将它的子节点与query Q比较,找出最近的那个节点Cq,同层次的其他节点加入到优先队列中;
(4) 对Cq节点进行递归搜索;
(5) 如果优先队列不为空且 count& matches, const Mat& mask=Mat() ) const;
// Find k best matches for each query descriptor (in increasing order of distances).
// compactResult is used when mask is not empty. If compactResult is false matches
// vector will have the same size as queryDescriptors rows. If compactResult is true
// matches vector will not contain matches for fully masked out query descriptors.
//为每个查询描述子找到 k 个最佳匹配项(按距离递增的顺序)。 当掩码不为空时使用 compactResult。
//如果 compactResult 为 false 匹配向量将具有与 queryDescriptors 行相同的大小。
//如果 compactResult 为真,则匹配向量将不包含完全屏蔽的查询描述符的匹配项。
CV_WRAP void knnMatch( const Mat& queryDescriptors, const Mat& trainDescriptors,
CV_OUT vector >& matches, int k,
const Mat& mask=Mat(), bool compactResult=false ) const;
// Find best matches for each query descriptor which have distance less than
// maxDistance (in increasing order of distances).
//查找距离小于 maxDistance 的每个查询描述子的最佳匹配(按距离递增的顺序)。
void radiusMatch( const Mat& queryDescriptors, const Mat& trainDescriptors,
vector >& matches, float maxDistance,
const Mat& mask=Mat(), bool compactResult=false ) const;
方法重载,用于图像和图像集匹配的方法声明
CV_WRAP void match( const Mat& queryDescriptors, CV_OUT vector& matches,
const vector& masks=vector() );
CV_WRAP void knnMatch( const Mat& queryDescriptors, CV_OUT vector >& matches, int k,
const vector& masks=vector(), bool compactResult=false );
void radiusMatch( const Mat& queryDescriptors, vector >& matches, float maxDistance,
const vector& masks=vector(), bool compactResult=false );
DMatcher 是用来保存匹配结果的,主要有以下几个属性
CV_PROP_RW int queryIdx; // query descriptor index
CV_PROP_RW int trainIdx; // train descriptor index
CV_PROP_RW int imgIdx; // train image index
CV_PROP_RW float distance;
在图像匹配时有两种图像的集合,查找集(Query Set)和训练集(Train Set),
对于每个Query descriptor,DMatch中保存了和其最好匹配的Train descriptor。
另外,每个train image会生成多个train descriptor。
如果是图像对之间的匹配的话,由于所有的train descriptor都是由一个train image生成的,
所以在匹配结果DMatch中所有的imgIdx是一样的,都为0.
4.2 KNNMatch
匹配过程中很可能发生错误的匹配,错误的匹配主要有两种:匹配的特征点事错误的,图像上的特征点无法匹配。常用的删除错误的匹配有
交叉过滤
如果第一幅图像的一个特征点和第二幅图像的一个特征点相匹配,则进行一个相反的检查,即将第二幅图像上的特征点与第一幅图像上相应特征点进行匹配,如果匹配成功,则认为这对匹配是正确的。
OpenCV中的BFMatcher已经包含了这种过滤 BFMatcher matcher(NORM_L2,true),在构造BFMatcher是将第二个参数设置为true。
比率测试
KNNMatch,可设置K = 2 ,即对每个匹配返回两个最近邻描述符,仅当第一个匹配与第二个匹配之间的距离足够小时,才认为这是一个匹配。
在抽象基类DescriptorMatcher中封装了knnMatch方法,具体使用方法如下:
void FeatureMatchTest::knnMatch(vector& matches) {
const float minRatio = 1.f / 1.5f;
const int k = 2;
vector> knnMatches;
matcher->knnMatch(leftPattern->descriptors, rightPattern->descriptors, knnMatches, k);
for (size_t i = 0; i < knnMatches.size(); i++) {
const DMatch& bestMatch = knnMatches[i][0];
const DMatch& betterMatch = knnMatches[i][1];
float distanceRatio = bestMatch.distance / betterMatch.distance;
if (distanceRatio < minRatio)
matches.push_back(bestMatch);
}
}
RASIC方法计算基础矩阵,并细化匹配结果
如果已经知道了两视图(图像)间的多个点的匹配,就可以进行基础矩阵F的计算了。OpenCV2中可以使用findFundamentalMat方法,其声明如下:
//! finds fundamental matrix from a set of corresponding 2D points
CV_EXPORTS_W Mat findFundamentalMat( InputArray points1, InputArray points2,
int method=FM_RANSAC,
double param1=3., double param2=0.99,
OutputArray mask=noArray());
参数说明:
points1,points2 两幅图像间相匹配的点,点的坐标要是浮点数(float或者double)
第三个参数method是用来计算基础矩阵的具体方法,是一个枚举值。
param1,param2保持默认值即可。
主要来说下mask参数,有N个匹配点用来计算基础矩阵,则该值有N个元素,每个元素的值为0或者1.值为0时,代表该匹配点事错误的匹配(离群值),只在使用RANSAC和LMeds方法时该值有效,
可以使用该值来删除错误的匹配。
另外,在匹配完成后使用得到的匹配点来计算基础矩阵时,首先需要将特征点对齐,
并且将特征点转换为2D点,具体实现如下:
//Align all points
vector alignedKps1, alignedKps2;
for (size_t i = 0; i < matches.size(); i++) {
alignedKps1.push_back(leftPattern->keypoints[matches[i].queryIdx]);
alignedKps2.push_back(rightPattern->keypoints[matches[i].trainIdx]);
}
//Keypoints to points
vector ps1, ps2;
for (unsigned i = 0; i < alignedKps1.size(); i++)
ps1.push_back(alignedKps1[i].pt);
for (unsigned i = 0; i < alignedKps2.size(); i++)
ps2.push_back(alignedKps2[i].pt);
五、 OpenCV 4.x API 详解与C++实例-特征检测与描述
https://blog.csdn.net/wujuxKkoolerter/article/details/114162810
OpenCV提供了丰富的特征检测算法,比如SIFT(Scale Invariant Feature Transform)、
AffineFeature、AgastFeatureDetector、AKAZE、BRISK、FastFeatureDetector、GFTTDetector、
KAZE、MSER、ORB、SimpleBlobDetector等
4.1 SIFT SIFT(Scale Invariant Feature Transform)
尺度不变特征变换算法提取图像特征
SIFT类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::SIFT::create(int nfeatures = 0,int nOctaveLayers = 3,double contrastThreshold = 0.04,double edgeThreshold = 10,double sigma = 1.6)
参数名称 参数描述
nfeatures 保留的最佳特征的数量;特征按其得分排名
nOctaveLayers 每个八度中的层数。 3是D.Lowe谁中使用的值。 八度的数量是根据图像分辨率自动计算的。
contrastThreshold 对比度阈值,用于过滤半均匀(低对比度)区域中的弱点。 阈值越大,检测器产生的特征越少。
edgeThreshold 用于过滤边缘特征的阈值。 请注意,其含义与contrastThreshold不同,即edgeThreshold越大,滤除的特征越少(保留的特征越多)。
sigma 高斯的sigma应用于八度为#0的输入图像。 如果使用带软镜头的弱相机拍摄图像,则可能需要减少数量。
注意:应用过滤时,对比度阈值将被nOctaveLayers除。 当nOctaveLayers设置为默认值并且如果要使用D.Lowe论文中使用的值0.03时,请将此参数设置为0.09。
#include
#include
using namespace std;
int main()
{
// 读取图像
cv::Mat src = cv::imread("images/f1.jpg");
if(src.empty()){
cerr << "cannot read image.\n";
return EXIT_FAILURE;
}
// 转换成灰度图像
cv::Mat gray;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
// 创建SIFT特征提取器
cv::Ptr feature = cv::SIFT::create(1024,3,0.04,120,1.5);
// 检测特征点
vector keypoints;
cv::Mat descriptor;
feature->detectAndCompute(gray,cv::Mat(),keypoints,descriptor);
// 绘制特征点
cv::Mat output;
cv::drawKeypoints(src,keypoints,output,cv::Scalar(0,0,255));
cv::imshow("src",src);
cv::imshow("output",output);
cv::waitKey();
return 0;
}
4.2 AffineFeature
用于实现使检测器和提取器具有仿射不变的包装器的类,在[Guoshen Yu and Jean-Michel Morel. Asift: An algorithm for fully affine invariant comparison. Image Processing On Line, 1:11–38, 2011.]中描述为ASIFT。
AffineFeature类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::AffineFeature::create(const Ptr< Feature2D > & backend,int maxTilt = 5,int minTilt = 0,float tiltStep = 1.4142135623730951f,float rotateStepBase = 72)
参数 参数描述
backend 用作后端的检测器/提取器。
maxTilt 倾斜系数的最高功率指标。 在纸张中使用5作为倾斜采样范围n。
minTilt 最低的倾斜系数功率指数。 本文使用0。
tiltStep 倾斜采样步骤δt。
rotateStepBase 旋转采样阶跃系数b
#include
#include
using namespace std;
int main()
{
// 读取图像
cv::Mat src = cv::imread("images/f1.jpg");
if(src.empty()){
cerr << "cannot read image.\n";
return EXIT_FAILURE;
}
// 转换成灰度图像
cv::Mat gray;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
// 使用SIFT特征检测算法创建
cv::Ptr feature = cv::AffineFeature::create(cv::SIFT::create(128));
// 检测特征点
vector keypoints;
cv::Mat descriptor;
feature->detectAndCompute(gray,cv::Mat(),keypoints,descriptor);
// 绘制特征点
cv::Mat output;
cv::drawKeypoints(src,keypoints,output,cv::Scalar(0,0,255));
cv::imshow("src",src);
cv::imshow("output",output);
cv::waitKey();
return 0;
}
4.3 AgastFeatureDetector
使用AGAST方法进行特征检测的包装类。
AgastFeatureDetector类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::AgastFeatureDetector::create(int threshold = 10,bool nonmaxSuppression = true,AgastFeatureDetector::DetectorType type = AgastFeatureDetector::OAST_9_16)
#include
#include
using namespace std;
int main()
{
// 读取图像
cv::Mat src = cv::imread("images/f1.jpg");
if(src.empty()){
cerr << "cannot read image.\n";
return EXIT_FAILURE;
}
// 转换成灰度图像
cv::Mat gray;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
// 创建AgastFeatureDetector特征提取器
cv::Ptr feature = cv::AgastFeatureDetector::create();
// 检测特征点
vector keypoints;
cv::Mat descriptor;
feature->detect(gray,keypoints);
// 绘制特征点
cv::Mat output;
cv::drawKeypoints(src,keypoints,output,cv::Scalar(0,0,255));
cv::imshow("src",src);
cv::imshow("output",output);
cv::waitKey();
return 0;
}
4.4 AKAZE
实现AKAZE关键点检测器和描述符提取器的类,在[Pablo F Alcantarilla, Jesús Nuevo, and Adrien Bartoli. Fast explicit diffusion for accelerated features in nonlinear scale spaces. Trans. Pattern Anal. Machine Intell, 34(7):1281–1298, 2011.]中进行了描述。
AKAZE类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::AKAZE::create (AKAZE::DescriptorType descriptor_type = AKAZE::DESCRIPTOR_MLDB,int descriptor_size = 0,int descriptor_channels = 3,float threshold = 0.001f,int nOctaves = 4,int nOctaveLayers = 4,KAZE::DiffusivityType diffusivity = KAZE::DIFF_PM_G2)
参数名称 参数描述
descriptor_type 提取的描述符的类型:DESCRIPTOR_KAZE,DESCRIPTOR_KAZE_UPRIGHT,DESCRIPTOR_MLDB或DESCRIPTOR_MLDB_UPRIGHT。
descriptor_size 描述符的大小(以位为单位)。 0->全尺寸
descriptor_channels 描述符中的通道数(1、2、3)
threshold 检测器响应阈值以接受点
nOctaves 图像的最大八度演变
nOctaveLayers 每个比例级别的默认子级别数
diffusivity 扩散类型。 DIFF_PM_G1,DIFF_PM_G2,DIFF_WEICKERT或DIFF_CHARBONNIER
4.5 BRISK
实现BRISK关键点检测器和描述符提取器的类,在[Stefan Leutenegger, Margarita Chli, and Roland Yves Siegwart. Brisk: Binary robust invariant scalable keypoints. In Computer Vision (ICCV), 2011 IEEE International Conference on, pages 2548–2555. IEEE, 2011.]中进行了描述。
BRISK类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::BRISK::create(int thresh = 30,int octaves=3,float patternScale=1.0f)
参数名称 参数描述
thresh AGAST检测阈值得分。
octaves 检测八度。 使用0进行单刻度。
patternScale 将此比例应用于用于采样关键点邻域的模式。
自定义模式的BRISK构造函数。
static Ptr cv::BRISK::create (const std::vector< float > & radiusList,const std::vector< int > & numberList,float dMax = 5.85f,float dMin = 8.2f,const std::vector< int > & indexChange=std::vector< int >())
参数名称 参数描述
numberList 定义采样圆上的采样点数。 必须与radiusList大小相同。
radiusList 定义在关键点周围采样的半径(以像素为单位)(对于关键点比例1)。
dMax 用于描述符形成的短配对的阈值(对于关键点比例1,以像素为单位)。
dMin 用于方向确定的长配对的阈值(对于关键点比例1,以像素为单位)。
indexChange 位的索引重新映射。
static Ptr cv::BRISK::create (int thresh,int octaves,const std::vector< float > & radiusList,const std::vector< int > & numberList,float dMax = 5.85f,float dMin = 8.2f,const std::vector< int > & indexChange = std::vector< int >())
参数名称 参数描述
thresh AGAST检测阈值得分。
octaves 检测八度。 使用0进行单刻度。
radiusList 定义在关键点周围采样的半径(以像素为单位)(对于关键点比例1)。
numberList 定义采样圆上的采样点数。 必须与radiusList大小相同。
dMax 用于描述符形成的短配对的阈值(对于关键点比例1,以像素为单位)。
dMin 用于方向确定的长配对的阈值(对于关键点比例1,以像素为单位)。
indexChange 位的索引重新映射。
4.6 FastFeatureDetector
使用FAST方法进行特征检测的包装类。FastFeatureDetector类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::FastFeatureDetector::create(int threshold = 10,bool nonmaxSuppression = true,FastFeatureDetector::DetectorType type = FastFeatureDetector::TYPE_9_16)
4.7 GFTTDetector
使用goodFeaturesToTrack函数包装特征的包装类。GFTTDetector类继承了cv::Feature2D类,通过create静态方法创建。
static Ptr cv::GFTTDetector::create(int maxCorners=1000,double qualityLevel = 0.01,double minDistance = 1,int blockSize = 3,bool useHarrisDetector = false,double k = 0.04)
4.8 KAZE
实现KAZE关键点检测器和描述符提取器的类,在[Pablo Fernández Alcantarilla, Adrien Bartoli, and Andrew J Davison. Kaze features. In Computer Vision–ECCV 2012, pages 214–227. Springer, 2012.]中进行了描述。
AKAZE描述符只能与KAZE或AKAZE关键点一起使用。AKAZE类继承了cv::Feature2D类,通过create静态方法创建。参数如下:
static Ptr cv::KAZE::create(bool extended = false,bool upright = false,float threshold = 0.001f,int nOctaves = 4,int nOctaveLayers = 4,KAZE::DiffusivityType diffusivity = KAZE::DIFF_PM_G2)
参数 参数名称
extended 设置为启用扩展(128字节)描述符的提取。
upright 设置为启用直立描述符(非旋转不变)。
threshold 检测器响应阈值以接受点
nOctaves 图像的最大八度演变
nOctaveLayers 每个比例级别的默认子级别数
diffusivity 扩散类型。 DIFF_PM_G1,DIFF_PM_G2,DIFF_WEICKERT或DIFF_CHARBONNIER
4.9 MSER
最大限度地稳定的极值区域提取。该类封装了MSER提取算法的所有参数。MSER类继承了cv::Feature2D类,通过create静态方法创建。参数如下:
static Ptr cv::MSER::create(int _delta = 5,int _min_area = 60,int _max_area = 14400,double _max_variation = 0.25,double _min_diversity = .2,int _max_evolution = 200,double _area_threshold = 1.01,double _min_margin = 0.003,int _edge_blur_size = 5)
参数名称 参数描述
_delta 它比较(sizei-sizei-delta)/ sizei-delta
_min_area 修剪小于minArea的区域
_max_area 修剪大于maxArea的区域
_max_variation 修剪该区域的大小与其子面积相似
_min_diversity 对于彩色图像,回溯以切断多样性小于min_diversity的mser
_max_evolution 对于彩色图像,演变步骤
_area_threshold 对于彩色图像,导致重新初始化的面积阈值
_min_margin 对于彩色图像,请忽略过小的边距
_edge_blur_size 对于彩色图像,边缘模糊的光圈大小
4.10 ORB
实现ORB(面向Brief)关键点检测器和描述符提取器的类。
在[Ethan Rublee, Vincent Rabaud, Kurt Konolige, and Gary Bradski. Orb: an efficient alternative to sift or surf. In Computer Vision (ICCV), 2011 IEEE International Conference on, pages 2564–2571. IEEE, 2011.]中描述。 该算法在金字塔中使用FAST来检测稳定的关键点,使用FAST或Harris响应选择最强的特征,使用一阶矩找到它们的方向,并使用Brief来计算描述符(其中随机点对(或k元组)的坐标为 根据测量的方向旋转)。
ORB类继承了cv::Feature2D类,通过create静态方法创建。参数如下:
static Ptr cv::ORB::create(int nfeatures=500,float scaleFactor = 1.2f,int nlevels = 8,int edgeThreshold = 31,int firstLevel = 0,int WTA_K = 2,ORB::ScoreType scoreType = ORB::HARRIS_SCORE,int patchSize = 31,int fastThreshold = 20)
参数 参数描述
nfeatures 保留的最大特征数量。
scaleFactor 金字塔抽取率大于1。scaleFactor == 2表示经典金字塔,其中每个下一个级别的像素比上一个少4倍,但是如此大的比例因子将大大降低特征匹配分数。 另一方面,太接近1的比例因子意味着要覆盖一定的比例范围,您将需要更多的金字塔等级,因此速度会受到影响。
nlevels 金字塔等级的数量。 最小级别的线性大小等于input_image_linear_size / pow(scaleFactor,nlevels-firstLevel)。
edgeThreshold 未检测到特征的边框的大小。 它应该与patchSize参数大致匹配。
firstLevel 要放置源图像的金字塔等级。 先前的层填充有放大的源图像。
WTA_K 产生定向的Brief描述符的每个元素的点数。 默认值2表示“ BRIEF”,我们采用一个随机点对并比较它们的亮度,因此得到0/1响应。 其他可能的值是3和4。例如,3表示我们取3个随机点(当然,这些点坐标是随机的,但它们是从预定义的种子生成的,因此,BRIEF描述符的每个元素都是从确定的 像素矩形),找到最大亮度的点和获胜者的输出指数(0、1或2)。 这样的输出将占用2位,因此将需要Hamming距离的特殊变体,表示为NORM_HAMMING2(每个bin 2位)。 当WTA_K = 4时,我们取4个随机点来计算每个bin(也将占用2位,可能的值为0、1、2或3)。
scoreType 默认的HARRIS_SCORE表示将Harris算法用于对要素进行排名(分数被写入KeyPoint :: score并用于保留最佳nfeatures要素); FAST_SCORE是该参数的替代值,它产生的稳定关键点会稍少一些,但计算速度稍快一些。
patchSize 定向的Brief描述符使用的补丁大小。 当然,在较小的金字塔层上,特征覆盖的感知图像区域将更大。
fastThreshold 快速阈值
4.11 SimpleBlobDetector
用于从图像中提取斑点的类。 SimpleBlobDetector类继承了cv::Feature2D类,通过create静态方法创建。参数如下:
static Ptr cv::SimpleBlobDetector::create(const SimpleBlobDetector::Params & parameters = SimpleBlobDetector::Params())
六、 opencv学习——cv2.findHomography()
https://blog.csdn.net/qq_36387683/article/details/98446442
我们之前使用了查询图像,找到其中的一些特征点,我们取另外一个训练图像,找到里面的特征,我们找到它们中间最匹配的。.简单说就是我们在一组图像里找一个目标的某个部分的位置。
我们可以使用一个calib3d模块里的函数,cv2.findHomography().如果我们传了两个图像里的点集合,它会找到那个目标的透视转换。然后我们可以使用cv2.perspectiveTransform()来找目标,它需要至少4个正确的点来找变换。
我们看过可能会有一些匹配是的错误而影响结果。哟啊解决这个问题,算法使用了RANSAC或者LEAST_MEDIAN(由标志决定)。提供正确估计的好的匹配被叫做inliers,而其他的叫做outliers。cv2.findHomography()返回一个掩图来指定inlier和outlier。
https://cloud.tencent.com/developer/article/1435205
https://www.cnblogs.com/kbqLibrary/p/12389254.html
findHomography:
计算多个二维点对之间的最优单映射变换矩阵 H(3行x3列) (就是对图片的矫正),使用最小均方误差或者RANSAC方法
函数功能:找到两个平面之间的转换矩阵。
射影变换也叫做单应(Homography)
图1通过H矩阵变换变成图2,就是这个函数的公式
X′=HX
X′代表图2
其操作过程
在“大”图像(目标图像)上选择4个点和“小”图像(被合并图像)的四角做对应,然后根据这4对对应的点计算两幅图像的单应矩阵。
得到单应矩阵H后,利用函数warpPerspective将H应用到“小”图像上,得到图像M
将图像M合并到目标图像中选择的四个点的位置
Mat cv::findHomography ( InputArray srcPoints,
InputArray dstPoints,
int method = 0,
double ransacReprojThreshold = 3,
OutputArray mask = noArray(),
const int maxIters = 2000,
const double confidence = 0.995
)
参数详解:
srcPoints 源平面中点的坐标矩阵,可以是CV_32FC2类型,也可以是vector类型
dstPoints 目标平面中点的坐标矩阵,可以是CV_32FC2类型,也可以是vector类型
method 计算单应矩阵所使用的方法。不同的方法对应不同的参数,具体如下:
0 - 利用所有点的常规方法
RANSAC - RANSAC-基于RANSAC的鲁棒算法
LMEDS - 最小中值鲁棒算法
RHO - PROSAC-基于PROSAC的鲁棒算法
ransacReprojThreshold
将点对视为内点的最大允许重投影错误阈值(仅用于RANSAC和RHO方法)。如果
则点被认为是个外点(即错误匹配点对)。若srcPoints和dstPoints是以像素为单位的,则该参数通常设置在1到10的范围内。
mask
可选输出掩码矩阵,通常由鲁棒算法(RANSAC或LMEDS)设置。 请注意,输入掩码矩阵是不需要设置的。
maxIters RANSAC 算法的最大迭代次数,默认值为2000。
confidence 可信度值,取值范围为0到1.
//图片映射矩阵把不同角度的图片矫正
void findHomographyText(){
// Read source image.
Mat src = imread("F:\\视觉\\opencv\\pic\\1.png");
// Four corners of the book in source image
vector pts_src;
pts_src.push_back(Point2f(0, 0));
pts_src.push_back(Point2f(src.cols, 0));
pts_src.push_back(Point2f(src.cols, src.rows));
pts_src.push_back(Point2f(0, src.rows));
// Four corners of the book in destination image.
vector pts_dst;
pts_dst.push_back(Point2f(0, 0));
pts_dst.push_back(Point2f(src.cols/4, 0));
pts_dst.push_back(Point2f(src.cols/3, src.rows));
pts_dst.push_back(Point2f(0, src.rows/2));
// Calculate Homography
Mat h = findHomography(pts_src, pts_dst);
// Output image
Mat im_out;
// Warp source image to destination based on homography
warpPerspective(src, im_out, h, src.size());
// Display images
imshow("Source Image", src);
imshow("Warped Source Image", im_out);
waitKey(0);
}
6.2,SURF对图像的识别和标记
1,开发思路
(1)使用SIFT或者SURF进行角点检测,获取两个图像的的角点集合
(2)根据两个集合,使用特征点匹配,匹配类似的点 FlannBasedMatcher
(3)过滤特征点对。
(4)通过特征点对,求出H值
(5)画出特征区域
代码实现:
1,使用SIFT或者SURF进行角点检测,获取两个图像的的角点集合
src = imread("F:\\视觉\\opencv\\pic\\11.png");//读图片
src3 = imread("F:\\视觉\\opencv\\pic\\5.png");//读图片
int minHessian = 400;
cvtColor(src, src, COLOR_BGR2GRAY);
cvtColor(src3, src3, COLOR_BGR2GRAY);
Ptr detector = SIFT::create(minHessian);
vector keypoints_obj;//图片1特征点
vector keypoints_scene;//图片2特征点
Mat descriptor_obj, descriptor_scene;
//找出特征点存到keypoints_obj与keypoints_scene点集中
detector->detectAndCompute(src, Mat(), keypoints_obj, descriptor_obj);
detector->detectAndCompute(src3, Mat(), keypoints_scene, descriptor_scene);
// matching 找到特征集合
FlannBasedMatcher matcher;
vector matches;
matcher.match(descriptor_obj, descriptor_scene, matches);
2,过滤相似度高的图像
// find good matched points
double minDist = 1000;
double maxDist = 0;
for (int i = 0; i < descriptor_obj.rows; i++) {
double dist = matches[i].distance;
if (dist > maxDist) {
maxDist = dist;
}
if (dist < minDist) {
minDist = dist;
}
}
printf("max distance : %f\n", maxDist);
printf("min distance : %f\n", minDist);
vector goodMatches;
//过滤相同的点
for (int i = 0; i < descriptor_obj.rows; i++) {
double dist = matches[i].distance;//相识度
printf("distance : %f\n", dist);
if (dist < max(3 * minDist, 0.2)) {
goodMatches.push_back(matches[i]);
}
}
3,求出H
vector obj;
vector objInScene;
for (size_t t = 0; t < goodMatches.size(); t++) {
//把DMatch转成坐标 Point2f
obj.push_back(keypoints_obj[goodMatches[t].queryIdx].pt);
objInScene.push_back(keypoints_scene[goodMatches[t].trainIdx].pt);
}
//用来求取“射影变换”的H转制矩阵函数 X'=H X ,并使用RANSAC消除一些出错的点
Mat H = findHomography(obj, objInScene, RANSAC);
4,使用H求出映射到大图的点
vector obj_corners(4);
vector scene_corners(4);
obj_corners[0] = Point(0, 0);
obj_corners[1] = Point(src.cols, 0);
obj_corners[2] = Point(src.cols, src.rows);
obj_corners[3] = Point(0, src.rows);
//透视变换(把斜的图片扶正)
cout << H << endl;
perspectiveTransform(obj_corners, scene_corners, H);
5,在原图上画线段
Mat dst;
cvtColor(src3, dst, COLOR_GRAY2BGR);
line(dst, scene_corners[0], scene_corners[1], Scalar(0, 0, 255), 2, 8, 0);
line(dst, scene_corners[1], scene_corners[2], Scalar(0, 0, 255), 2, 8, 0);
line(dst, scene_corners[2], scene_corners[3], Scalar(0, 0, 255), 2, 8, 0);
line(dst, scene_corners[3], scene_corners[0], Scalar(0, 0, 255), 2, 8, 0);
imshow("Draw object", dst);
Homography应用:虚拟广告牌
在足球或者棒球体育直播中,经常可以看到球场旁边有虚拟广告,并且还会根据地区,国家的不同播放不同的广告,这是如何做到的?
看完此篇博客,你应该就能知道如何实现了。原理跟前一个差不多,这里直接上代码
#include
using namespace cv;
using namespace std;
struct userdata{
Mat im;
vector points;
};
void mouseHandler(int event, int x, int y, int flags, void* data_ptr)
{
if ( event == EVENT_LBUTTONDOWN )
{
userdata *data = ((userdata *) data_ptr);
circle(data->im, Point(x,y),3,Scalar(0,255,255), 5, CV_AA);
imshow("Image", data->im);
if (data->points.size() < 4)
{
data->points.push_back(Point2f(x,y));
}
}
}
int main( int argc, char** argv)
{
// Read in the image.
Mat im_src = imread("first-image.jpg");
Size size = im_src.size();
// Create a vector of points.
vector pts_src;
pts_src.push_back(Point2f(0,0));
pts_src.push_back(Point2f(size.width - 1, 0));
pts_src.push_back(Point2f(size.width - 1, size.height -1));
pts_src.push_back(Point2f(0, size.height - 1 ));
// Destination image
Mat im_dst = imread("times-square.jpg");
// Set data for mouse handler
Mat im_temp = im_dst.clone();
userdata data;
data.im = im_temp;
//show the image
imshow("Image", im_temp);
cout << "Click on four corners of a billboard and then press ENTER" << endl;
//set the callback function for any mouse event
setMouseCallback("Image", mouseHandler, &data);
waitKey(0);
// Calculate Homography between source and destination points
Mat h = findHomography(pts_src, data.points);
// Warp source image
warpPerspective(im_src, im_temp, h, im_temp.size());
// Extract four points from mouse data
Point pts_dst[4];
for( int i = 0; i < 4; i++)
{
pts_dst[i] = data.points[i];
}
// Black out polygonal area in destination image.
fillConvexPoly(im_dst, pts_dst, 4, Scalar(0), CV_AA);
// Add warped source image to destination image.
im_dst = im_dst + im_temp;
// Display image.
imshow("Image", im_dst);
waitKey(0);
return 0;
}
Homography是一个3*3的变换矩阵,将一张图中的点映射到另一张图中对应的点
retval, mask = cv.findHomography( srcPoints, dstPoints[, method[, ransacReprojThreshold[, mask[, maxIters[, confidence]]]]] )
参数 描述
srcPoints 原始点
dstPoints 目标点
retval 变换矩阵
mask Optional output mask set by a robust method ( RANSAC or LMEDS ). Note that the input mask values are ignored.
//七、 Opencv函数介绍: sort、sortIdx函数
cv::sort 负责返回排序后的矩阵,cv::sortIdx 负责返回对应原矩阵的索引。
还有在 MATLAB 里,1 和 2 用来分别指示是对列还是对行进行排序,'ascend' 和 'descend' 用来指示是升序还是降序。在 OpenCV 中,我们用类似于 CV_SORT_EVERY_ROW + CV_SORT_ASCENDING 这样的方式来一并指定对列还是对行以及升序还是降序,其指示值定义如下,所以可以组合出 4 种不同的方式:
#define CV_SORT_EVERY_ROW 0
#define CV_SORT_EVERY_COLUMN 1
#define CV_SORT_ASCENDING 0
#define CV_SORT_DESCENDING 16
//CV_SORT_EVERY_ROW + CV_SORT_ASCENDING:对矩阵的每行按照升序排序;
//CV_SORT_EVERY_ROW + CV_SORT_DESCENDING:对矩阵的每行按照降序排序;
//CV_SORT_EVERY_COLUMN + CV_SORT_ASCENDING:对矩阵的每列按照升序排序;
//CV_SORT_EVERY_COLUMN + CV_SORT_DESCENDING:对矩阵的每列按照降序排序;
void demo_sort_sortIdx()
{
int testArrLen = 5;
cv::Mat_ testArr = cv::Mat::zeros(2, testArrLen, CV_32S);
testArr(0,0) = 87;
testArr(0,1) = 65;
testArr(0,2) = 98;
testArr(0,3) = 12;
testArr(0,4) = 55;
testArr(1,0) = 86;
testArr(1,1) = 66;
testArr(1,2) = 97;
testArr(1,3) = 17;
testArr(1,4) = 54;
cv::Mat_ sortArr, sortIdxArr;
cv::sort(testArr, sortArr, CV_SORT_EVERY_ROW + CV_SORT_ASCENDING);
cv::sortIdx(testArr, sortIdxArr, CV_SORT_EVERY_ROW + CV_SORT_ASCENDING);
std::cout<<"testArr = "< corners(4);
corners[0] = Point2f(0,0);
corners[1] = Point2f(img_width-1,0);
corners[2] = Point2f(0,img_height-1);
corners[3] = Point2f(img_width-1,img_height-1);
vector corners_trans(4);
corners_trans[0] = Point2f(150,250);
corners_trans[1] = Point2f(771,0);
corners_trans[2] = Point2f(0,img_height-1);
corners_trans[3] = Point2f(650,img_height-1);
Mat transform = getPerspectiveTransform(corners,corners_trans);
cout< ponits, points_trans;
for(int i=0;i(i);
for(int j=0;j(y);
t[x*3] = p[j*3];
t[x*3+1] = p[j*3+1];
t[x*3+2] = p[j*3+2];
count++;
}
}
imwrite("boy_trans.png",img_trans);
return 0;
}
除了getPerspectiveTransform()函数,OpenCV还提供了findHomography()的函数,不是用点来找,而是直接用透视平面来找变换公式。这个函数在特征匹配的经典例子中有用到,也非常直观:
int main( int argc, char** argv )
{
Mat img_object = imread( argv[1], IMREAD_GRAYSCALE );
Mat img_scene = imread( argv[2], IMREAD_GRAYSCALE );
if( !img_object.data || !img_scene.data )
{ std::cout<< " --(!) Error reading images " << std::endl; return -1; }
//-- Step 1: 使用 SURF Detector 检测关键点 Detect the keypoints using SURF Detector
int minHessian = 400;
SurfFeatureDetector detector( minHessian );
std::vector keypoints_object, keypoints_scene;
detector.detect( img_object, keypoints_object );
detector.detect( img_scene, keypoints_scene );
//-- Step 2: 计算描述子(特征向量)Calculate descriptors (feature vectors)
SurfDescriptorExtractor extractor;
Mat descriptors_object, descriptors_scene;
extractor.compute( img_object, keypoints_object, descriptors_object );
extractor.compute( img_scene, keypoints_scene, descriptors_scene );
//-- Step 3:使用 FLANN 匹配器匹配描述子向量 Matching descriptor vectors using FLANN matcher
FlannBasedMatcher matcher;
std::vector< DMatch > matches;
matcher.match( descriptors_object, descriptors_scene, matches );
double max_dist = 0; double min_dist = 100;
//-- 快速计算关键点之间的最大和最小距离 Quick calculation of max and min distances between keypoints
for( int i = 0; i < descriptors_object.rows; i++ )
{ double dist = matches[i].distance;
if( dist < min_dist ) min_dist = dist;
if( dist > max_dist ) max_dist = dist;
}
printf("-- Max dist : %f \n", max_dist );
printf("-- Min dist : %f \n", min_dist );
//--仅绘制“好”匹配项(即距离小于 3*min_dist ) Draw only "good" matches (i.e. whose distance is less than 3*min_dist )
std::vector< DMatch > good_matches;
for( int i = 0; i < descriptors_object.rows; i++ )
{ if( matches[i].distance < 3*min_dist )
{ good_matches.push_back( matches[i]); }
}
Mat img_matches;
drawMatches( img_object, keypoints_object, img_scene, keypoints_scene,
good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
vector(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
//--在 img_2 中本地化来自 img_1 的对象 Localize the object from img_1 in img_2
std::vector obj;
std::vector scene;
for( size_t i = 0; i < good_matches.size(); i++ )
{
//-- 从好的匹配中获取关键点Get the keypoints from the good matches
obj.push_back( keypoints_object[ good_matches[i].queryIdx ].pt );
scene.push_back( keypoints_scene[ good_matches[i].trainIdx ].pt );
}
Mat H = findHomography( obj, scene, RANSAC );
//--从 image_1 获取角点(要“检测”的对象) Get the corners from the image_1 ( the object to be "detected" )
std::vector obj_corners(4);
obj_corners[0] = Point(0,0); obj_corners[1] = Point( img_object.cols, 0 );
obj_corners[2] = Point( img_object.cols, img_object.rows ); obj_corners[3] = Point( 0, img_object.rows );
std::vector scene_corners(4);
perspectiveTransform( obj_corners, scene_corners, H);
//--在角落之间画线(场景中的映射对象 - image_2 ) Draw lines between the corners (the mapped object in the scene - image_2 )
Point2f offset( (float)img_object.cols, 0);
line( img_matches, scene_corners[0] + offset, scene_corners[1] + offset, Scalar(0, 255, 0), 4 );
line( img_matches, scene_corners[1] + offset, scene_corners[2] + offset, Scalar( 0, 255, 0), 4 );
line( img_matches, scene_corners[2] + offset, scene_corners[3] + offset, Scalar( 0, 255, 0), 4 );
line( img_matches, scene_corners[3] + offset, scene_corners[0] + offset, Scalar( 0, 255, 0), 4 );
//-- 显示检测到的匹配项 Show detected matches
imshow( "Good Matches & Object detection", img_matches );
waitKey(0);
return 0;
}
//九、 cv2.polylines()
https://blog.csdn.net/qq_44109682/article/details/118229813
cv2.polylines这一函数的出现,该函数可以画任意的多边形
polylines(img, pts, isClosed, color, thickness=None, lineType=None, shift=None)
参数:
img(array):为ndarray类型(可以为cv.imread)直接读取的数据
pts(array):为所画多边形的顶点坐标,举个简单的例子:当一张图片需要有多个四边形时,该数组ndarray的shape应该为(N,4,2)
isClosed(bool):所画四边形是否闭合,通常为True
color(tuple):RGB三个通道的值
thickness(int):画线的粗细
shift:顶点坐标中小数的位数
polylines函数其实可以不需要将返回值赋给新的变量,它是直接将输入变量上进行操作,所以上面两行代码,我特意进行了一次copy操作,以防将最初的图片覆盖掉。
//十、 opencv 添加 线条 矩形 椭圆 圆 多边形
import cv2 as cv
import numpy as np
img = np.zeros((480,640,3),np.uint8)
b,g,r = cv.split(img)
b[:] = 250
g[:] = 250
r[:] = 250
img2 = cv.merge((b,g,r))
# 绘制线条
cv.line(img2,(0,0),(300,300),(0,255,0),5,4)
cv.line(img2,(40,0),(340,300),(0,0,255),5,16)
# 绘制矩形
cv.rectangle(img2,(10,40),(70,0),(255,255,0),-1)
# 绘制园
cv.circle(img2,(320,240),100,(255,255,0))
# 绘制椭圆
cv.ellipse(img2,(320,240),(100,50),30,0,180,(0,255,0))
# 绘制多边形
pts = np.array([(300,10),(150,100),(450,100)],np.int32)
print(pts)
cv.polylines(img2,[pts],True,(0,0,255))
# 填充多边形
cv.fillPoly(img2,[pts],(0,0,255))
# 画文本
cv.putText(img2,'hello world',(0,100),cv.FONT_HERSHEY_DUPLEX,3,(255,0,0))
cv.imshow('draw',img2)
cv.waitKey(0)
//十一、 opencv for python(3) 用opencv作图,直线,圆,填充字
import numpy as np
import cv2
img = np.zeros((512,512,3),np.uint8)
#画一条线,参数为图片,起点,终点,颜色,线条类型
cv2.line(img,(0,0),(512,512),(255,0,0),5)
#画矩形,参数为图片,左上角顶点,右下角顶点,颜色,线条类型
cv2.rectangle(img,(384,0),(510,128),(0,255,0),3)
#画圆,参数为图片,中心点坐标,半径,颜色,线条类型:填充
cv2.circle(img,(447,63),63,(0,0,255),-1)
#画椭圆,参数为图片,中心点坐标,长短轴,逆时针旋转的角度,
# 椭圆弧沿顺时针方向的起始角度和结束角度,颜色类型填充
cv2.ellipse(img,(256,256),(100,50),0,0,180,255,-1)
# pts = np.array([[10,5],[20,30],[70,20],[50,10]],np.int32)
# pts = pts.reshape((-1,1,2))
#在图片添加文字,参数为,图片,绘制文字,位置,字体类型,字体大小,颜色,线条类型
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img,'OpenCV',(10,500),font,4,(255,255,255),2)
winname = 'example'
cv2.namedWindow(winname)
cv2.imshow(winname,img)
cv2.waitKey(0)
cv2.destroyAllWindows(winname)
http://www.juzicode.com/opencv-python-polylines-puttext/
OpenCV-Python教程:绘制多边形、输出文字(polylines,putText,解决中文乱码问题)
11.1 多边形
cv2.polylines()用来画多边形。
第1个参数为图像对象;
第2个参数为包含一个三元组元素的列表,包含了多边形的各个顶点;
第3个参数为Bool型参数表示是否闭合;
第4个参数为颜色;
第5个参数为线条宽度,注意不能使用-1表示填充;
方形可以看做是一种特殊的多边形,知道其4个顶点的坐标也可以利用polylines()画出方形。下面这个例子利用polylines()画出一个方形和五角星:
import cv2
import numpy as np
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img = np.ones((512,512,3)) #白色背景
color=(0,255,0) #绿色
#五角星
pts = np.array([[70,190],[222,190],[280,61],[330,190],[467,190],
[358,260],[392,380],[280,308],[138,380],[195,260]])
pts = pts.reshape((-1,1,2)) #reshape为10x1x2的numpy
print(pts.shape) # (10, 1, 2)
cv2.polylines(img,[pts],True,color,5)
#方形
pts = np.array([[10,50],[100,50],[100,100],[10,100]])
pts = pts.reshape((-1,1,2))
cv2.polylines(img,[pts],True,color,3)
cv2.imshow('juzicode.com',img)
cv2.waitKey()
11.2 cv2.putText()用来输出文字。
第1个参数为图像对象;
第2个参数为要输出的字符串;
第3个参数为起始点位置;
第4个参数为字体;
第5个参数为字体大小;
第6个参数为颜色;
第7个参数为线条宽度,默认为1;
第8个参数为线型,默认为LINE8;
import cv2
import numpy as np
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img = np.ones((512,512,3)) #白色背景
text = 'VX:juzicode'
start = (10,200)
font = cv2.FONT_HERSHEY_SIMPLEX
fontscale = 2
color=(0,0,255) #红色
thick = 2
linetype = cv2.LINE_AA
cv2.putText(img,text,start, font, fontscale, color,thick,linetype)
cv2.imshow('juzicode.com',img)
cv2.waitKey()
上面的例子是输出英文,我们试下包含中文的情况,将text内容修改为“VX:桔子code”,输出乱码了:
通过dir()命令可以将所有FONT开头的内容打印出来,OpenCV中并没有包含中文字体:
一个变通的方法是通过pillow转换后显示中文:
import cv2
import numpy as np
from PIL import Image,ImageDraw,ImageFont
print('VX公众号: 桔子code / juzicode.com')
print('cv2.__version__:',cv2.__version__)
img = np.full((512,512,3),255,np.uint8) #白色背景
img_pil = Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
text = 'VX:桔子code'
start = (10,200) #起始位置
fonttype = 'msyh.ttc' #微软雅黑字体,和具体操作系统相关
fontscale = 30 #字体大小
font = ImageFont.truetype(fonttype,fontscale)
draw = ImageDraw.Draw(img_pil)
draw.text(start,text,font=font,fill=(255,0,0)) #PIL中BGR=(255,0,0)表示红色
img_ocv = np.array(img_pil) #PIL图片转换为numpy
img = cv2.cvtColor(img_ocv,cv2.COLOR_RGB2BGR) #PIL格式转换为OpenCV的BGR格式
cv2.imshow('juzicode.com',img)
cv2.waitKey()