In this chapter, we will cover:
- Eroding and dilating images using morphological filters
- Opening and closing images using morphological filters
- Detecting edges and corners using morphological filters
- Segmenting images using watersheds 分水岭算法
- Extracting foreground objects with the GrabCut algorithm
Eroding、dilating、Opening、closing
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
int main()
{
// Read input image
cv::Mat image= cv::imread("../binary.bmp" );
if (!image.data)
return 0;
// Display the image
cv::namedWindow( "Image");
cv::imshow( "Image",image);
// Erode the image
cv::Mat eroded;
cv::erode(image,eroded,cv::Mat());
// Display the eroded image
cv::namedWindow( "Eroded Image");
cv::imshow( "Eroded Image",eroded);
// Dilate the image
cv::Mat dilated;
cv::dilate(image,dilated,cv::Mat());
// Display the dialted image
cv::namedWindow( "Dilated Image");
cv::imshow( "Dilated Image",dilated);
// Erode the image with a larger s.e.
cv::Mat element(7,7,CV_8U,cv::Scalar(1));
cv::erode(image,eroded,element);
// Display the eroded image
cv::namedWindow( "Eroded Image (7x7)");
cv::imshow( "Eroded Image (7x7)",eroded);
// Erode the image 3 times.
cv::erode(image,eroded,cv::Mat(),cv::Point(-1,-1),3);
// Display the eroded image
cv::namedWindow( "Eroded Image (3 times)");
cv::imshow( "Eroded Image (3 times)",eroded);
// Close the image
cv::Mat element5(5,5,CV_8U,cv::Scalar(1));
cv::Mat closed;
cv::morphologyEx(image,closed,cv::MORPH_CLOSE,element5);
// Display the opened image
cv::namedWindow( "Closed Image");
cv::imshow( "Closed Image",closed);
// Open the image
cv::Mat opened;
cv::morphologyEx(image,opened,cv::MORPH_OPEN,element5);
// Display the opened image
cv::namedWindow( "Opened Image");
cv::imshow( "Opened Image",opened);
// Close and Open the image
cv::morphologyEx(image,image,cv::MORPH_CLOSE,element5);
cv::morphologyEx(image,image,cv::MORPH_OPEN,element5);
// Display the close/opened image
cv::namedWindow( "Closed and Opened Image");
cv::imshow( "Closed and Opened Image",image);
cv::imwrite( "binaryGroup.bmp",image);
// Read input image
image= cv::imread("../binary.bmp");
// Open and Close the image
cv::morphologyEx(image,image,cv::MORPH_OPEN,element5);
cv::morphologyEx(image,image,cv::MORPH_CLOSE,element5);
// Display the close/opened image
cv::namedWindow( "Opened and Closed Image");
cv::imshow( "Opened and Closed Image",image);
cv::waitKey();
return 0;
}
results:
Detecting edges and corners using morphological filters
morphoFeatures.h
#if !defined MORPHOF
#define MORPHOF
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
class MorphoFeatures {
private:
// threshold to produce binary image
int threshold;
// structuring elements used in corner detection
cv::Mat cross;
cv::Mat diamond;
cv::Mat square;
cv::Mat x;
public:
MorphoFeatures() : threshold(-1),
cross(5, 5, CV_8U, cv::Scalar(0)),
diamond(5, 5, CV_8U, cv::Scalar(0)),
square(5, 5, CV_8U, cv::Scalar(0)),
x(5, 5, CV_8U, cv::Scalar(0)) {
// Creating the cross-shaped structuring element
for (int i = 0; i < 5; i++) {
cross.at<uchar>(2, i) = 1;
cross.at<uchar>(i, 2) = 1;
}
// Creating the diamond-shaped structuring element
diamond.at<uchar>(0, 0) = 0;
diamond.at<uchar>(0, 1) = 0;
diamond.at<uchar>(1, 0) = 0;
diamond.at<uchar>(4, 4) = 0;
diamond.at<uchar>(3, 4) = 0;
diamond.at<uchar>(4, 3) = 0;
diamond.at<uchar>(4, 0) = 0;
diamond.at<uchar>(4, 1) = 0;
diamond.at<uchar>(3, 0) = 0;
diamond.at<uchar>(0, 4) = 0;
diamond.at<uchar>(0, 3) = 0;
diamond.at<uchar>(1, 4) = 0;
// Creating the x-shaped structuring element
for (int i = 0; i < 5; i++) {
x.at<uchar>(i, i) = 1;
x.at<uchar>(4 - i, i) = 1;
}
}
void setThreshold(int t) {
if (t > 0)
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;
}
void applyThreshold(cv::Mat &result) {
// Apply threshold on result
if (threshold > 0) {
cv::threshold(result, result, threshold, 255, cv::THRESH_BINARY_INV);
}
}
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));
}
}
}
};
#endif
morph.cpp
#include <iostream>
#include "morphoFeatures.h"
int main() {
cv::Mat image = cv::imread( "../building.jpg");
cv::cvtColor(image, image, CV_BGR2GRAY);
// Create the morphological features instance
MorphoFeatures morpho;
morpho.setThreshold(40);
// Get the edges
cv::Mat edges;
edges = morpho.getEdges(image);
cv::namedWindow( "Edges Image", CV_WINDOW_AUTOSIZE);
cv::imshow( "Edges Image", edges);
// Get the corners
cv::Mat corners;
corners = morpho.getCorners(image);
// Display the corner on the image
morpho.drawOnImage(corners, image);
cv::namedWindow( "Corners on Image", CV_WINDOW_AUTOSIZE);
cv::imshow( "Corners on Image", image);
cv::waitKey(0);
return 0;
}
results:
Segmenting images using watersheds
watershedSegment.h
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
class WatershedSegmenter {
private:
cv::Mat markers;
public:
void setMarkers(const cv::Mat &markerImage) {
// Convert to image of ints
markerImage.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);
return tmp;
}
// Return watershed in the form of an image
cv::Mat getWatersheds() {
cv::Mat tmp;
// Each pixel p is transform into
// 255p + 255 befor conversion
markers.convertTo(tmp, CV_8U, 255, 255);
return tmp;
}
};
// Read input image
cv::Mat image = cv::imread( "../group.jpg");
if (!image.data) {
return 0;
}
// Display the image
cv::namedWindow( "Original Image");
cv::imshow( "Original Image", image);
// Get the binary image
cv::Mat binary;
binary = cv::imread( "../binary.bmp", 0);
// Display the binary image
cv::namedWindow( "Binary Image");
cv::imshow( "Binary Image", binary);
// Eliminate noise and smaller objects
cv::Mat fg;
cv::erode(binary, fg, cv::Mat(), cv::Point(-1, -1), 6);
// Display the foreground image
cv::namedWindow( "Foreground Image");
cv::imshow( "Foreground Image", fg);
results:
// Identify image pixels without objects
cv::Mat bg;
cv::dilate(binary, bg, cv::Mat(), cv::Point(-1, -1), 6);
cv::threshold(bg, bg, 1, 128, cv::THRESH_BINARY_INV);
// Display the backgroud image
cv::namedWindow( "Background Image");
cv::imshow( "Background Image", bg);
results:
// Show markers image
cv::Mat markers(binary.size(), CV_8U, cv::Scalar(0));
markers = fg + bg;
cv::namedWindow( "Markers");
cv::imshow( "Markers", markers);
// Create watershed segmentation object
WatershedSegmenter segmenter;
// Set markers and process
segmenter.setMarkers(markers);
segmenter.process(image);
// Display segmentation result
cv::namedWindow( "Segmentation");
cv::imshow( "Segmentation", segmenter.getSegmentation());
// Display watersheds
cv::namedWindow( "Watershed");
cv::imshow( "Watershed", segmenter.getWatersheds());
// Open another image------------------------------------
image = cv::imread( "../tower.jpg");
// Identify background pixels
cv::Mat imageMask(image.size(), CV_8U, cv::Scalar(0));
cv::rectangle(imageMask, cv::Point(5, 5), cv::Point(image.cols - 5, image.rows - 5), cv::Scalar(255), 3);
// Identify forground pixels (in the middle of the image)
cv::rectangle(imageMask, cv::Point(image.cols / 2 - 10, image.rows / 2 - 10),
cv::Point(image.cols / 2 + 10, image.rows / 2 + 10), cv::Scalar(1), 10);
// Set markers and process
segmenter.setMarkers(imageMask);
segmenter.process(image);
// Display the image with markers
cv::rectangle(image, cv::Point(5, 5), cv::Point(image.cols - 5, image.rows - 5), cv::Scalar(255, 255, 255), 3);
cv::rectangle(image, cv::Point(image.cols / 2 - 10, image.rows / 2 - 10),
cv::Point(image.cols / 2 + 10, image.rows / 2 + 10), cv::Scalar(1, 1, 1), 10);
cv::namedWindow( "Image with marker");
cv::imshow( "Image with marker", image);
// Display watersheds
cv::namedWindow( "Watersheds of foreground object");
cv::imshow( "Watersheds of foreground object", segmenter.getWatersheds());
results:
Extracting foreground objects with the GrabCut algorithm
// Open another image
image = cv::imread( "../tower.jpg");
// define bounding rectange
cv::Rect rectangle(50, 70, image.cols - 150, image.rows - 180);
cv::Mat result; // segmentation result (4 possible values)
cv::Mat bgModel, fgModel; // the models (internally used)
// GrabCut segmentation
cv::grabCut(image, // input image
result, // segmentation result
rectangle, // rectangle containing foreground
bgModel, fgModel, // models
1, //number of iterations
cv::GC_INIT_WITH_RECT// use rectangle
);
// Get the pixles marked as likely foreground
cv::compare(result, cv::GC_PR_FGD, result, cv::CMP_EQ);
// Generate output image
cv::Mat foreground(image.size(), CV_8UC3, cv::Scalar(255, 255, 255));
image.copyTo(foreground, result); // bg pixels not copied
// draw rectangle on original image
cv::rectangle(image, rectangle, cv::Scalar(255,255,255),1);
cv::namedWindow( "Image");
cv::imshow( "Image",image);
// display result
cv::namedWindow( "Segmented Image");
cv::imshow( "Segmented Image",foreground);
// Open another image
image= cv::imread("../group.jpg");
// define bounding rectangle
cv::Rect rectangle2(10,100,380,180);
cv::Mat bkgModel,fgrModel; // the models (internally used)
// GrabCut segmentation
cv::grabCut(image, // input image
result, // segmentation result
rectangle2,bkgModel,fgrModel,5,cv::GC_INIT_WITH_RECT);
// Get the pixels marked as likely foreground
// cv::compare(result,cv::GC_PR_FGD,result,cv::CMP_EQ);
result= result&1;
foreground.create(image.size(),CV_8UC3);
foreground.setTo(cv::Scalar(255,255,255));
image.copyTo(foreground,result); // bg pixels not copied
// draw rectangle on original image
cv::rectangle(image, rectangle2, cv::Scalar(255,255,255),1);
cv::namedWindow( "Image 2");
cv::imshow( "Image 2",image);
// display result
cv::namedWindow( "Foreground objects");
cv::imshow( "Foreground objects",foreground);