OPENCV第九章-图像分析与修复
积分图像
积分图像原理
在左图中,p0的积分就是就是左侧蓝色图形区域进行求和,p1就是前4列和前7行进行求和。
当求取的某些区域的像素平均值时,可以用积分图像,比如灰色区域的像素值等于P3-P2-P1+P0就是灰色区域的总体像素,先计算积分图像,然后再求取区域可以加快计算过程
积分图像函数
允许省略部分参数,比如省略sqsum和titled ,但是不能只生成sqsum,和titled
示例
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
//创建一个16×16全为1的矩阵,因为256=16×16
Mat img = Mat::ones(16, 16, CV_32FC1);
//在图像中加入随机噪声
RNG rng(10086);
for (int y = 0; y < img.rows; y++)
{
for (int x = 0; x < img.cols; x++)
{
float d = rng.uniform(-0.5, 0.5);
img.at<float>(y, x) = img.at<float>(y, x) + d;
}
}
//计算标准求和积分
Mat sum;
integral(img, sum);
//为了便于显示,转成CV_8U格式
Mat sum8U = Mat_<uchar>(sum);
//计算平方求和积分
Mat sqsum;
integral(img, sum, sqsum);
//为了便于显示,转成CV_8U格式
Mat sqsum8U = Mat_<uchar>(sqsum);
//计算倾斜求和积分
Mat tilted;
integral(img, sum, sqsum, tilted);
//为了便于显示,转成CV_8U格式
Mat tilted8U = Mat_<uchar>(tilted);
//输出结果
namedWindow("sum8U", WINDOW_NORMAL);
namedWindow("sqsum8U", WINDOW_NORMAL);
namedWindow("tilted8U", WINDOW_NORMAL);
imshow("sum8U", sum8U);
imshow("sqsum8U", sqsum8U);
imshow("tilted8U", tilted8U);
waitKey();
return 0;
}
图像分割
漫水填充方法
阈值是一个范围,为种子-loDiff 到 种子+upDiff
相关函数
种子1为20,loDif和upDif均为10,则当前为[10,30], 当下一个像素成为种子时候范围会改变,如果不想让其改变可使用FLOODFILL_FIXED_RANGE
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
system("color F0"); //将DOS界面调成白底黑字
Mat img = imread("lena.png");
if (!(img.data))
{
cout << "读取图像错误,请确认图像文件是否正确" << endl;
return -1;
}
RNG rng(10086);//随机数,用于随机生成像素
//设置操作标志flags
int connectivity = 4; //连通邻域方式
int maskVal = 255; //掩码图像的数值
int flags = connectivity | (maskVal << 8) | FLOODFILL_FIXED_RANGE; //漫水填充操作方式标志
//设置与选中像素点的差值
Scalar loDiff = Scalar(20, 20, 20);
Scalar upDiff = Scalar(20, 20, 20);
//声明掩模矩阵变量
Mat mask = Mat::zeros(img.rows + 2, img.cols + 2, CV_8UC1);
while (true)
{
//随机产生图像中某一像素点
int py = rng.uniform(0, img.rows - 1);
int px = rng.uniform(0, img.cols - 1);
Point point = Point(px, py);
//彩色图像中填充的像素值
Scalar newVal = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
//漫水填充函数
int area = floodFill(img, mask, point, newVal, 0, loDiff, upDiff, flags);
//输出像素点和填充的像素数目
cout << "像素点x:" << point.x << " y:" << point.y
<< " 填充像数数目:" << area << endl;
//输出填充的图像结果
imshow("填充的彩色图像", img);
imshow("掩模图像", mask);
//判断是否结束程序
int c = waitKey(0);
if ((c & 255) == 27)
{
break;
}
}
return 0;
}
分水岭法
分水岭方法介绍
分水岭法持续注水直到所有岭都被水淹没
markers作为输入的时候需要人为设定最低点(注水点),这样可以认为设置待分割的区域
#include <opencv2\opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat img, imgGray, imgMask, img_;
Mat maskWaterShed; // watershed()函数的参数
img = imread("lenaw.png"); //含有标记的图像
img_ = imread("lena.png"); //原图像
cvtColor(img, imgGray, COLOR_BGR2GRAY);
//二值化并开运算
threshold(imgGray, imgMask, 254, 255, THRESH_BINARY);
Mat k = getStructuringElement(0, Size(3, 3));
morphologyEx(imgMask, imgMask, MORPH_OPEN, k);
imshow("imgmask", imgMask);
imshow("含有标记的图像", img);
imshow("原图像", img_);
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(imgMask, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE);
//在maskWaterShed上绘制轮廓,用于输入分水岭算法,绘制轮廓的目的是标记所画的轮廓
maskWaterShed = Mat::zeros(imgMask.size(), CV_32S);
for (int index = 0; index < contours.size(); index++)
{
drawContours(maskWaterShed, contours, index, Scalar::all(index + 1), -1, 8, hierarchy, INT_MAX);//用当前的Index + 1作为颜色进行标记
}
//分水岭算法 需要对原图像进行处理
watershed(img_, maskWaterShed);
vector<Vec3b> colors; // 随机生成几种颜色
for (int i = 0; i < contours.size(); i++)
{
int b = theRNG().uniform(0, 255);
int g = theRNG().uniform(0, 255);
int r = theRNG().uniform(0, 255);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
Mat resultImg = Mat(img.size(), CV_8UC3); //显示图像
for (int i = 0; i < imgMask.rows; i++)
{
for (int j = 0; j < imgMask.cols; j++)
{
// 绘制每个区域的颜色
int index = maskWaterShed.at<int>(i, j);
if (index == -1) // 区域间的值被置为-1(边界)
{
resultImg.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
}
else if (index <= 0 || index > contours.size()) // 没有标记清楚的区域被置为0
{
resultImg.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
}
else // 其他每个区域的值保持不变:1,2,…,contours.size()
{
resultImg.at<Vec3b>(i, j) = colors[index - 1]; // 把些区域绘制成不同颜色
}
}
}
imshow("resultImg", resultImg);
resultImg = resultImg * 0.8 + img_ * 0.2;
//addWeighted(resultImg, 0.8, img_, 0.2, 0, resultImg);
imshow("分水岭结果", resultImg);
//绘制每个区域的图像
for (int n = 1; n <= contours.size(); n++)
{
Mat resImage1 = Mat(img.size(), CV_8UC3); // 声明一个最后要显示的图像
for (int i = 0; i < imgMask.rows; i++)
{
for (int j = 0; j < imgMask.cols; j++)
{
int index = maskWaterShed.at<int>(i, j);
if (index == n)
resImage1.at<Vec3b>(i, j) = img_.at<Vec3b>(i, j);
else
resImage1.at<Vec3b>(i, j) = Vec3b(0, 0, 0);
}
}
//显示图像
imshow(to_string(n), resImage1);
}
waitKey(0);
return 0;
}
Harris角点检测
定义矩形区域,求取区域像素值之和,移动之后再次求取像素值之和,如果移动之后差值较大则认为移动前存在一个角点
评价系数比较大的时候可能是角点,比较小的时候一定不是角点
寻找角点函数
k的通常取值为0.02-0.04
绘制角点函数
keypoint只需要输入坐标就行
示例
#include <opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("lena.png", IMREAD_COLOR);
if (!img.data)
{
cout << "读取图像错误,请确认图像文件是否正确" << endl;
return -1;
}
//转成灰度图像
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
//计算Harris系数
Mat harris;
int blockSize = 2; //邻域半径
int apertureSize = 3; //
cornerHarris(gray, harris, blockSize, apertureSize, 0.04);
//harris角点返回的32F类型
//归一化便于进行数值比较和结果显示
Mat harrisn;
normalize(harris, harrisn, 0, 255, NORM_MINMAX);
//将图像的数据类型变成CV_8U
convertScaleAbs(harrisn, harrisn);
//寻找Harris角点
vector<KeyPoint> keyPoints;
for (int row = 0; row < harrisn.rows; row++)
{
for (int col = 0; col < harrisn.cols; col++)
{
int R = harrisn.at<uchar>(row, col);
if (R > 125)
{
//向角点存入KeyPoint中
KeyPoint keyPoint;
keyPoint.pt.y = row;
keyPoint.pt.x = col;
keyPoints.push_back(keyPoint);
}
}
}
//绘制角点与显示结果
drawKeypoints(img, keyPoints, img);
imshow("系数矩阵", harrisn);
imshow("Harris角点", img);
waitKey(0);
return 0;
}
harris角点可以用于计算角度变化进行定位或者匹配等其他操作
shi-tomas角点
tomas角点是harris角点的改进版
shi-tomas角点检测原理
选取的二者的最小值
示例
#include <opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("lena.png");
if (!img.data)
{
cout << "读取图像错误,请确认图像文件是否正确" << endl;
return -1;
}
//深拷贝用于第二种方法绘制角点
Mat img2;
img.copyTo(img2);
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
// Detector parameters
//提取角点
int maxCorners = 100; //检测角点数目
double quality_level = 0.01; //质量等级,或者说阈值与最佳角点的比例关系
double minDistance = 0.04; //两个角点之间的最小欧式距离
vector<Point2f> corners;
goodFeaturesToTrack(gray, corners, maxCorners, quality_level, minDistance, Mat(), 3, false);
//绘制角点
vector<KeyPoint> keyPoints; //存放角点的KeyPoint类,用于后期绘制角点时用
RNG rng(10086);
for (int i = 0; i < corners.size(); i++)
{
//第一种方式绘制角点,用circle()函数绘制角点
int b = rng.uniform(0, 256);
int g = rng.uniform(0, 256);
int r = rng.uniform(0, 256);
circle(img, corners[i], 5, Scalar(b, g, r), 2, 8, 0);
//将角点存放在KeyPoint类中
KeyPoint keyPoint;
keyPoint.pt = corners[i];
keyPoints.push_back(keyPoint);
}
//第二种方式绘制角点,用drawKeypoints()函数
drawKeypoints(img2, keyPoints, img2);
//输出绘制角点的结果
imshow("用circle()函数绘制角点结果", img);
imshow("通过绘制关键点函数绘制角点结果", img2);
waitKey(0);
return 0;
}
亚像素级别的角点位置优化
原理

