数学形态学用于分析和处理离散图像,它定义了一系列的运算,用预定义的形状元素探测图像,从而实现图像的转换。
1、形态学滤波器腐蚀和膨胀图像
结构元素定义为像素的组合,在对应的像素上定义一个原点(锚点)。结构元素可以是任意形状,如正方形,圆形等,中心点为原点。
OpenCV实现腐蚀和膨胀运算: cv::erode, cv::dilate
cv::Mat image=cv::imread("xxxx.jpg");
//腐蚀图像
cv::Mat erode;
cv::erode(image,erode,cv::Mat());
//膨胀图像
cv::Mat dilate;
cv::dilate(image,dilate,cv::Mat());
腐蚀就是把当前像素替换成所定义像素集合中的最小值;膨胀与腐蚀相反,它把当前像素替换成所定义像素集合中的最大值;
腐蚀图像相当于对其反色图像膨胀后再取反色;膨胀图像相当于对其反色图像腐蚀后再取反色。
2、用形态学滤波器开启和闭合图像
开启的定义是对图像先膨胀后腐蚀;闭合的定义是对图像先腐蚀后膨胀。用于目标检测时,闭合滤波器可把错误分裂成小碎片的物体连接起来,开启滤波器可以一处因图像噪声产生的斑点。
cv::Mat element5(5,5,CV_8U,cv::Scalar(1));
//关闭
cv::Mat closed;
cv::morphologyEx(image,closed,cv::MORPH_CLOSE,element5);
//开启
cv::opened;
cv::morphologyEx(image,opened,cv::MORPH_OPEN,element5);
3、用形态学滤波器检测边缘和脚点
形态学滤波器可以用于检测图像中的某些特征。
实现原理:把图像看做是一个拓扑地貌,不同的灰度级别代表不同的高度,因此,明亮的区域代表高山,黑暗的区域代表深谷。边缘相当于黑暗和明亮像素之间的过渡,可以把边缘比作是悬崖,腐蚀的结果是:每个像素被替换成领域内的最小值,从而降低了它的高度。结果悬崖被“腐蚀”,山谷扩大;膨胀的效果刚好相反,即悬崖扩大,山谷缩小。平底的强度始终不不变。
检测边缘的方法:通过计算膨胀后的图像与腐蚀后的图像之间的差距得到边缘 。
定义特征检测类:
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
class MorphoFeatures {
private:
// threshold to produce binary image
int threshold;
// structuring elements used in corner detection
cv::Mat_<uchar> cross; //十字形
cv::Mat_<uchar> diamond;//菱形
cv::Mat_<uchar> square;//正方形
cv::Mat_<uchar> x; //x形
void applyThreshold(cv::Mat& result) {
// Apply threshold on result
if (threshold>0)
cv::threshold(result, result, threshold, 255, cv::THRESH_BINARY_INV);
}
public:
MorphoFeatures() : threshold(-1),
cross(5, 5), diamond(5, 5), square(5, 5), x(5, 5) {
// Creating the cross-shaped structuring element
cross <<
0, 0, 1, 0, 0,
0, 0, 1, 0, 0,
1, 1, 1, 1, 1,
0, 0, 1, 0, 0,
0, 0, 1, 0, 0;
// Creating the diamond-shaped structuring element
diamond <<
0, 0, 1, 0, 0,
0, 1, 1, 1, 0,
1, 1, 1, 1, 1,
0, 1, 1, 1, 0,
0, 0, 1, 0, 0;
// Creating the x-shaped structuring element
x <<
1, 0, 0, 0, 1,
0, 1, 0, 1, 0,
0, 0, 1, 0, 0,
0, 1, 0, 1, 0,
1, 0, 0, 0, 1;
// Creating the square-shaped structuring element
x <<
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1;
}
void setThreshold(int t) {
threshold= t;
}
int getThreshold() const {
return threshold;
}
cv::Mat getEdges(const cv::Mat &image) {
// Get the gradient image
cv::Mat result;
cv::morphologyEx(image,result,cv::MORPH_GRADIENT,cv::Mat());
// Apply threshold to obtain a binary image
applyThreshold(result);
return result;
}
cv::Mat getCorners(const cv::Mat &image) {
cv::Mat result;
// Dilate with a cross
cv::dilate(image,result,cross);
// Erode with a diamond
cv::erode(result,result,diamond);
cv::Mat result2;
// Dilate with a X
cv::dilate(image,result2,x);
// Erode with a square
cv::erode(result2,result2,square);
// Corners are obtained by differencing
// the two closed images
cv::absdiff(result2,result,result);
// Apply threshold to obtain a binary image
applyThreshold(result);
return result;
}
void drawOnImage(const cv::Mat& binary, cv::Mat& image) {
cv::Mat_<uchar>::const_iterator it= binary.begin<uchar>();
cv::Mat_<uchar>::const_iterator itend= binary.end<uchar>();
// for each pixel
for (int i=0; it!= itend; ++it,++i) {
if (!*it)
cv::circle(image,cv::Point(i%image.step,i/image.step),5,cv::Scalar(255,0,0));
}
}
};
使用:
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include "morphoFeatures.h"
int main()
{
// Read input image
cv::Mat image= cv::imread("house2.jpg",0);
if (!image.data)
return 0;
// resize for book printing
cv::resize(image, image, cv::Size(), 0.7, 0.7);
// Display the image
cv::namedWindow("Image");
cv::imshow("Image",image);
// Create the morphological features instance
MorphoFeatures morpho;
morpho.setThreshold(40);
// Get the edges
cv::Mat edges;
edges= morpho.getEdges(image);
// Display the edge image
cv::namedWindow("Edge Image");
cv::imshow("Edge Image",edges);
// Get the corners
morpho.setThreshold(-1);
cv::Mat corners;
corners= morpho.getCorners(image);
cv::morphologyEx(corners,corners,cv::MORPH_TOPHAT,cv::Mat());
cv::threshold(corners, corners, 35, 255, cv::THRESH_BINARY_INV);
// Display the corner image
cv::namedWindow("Corner Image");
cv::imshow("Corner Image",corners);
// Display the corner on the image
morpho.drawOnImage(corners,image);
cv::namedWindow("Corners on Image");
cv::imshow("Corners on Image",image);
return 0;
}
4、使用MSER算法提取特征区域
最大稳定值区域(MSER)算法可以从图像和总提取有意义的区域。
class MSERFeatures {
private:
cv::MSER mser; // mser detector
double minAreaRatio; // extra rejection parameter
public:
MSERFeatures(int minArea=60, int maxArea=14400, // aceptable size range
double minAreaRatio=0.5, // min value for MSER area/bounding-rect area
int delta=5, // delta value used for stability measure
double maxVariation=0.25, // max allowed area variation
double minDiversity=0.2) // min size increase between child and parent
: mser(delta,minArea,maxArea,maxVariation,minDiversity),
minAreaRatio(minAreaRatio) {}
// get the rotated bouding rectangles corresponding to each MSER feature
// if (mser area / bounding rect area) < areaRatio, the feature is rejected
void getBoundingRects(const cv::Mat &image, std::vector<cv::RotatedRect> &rects) {
// detect MSER features
std::vector<std::vector<cv::Point> > points;
mser(image, points);
// for each detected feature
for (std::vector<std::vector<cv::Point> >::iterator it= points.begin();
it!= points.end(); ++it) {
// Extract bouding rectangles
cv::RotatedRect rr= cv::minAreaRect(*it);
// check area ratio
if (it->size() > minAreaRatio*rr.size.area())
rects.push_back(rr);
}
}
// draw the rotated ellipses corresponding to each MSER feature
cv::Mat getImageOfEllipses(const cv::Mat &image, std::vector<cv::RotatedRect> &rects, cv::Scalar color=255) {
// image on which to draw
cv::Mat output= image.clone();
// get the MSER features
getBoundingRects(image, rects);
// for each detected feature
for (std::vector<cv::RotatedRect>::iterator it= rects.begin();
it!= rects.end(); ++it) {
cv::ellipse(output,*it,color);
}
return output;
}
double getAreaRatio() {
return minAreaRatio;
}
void setAreaRatio(double ratio) {
minAreaRatio= ratio;
}
};
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <vector>
#include "mserFeatures.h"
int main()
{
// Read input image
cv::Mat image= cv::imread("graze.jpg",0);
if (!image.data)
return 0;
// resize for book printing
cv::resize(image, image, cv::Size(), 0.7, 0.7);
// Display the image
cv::namedWindow("Image");
cv::imshow("Image",image);
// basic MSER detector
cv::MSER mser(5, // delta value for local minima detection
200, // min acceptable area
1500); // max acceptable area
// vector of point sets
std::vector<std::vector<cv::Point> > points;
// detect MSER features
mser(image, points);
std::cout << points.size() << " MSERs detected" << std::endl;
// create white image
cv::Mat output(image.size(),CV_8UC3);
output= cv::Scalar(255,255,255);
// random number generator
cv::RNG rng;
// for each detected feature
for (std::vector<std::vector<cv::Point> >::iterator it= points.begin();
it!= points.end(); ++it) {
// generate a random color
cv::Vec3b c(rng.uniform(0,255),
rng.uniform(0,255),
rng.uniform(0,255));
std::cout << "MSER size= " << it->size() << std::endl;
// for each point in MSER set
for (std::vector<cv::Point>::iterator itPts= it->begin();
itPts!= it->end(); ++itPts) {
//do not overwrite MSER pixels
if (output.at<cv::Vec3b>(*itPts)[0]==255) {
output.at<cv::Vec3b>(*itPts)= c;
}
}
}
cv::namedWindow("MSER point sets");
cv::imshow("MSER point sets",output);
cv::imwrite("mser.bmp", output);
// detection using mserFeatures class
// create MSER feature detector instance
MSERFeatures mserF(200, // min area
1500, // max area
0.5); // ratio area threshold
// default delta is used
// the vector of bounding rotated rectangles
std::vector<cv::RotatedRect> rects;
// detect and get the image
cv::Mat result= mserF.getImageOfEllipses(image,rects);
// display detected MSER
cv::namedWindow("MSER regions");
cv::imshow("MSER regions",result);
cv::waitKey();
}