Learning OpenCV Lecture 4 (Transforming Images with Morphological Operations)
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);