函数
示例
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
using namespace cv;
using namespace std;
int main()
{
system("color F0"); //改变DOS界面颜色
Mat img = imread("lena.png", IMREAD_COLOR);
if (!img.data)
{
cout << "读取图像错误,请确认图像文件是否正确" << endl;
return -1;
}
//彩色图像转成灰度图像
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
//提取角点
int maxCorners = 100; //检测角点数目
double quality_level = 0.01; //质量等级,或者说阈值与最佳角点的比例关系
double minDistance = 0.04; //两个角点之间的最小欧式距离
vector<Point2f> corners;
goodFeaturesToTrack(gray, corners, maxCorners, quality_level, minDistance, Mat(), 3, false);
//计算亚像素级别角点坐标
vector<Point2f> cornersSub = corners; //角点备份,方式别函数修改
Size winSize = Size(5, 5);
Size zeroZone = Size(-1, -1);
TermCriteria criteria = TermCriteria(TermCriteria::EPS + TermCriteria::COUNT, 40, 0.001);//EPS是次数,COUNT是精度,后面的两个是分别的参数
cornerSubPix(gray, cornersSub, winSize, zeroZone, criteria);
//输出初始坐标和精细坐标
for (size_t i = 0; i < corners.size(); i++)
{
string str = to_string(i);
str = "第" + str + "个角点点初始坐标:";
cout << str << corners[i] << " 精细后坐标:" << cornersSub[i] << endl;
}
return 0;
}
特征点
Features2D类
features2D文档 定义基类,之后让具体的子类继承基类即可 子类没有定义基本函数,而是通过定义子类之后使用基类的函数进行运算
ORB特征点
ORB特征点原理
在高斯金字塔的每一层都分别进行计算fast角点,在每一个尺度下面都进行查找,在每一个尺度下面都有才输出为ORB特征点
ORB特征点具有尺度不变性
函数
scoreType有HARRIS_SCORE 和FAST_SCORE方法
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace std;
using namespace cv;
int main()
{
Mat img = imread("lena.png");
if (!img.data)
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
//创建 ORB 特征点类变量
Ptr<ORB> orb = ORB::create(500, //特征点数目
1.2f, //金字塔层级之间的缩放比例
8, //金字塔图像层数系数
31, //边缘阈值
0, //原图在金字塔中的层数
2, //生成描述子时需要用的像素点数目
ORB::HARRIS_SCORE, //使用 Harris 方法评价特征点
31, //生成描述子时关键点周围邻域的尺寸
20 //计算 FAST 角点时像素值差值的阈值
);
//计算 ORB 关键点
vector<KeyPoint> Keypoints;
orb->detect(img, Keypoints); //确定关键点
//计算 ORB 描述子
Mat descriptions;
orb->compute(img, Keypoints, descriptions); //计算描述子
//绘制特征点
Mat imgAngel;
img.copyTo(imgAngel);
//绘制不含角度和大小的结果
drawKeypoints(img, Keypoints, img, Scalar(255, 255, 255));
//绘制含有角度和大小的结果
drawKeypoints(img, Keypoints, imgAngel, Scalar(255, 255, 255), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
//显示结果
imshow("不含角度和大小的结果", img);
imshow("含有角度和大小的结果", imgAngel);
waitKey(0);
return 0;
}
特征点匹配
在一张图片中匹配到部分特征点,相机移动之后在另一张图像中查找相同特征点从而计算两张图象的关系
特征点匹配类
描述符之间距离越短性能越好
特征点匹配函数
特征点函数也继承了一个基类,既(DescriptorMatcher)
方法1 一一匹配
queryDescriptors需要输入的特征点的描述子而不是坐标,之后的也是
方法2 在查询描述子选择一个,在训练描述子集合里面选择若干个描述子
matches外层是第i个查询描述子,内层是第i个查询描述子匹配的若干个描述子
方法3 根据距离的方式进行匹配,输出所有小于距离阈值的点
一般都是1对1匹配
以上都是基类中的方法,在所有方法中只要继承基类就可以用这些方法
暴力匹配
将1集合的每一个特征子和2集合的每一个特征子进行比较
使用ORB特征子时应使用NORM_HAMMING或NORM_HAMMING2,因为ORB是128的描述子,每一个描述符不是0就是1
一般来说要对匹配结果进行优化
绘制匹配图像
两张图片尺寸可以不一致
#include <opencv2\opencv.hpp>
#include <iostream>
#include <vector>
using namespace std;
using namespace cv;
void orb_features(Mat& gray, vector<KeyPoint>& keypionts, Mat& descriptions)
{
Ptr<ORB> orb = ORB::create(1000, 1.2f);
orb->detect(gray, keypionts);
orb->compute(gray, keypionts, descriptions);
}
int main()
{
Mat img1, img2;
img1 = imread("box.png");
img2 = imread("box_in_scene.png");
if (!(img1.data && img2.dataend))
{
cout << "读取图像错误,请确认图像文件是否正确" << endl;
return -1;
}
//提取ORB特征点
vector<KeyPoint> Keypoints1, Keypoints2;
Mat descriptions1, descriptions2;
//计算特征点
orb_features(img1, Keypoints1, descriptions1);
orb_features(img2, Keypoints2, descriptions2);
//特征点匹配
vector<DMatch> matches; //定义存放匹配结果的变量
BFMatcher matcher(NORM_HAMMING); //定义特征点匹配的类,使用汉明距离
matcher.match(descriptions1, descriptions2, matches); //进行特征点匹配
cout << "matches=" << matches.size() << endl; //匹配成功特征点数目
//通过汉明距离删选匹配结果
double min_dist = 10000, max_dist = 0;
for (int i = 0; i < matches.size(); i++)//计算最小的汉明距离与最大的汉明距离
{
double dist = matches[i].distance;
if (dist < min_dist) min_dist = dist;
if (dist > max_dist) max_dist = dist;
}
//输出所有匹配结果中最大韩明距离和最小汉明距离
cout << "min_dist=" << min_dist << endl;
cout << "max_dist=" << max_dist << endl;
//将汉明距离较大的匹配点对删除
vector<DMatch> good_matches;
for (int i = 0; i < matches.size(); i++)
{
if (matches[i].distance <= max(2 * min_dist, 20.0))
{
good_matches.push_back(matches[i]);
}
}
cout << "good_min=" << good_matches.size() << endl; //剩余特征点数目
//绘制匹配结果
Mat outimg, outimg1;
drawMatches(img1, Keypoints1, img2, Keypoints2, matches, outimg);
drawMatches(img1, Keypoints1, img2, Keypoints2, good_matches, outimg1);
imshow("未筛选结果", outimg);
imshow("最小汉明距离筛选", outimg1);
waitKey(0);
return 0;
}
RANSAC算法匹配(RANdom SAmple Consensus)
假设两张图片之间存在单应矩阵,只需要四个匹配的点就计算单应矩阵
对但应矩阵去测试其他数据点,根据匹配结果计算匹配的点与正确匹配的结果的重投影误差。反复循环找出最佳的单应矩阵,在最佳的单应矩阵下筛掉错配特征点
函数
用于计算单应矩阵
重投影误差小于第四个参数就认定为正确的匹配
置信区间通常使用默认值,置信区间越接近1就越可信
迭代次数默认2000
RANSAC是随机选取数据,数据量比较大的时候正确性就比较低,否则需要增加迭代次数,所以建议用初步优化的数据
#include <iostream>
#include <opencv2\opencv.hpp>
#include <vector>
using namespace std;
using namespace cv;
void match_min(vector<DMatch> matches, vector<DMatch>& good_matches)
{
double min_dist = 10000, max_dist = 0;
for (int i = 0; i < matches.size(); i++)
{
double dist = matches[i].distance;
if (dist < min_dist) min_dist = dist;
if (dist > max_dist) max_dist = dist;
}
cout << "min_dist=" << min_dist << endl;
cout << "max_dist=" << max_dist << endl;
for (int i = 0; i < matches.size(); i++)
if (matches[i].distance <= max(2 * min_dist, 20.0))
good_matches.push_back(matches[i]);
}
//RANSAC算法实现
void ransac(vector<DMatch> matches, vector<KeyPoint> queryKeyPoint, vector<KeyPoint> trainKeyPoint, vector<DMatch>& matches_ransac)
{
//定义保存匹配点对坐标
vector<Point2f> srcPoints(matches.size()), dstPoints(matches.size());
//保存从关键点中提取到的匹配点对的坐标
for (int i = 0; i < matches.size(); i++)
{
srcPoints[i] = queryKeyPoint[matches[i].queryIdx].pt;
dstPoints[i] = trainKeyPoint[matches[i].trainIdx].pt;
}
//匹配点对进行RANSAC过滤
vector<int> inliersMask(srcPoints.size());
//Mat homography;
//homography = findHomography(srcPoints, dstPoints, RANSAC, 5, inliersMask);
findHomography(srcPoints, dstPoints, RANSAC, 5, inliersMask);//返回值为单应矩阵,可以不用
//手动的保留RANSAC过滤后的匹配点对
for (int i = 0; i < inliersMask.size(); i++)
if (inliersMask[i])
matches_ransac.push_back(matches[i]);
}
void orb_features(Mat& gray, vector<KeyPoint>& keypionts, Mat& descriptions)
{
Ptr<ORB> orb = ORB::create(1000, 1.2f);
orb->detect(gray, keypionts);
orb->compute(gray, keypionts, descriptions);
}
int main()
{
Mat img1 = imread("box.png"); //读取图像,根据图片所在位置填写路径即可
Mat img2 = imread("box_in_scene.png");
if (!(img1.data && img2.data))
{
cout << "读取图像错误,请确认图像文件是否正确" << endl;
return -1;
}
//提取ORB特征点
vector<KeyPoint> Keypoints1, Keypoints2;
Mat descriptions1, descriptions2;
//基于区域分割的ORB特征点提取
orb_features(img1, Keypoints1, descriptions1);
orb_features(img2, Keypoints2, descriptions2);
//特征点匹配
vector<DMatch> matches, good_min, good_ransac;
BFMatcher matcher(NORM_HAMMING);
matcher.match(descriptions1, descriptions2, matches);
cout << "matches=" << matches.size() << endl;
//最小汉明距离
match_min(matches, good_min);
cout << "good_min=" << good_min.size() << endl;
//用ransac算法筛选匹配结果
ransac(good_min, Keypoints1, Keypoints2, good_ransac);
cout << "good_matches.size=" << good_ransac.size() << endl;
//绘制匹配结果
Mat outimg, outimg1, outimg2;
drawMatches(img1, Keypoints1, img2, Keypoints2, matches, outimg);
drawMatches(img1, Keypoints1, img2, Keypoints2, good_min, outimg1);
drawMatches(img1, Keypoints1, img2, Keypoints2, good_ransac, outimg2);
imshow("未筛选结果", outimg);
imshow("最小汉明距离筛选", outimg1);
imshow("ransac筛选", outimg2);
waitKey(0); //等待键盘输入
return 0; //程序结束
}
当然不出意外的运行时间超级加倍,效果较好,但是如果对时间有要求的话最好还是用最小汉明距离的方式