1.图像的腐蚀和膨胀
前提:输入的是二值图像
腐蚀:用该像素周围像素集合中最小像素替换当前像素;
膨胀:用该像素周围像素集合中最大像素替换当前像素;
//读取图像
cv::Mat image=imread("binary.bmp");
//腐蚀图像
cv::erode(image,eroded,cv::Mat());```
//显示腐蚀后的图像
```cv:namedWindow("Eroded image");
cv::imshow("Eroded Image",eroded);```
//膨胀图像
```cv::Mat dilated;//目标图像
cv::dilate(image,dilated,cv::Mat());//cv::Mat()表示使用3*3的方形结构像素```
//显示膨胀后的图像
```cv:namedWindow("Dilated image");
cv::imshow("Dilated Image",dilated);```
#2.开闭计算
***闭运算:对图像先膨胀再腐蚀;***
```cv:Mat element5(5,5,CV_8U,cv::Scalar(1));
cv::Mat closed;
cv::morphologyEx(image,closed,cv::MORPH_CLOSE,element5);```
***开运算:对图像先腐蚀再膨胀;***
```cv::Mat opened;
cv::morphologyEx(image,opened,cv::MORPH_OPEN,element5);```
如果想先处理噪点,可以先进行开运算,再进行闭运算;
#3.使用形态学滤波对图像进行边缘及角点检测
***下面是如何进行直线检测:***
class MorphoFeatures{
private:
int threshold;//用于生成二值图像的阈值
cv::Mat cross;//角点检测中用到的结构元素//十字形
cv::Mat diamond;//菱形
cv::Mat x;//x型
cv::Mat square;//方形
public:
cv::Mat getEdges(const cv::Mat &image);
void applyThreshold(cv::Mat& result);
}
cv::Mat getEdges(const cv::Mat &image){
//得到梯度图
cv::Mat result;
cv::morphologyEx(image,result,cv::MORPH_GRADIENT,cv::Mat());
//阈值化以得到二值图像
applyThreshold(result);
return result;
}
void applyThreshold(cv::Mat& result){
//使用阈值化
if(threshold>0)
cv::threshold(result,result,threshold,255,cv::THRESH_BINARY);
}
int main(){
//创建形态学实例对象
MorphoFeatures morpho;
morpho.setThreshold(40);
//获取边缘
cv::Mat edges;
edges=morpho.getEdges(image);
}
下面是角点检测(opencv 没有直接实现它,事实上需要四种结构元素:方形,菱形,十字形,X形):
//构造函数
MorphoFeatures():threshold(-1),cross(5,5,CV_8U,cv::Scalar(0)),diamond(5,5,CV_8U,cv::Scalar(1)),square(5,5,CV_8U,cv::Scalar(1)),x(5,5,CV_8U,cv::Scalar(0)){
//创建十字形元素
for(int i=0;i<5;i++){
cross.at
cross.at
}
//创建菱形元素
diamond.at
diamond.at
diamond.at
diamond.at
diamond.at
diamond.at
diamond.at
diamond.at
diamond.at
diamond.at
diamond.at
diamond.at
//创建X形
for(i=0;i<5;i++){
x.at
x.at
}
}
cv::Mat getCorners(const cv::Mat &image){
cv::Mat result;
cv::dilate(image,result,cross);//十字形膨胀
cv::erode(result,result,diamond);//菱形腐蚀
cv::Mat result2;
cv::dilate(image,result2,x)//x形膨胀
cv::erode(result2,result2,square);//方形腐蚀
** cv::absdiff(result2,result,result);//通过对两张图像做差值得到角点图像**
applyThreshold(result);//阈值化以得到二值图
return result;
}
为了更好地可视化,在二值化图像上每个检测点绘制一个圆:
void drawOnImge(const cv::Mat& binary,cv::Mat& image){
cv::Mat_
cv::Mat_
//遍历每个元素
for(int i=0;i
cv::circle(image,cv::Point(i%image.step,i/image.step),5,cv::Scalar(255,0,0));
}
}
#4.使用分水岭算法对图像进行分割
分水岭分割方法,是一种基于拓扑理论的数学形态学的分割方法,其基本思想是把图像看作是测地学上的拓扑地貌,图像中每一点像素的灰度值表示该点的海拔高度,每一个局部极小值及其影响区域称为集水盆,而集水盆的边界则形成分水岭。分水岭的概念和形成可以通过模拟浸入过程来说明。在每一个局部极小值表面,刺穿一个小孔,然后把整个模型慢慢浸入水中,随着浸入的加深,每一个局部极小值的影响域慢慢向外扩展,在两个集水盆汇合处构筑大坝,即形成分水岭。
分水岭算法一般和区域生长法或聚类分析法相结合。
分水岭算法一般用于分割感兴趣的图像区域,应用如细胞边界的分割,分割出相片中的头像等等。
class WatershedSegmenter {
private:
cv::Mat markers;
public:
void setMarkers(const cv::Mat& markerImage) {
// Convert to image of ints
marker Image.convertTo(markers,CV_32S);
}
cv::Mat process(const cv::Mat &image) {
// Apply watershed
cv::watershed(image,markers);
return markers;
}
// Return result in the form of an image
cv::Mat getSegmentation() {
cv::Mat tmp;
// all segment with label higher than 255
// will be assigned value 255
markers.convertTo(tmp,CV_8U);
returntmp;
}
// Return watershed in the form of an image
cv::Mat getWatersheds() {
cv::Mat tmp;
markers.convertTo(tmp,CV_8U,255,255);
returnt tmp;
}
};```
5.grabcut算法进行图像分割
void cv::grabCut( const Mat& img, Mat& mask, Rect rect, Mat& bgdModel, Mat& fgdModel, int iterCount, int mode )
-img——待分割的源图像,必须是8位3通道(CV_8UC3)图像,在处理的过程中不会被修改;
-mask——掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保 存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果。mask只能取以下四种值:
GCD_BGD(=0),背景;
GCD_FGD(=1),前景;
GCD_PR_BGD(=2),可能的背景;
GCD_PR_FGD(=3),可能的前景。
如果没有手工标记GCD_BGD或者GCD_FGD,那么结果只会有GCD_PR_BGD或GCD_PR_FGD;
-rect——用于限定需要进行分割的图像范围,只有该矩形窗口内的图像部分才被处理;
-bgdModel——背景模型,如果为null,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为 1,列数只能为13x5;
-fgdModel——前景模型,如果为null,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型(CV_32FC1)图像,且行数只能为1, 列数只能为13x5;
-iterCount——迭代次数,必须大于0;
-mode——用于指示grabCut函数进行什么操作,可选的值有:
GC_INIT_WITH_RECT(=0),用矩形窗初始化GrabCut;
GC_INIT_WITH_MASK(=1),用掩码图像初始化GrabCut;
GC_EVAL(=2),执行分割
您可以按以下方式来使用GrabCut函数:
(1)用矩形窗或掩码图像初始化grabCut;
(2)执行分割;
(3)如果对结果不满意,在掩码图像中设定前景和(或)背景,再次执行分割;
(4)使用掩码图像中的前景或背景信息。
#include
#include
#include
usingnamespacecv;
usingnamespacestd;
#include
void getBinMask(constMat& comMask, Mat& binMask )
{
binMask.create( comMask.size(), CV_8UC1 );
binMask = comMask & 1;
}
int main(int argc,char** argv )
{
Mat image = imread("f:\\img\\lena.jpg", 1 );
conststring winName ="image";
imshow("src",image);
/***********************************/
Mat bg;Mat fg;
Rect rect = Rect(47,48,408,464);
Mat mask,res;
mask.create( image.size(), CV_8UC1);
grabCut( image, mask, rect, bg, fg, 1, 0 );
Mat binMask;
getBinMask( mask, binMask );
image.copyTo( res, binMask );
imshow("cut",res);
/***********************************/
waitKey(0);
return0;
}
6.Harris特征、SURF特征
基于特征点的图像匹配是图像处理中经常会遇到的问题,手动选取特征点太麻烦了。比较经典常用的特征点自动提取的办法有Harris特征、SIFT特征、SURF特征。
先介绍利用SURF特征的特征描述办法,其操作封装在类Surf FeatureDetector中,利用类内的detect函数可以检测出SURF特征的关键点,保存在vector容器中。第二部利用Surf DescriptorExtractor类进行特征向量的相关计算。将之前的vector变量变成向量矩阵形式保存在Mat中。最后强行匹配两幅图像的特征向量,利用了类BruteForceMatcher中的函数match。代码如下:
#include
#include
#include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"
using namespace cv;
int main(int argc,char** argv )
{
if( argc != 3 )
{return-1; }
Mat img_1 = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE );
Mat img_2 = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE );
if( !img_1.data || !img_2.data )
{return-1; }
//-- Step 1: Detect the keypoints using SURF Detector
int minHessian = 400;
SurfFeatureDetector detector( minHessian );
std::vector keypoints_1, keypoints_2;
detector.detect( img_1, keypoints_1 );
detector.detect( img_2, keypoints_2 );
//-- Step 2: Calculate descriptors (feature vectors)
SurfDescriptorExtractor extractor;
Mat descriptors_1, descriptors_2;
extractor.compute( img_1, keypoints_1, descriptors_1 );
extractor.compute( img_2, keypoints_2, descriptors_2 );
//-- Step 3: Matching descriptor vectors with a brute force matcher
BruteForceMatcher< L2 > matcher;
std::vector< DMatch > matches;
matcher.match( descriptors_1, descriptors_2, matches );
//-- Draw matches
Mat img_matches;
drawMatches( img_1, keypoints_1, img_2, keypoints_2, matches, img_matches );
//-- Show detected matches
imshow("Matches", img_matches );
waitKey(0);
return 0;
}```
当然,进行强匹配的效果不够理想,这里再介绍一种FLANN特征匹配算法。前两步与上述代码相同,第三步利用FlannBasedMatcher类进行特征匹配,并只保留好的特征匹配点,代码如下:
//-- Step 3: Matching descriptor vectors using FLANN matcher
FlannBasedMatcher matcher;
std::vector< DMatch > matches;
matcher.match( descriptors_1, descriptors_2, matches );
doublemax_dist = 0;doublemin_dist = 100;
//-- Quick calculation of max and min distances between keypoints
for(inti = 0; i < descriptors_1.rows; i++ )
{
doubledist = 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 );
//-- Draw only "good" matches (i.e. whose distance is less than 2min_dist )
//-- PS.- radiusMatch can also be used here.
std::vector< DMatch > good_matches;
for(inti = 0; i < descriptors_1.rows; i++ )
{
if( matches[i].distance < 2min_dist )
good_matches.push_back( matches[i]);
}
//-- Draw only "good" matches
Mat img_matches;
drawMatches( img_1, keypoints_1, img_2, keypoints_2,
good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
vector(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
//-- Show detected matches
imshow("Good Matches", img_matches );
然后再看一下Harris特征检测,在计算机视觉中,通常需要找出两帧图像的匹配点,如果能找到两幅图像如何相关,就能提取出两幅图像的信息。我们说的特征的最大特点就是它具有唯一可识别这一特点,图像特征的类型通常指边界、角点(兴趣点)、斑点(兴趣区域)。角点就是图像的一个局部特征,应用广泛。harris角点检测是一种直接基于灰度图像的角点提取算法,稳定性高,尤其对L型角点检测精度高,但由于采用了高斯滤波,运算速度相对较慢,角点信息有丢失和位置偏移的现象,而且角点提取有聚簇现象。具体实现就是使用函数cornerHarris实现。
除了利用Harris进行角点检测,还可以利用Shi-Tomasi方法进行角点检测。使用函数goodFeaturesToTrack对角点进行检测,效果也不错。也可以自己制作角点检测的函数,需要用到cornerMinEigenVal函数和minMaxLoc函数,最后的特征点选取,判断条件要根据自己的情况编辑。如果对特征点,角点的精度要求更高,可以用cornerSubPix函数将角点定位到子像素。