实验内容
1)实现基于阈值法对图像分割
2)实现区域生长方法对图像分割
3)实现任一种不同于以上的分割算法实现图像分割
运行效果
图像灰度读入后:
Opencv提供了多种阈值宏,可以方便地调用:
4.1 THRESH_BINARY
最常见的作法,设置一个阈值,大于的时候取最大值(maxval是threshold的参数),否则取0。
4.2 THRESH_BINARY_INV
与THRESH_BINARY相反,大于阈值取0,否则取最大值,maxval对于THRESH_BINARY和THRESH_BINARY_INV有意义。
4.3 THRESH_TRUNC
截断型,大于阈值的统一将为阈值,其余不变。
4.4 THRESH_TOZERO
过滤型,大于阈值的保持不变,其余设置为0。
4.5 THRESH_TOZERO_INV
大于阈值0,其他不变。
4.6 THRESH_MASK
这个不知道是什么,也没有解释
4.7 THRESH_OTSU
大津阈值
4.8 THRESH_TRIANGLE
矩形阈值
分别使用不同的threshold的mode与不同的阈值组合进行图像分割:
下面是基于区域生长算法对应地分割的效果图:
首先输入源图像:
进行背景转换:
对图像进行锐化处理:
对锐化后的图像进行二值化:
应用距离变换算法:
使用WaterShed算法得到的极值点的区域
最终得到的分割结果:
对于区域生长的方法:
可以看到生成的随机数的位置和最终生长出的区域有间断的情况,因为只用了一个随机点,效果不是很好,所以接着做了改进,修改为两个随机点,图片的左边和右边各一个,修改后的效果:
可以看到,此时使用两个随机点,效果比较令人满意。
项目源码
DigitalImageExp7.cpp
// DigitalImageExp7.cpp
#include "thresholdBasedSegmentation.h"
#include "otherSegmentationMethods.h"
#include "regionGrowingBasedSegmentation.h"
int main()
{
String imageName("../media/cat.jpg"); // by default
Mat src = imread(samples::findFile(imageName), IMREAD_COLOR);
if (src.empty()) {
cout << "OPEN ERROR" << endl;
exit(-1);
}
// Use the threshold base segmentation method
thresholdBasedSegmentation(src);
// Call the distance transformation and Watershed method
otherSegmentationTransformation(src);
// Use the region growing segmentation method
regionGrowingBasedSegmentation(src, 50);
return 0;
}
regionGrowingBasedSegmtation.h
#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>
#include <stack>
using namespace cv;
using namespace std;
void regionGrowingBasedSegmentation(Mat src, int threshold);
regionGrowingBasedSegmtation.cpp
#include "regionGrowingBasedSegmentation.h"
#define DEBUG
static int rInt(int a, int b) {
int temp = int(double(rand()) / RAND_MAX * (b - a) + a);
return temp;
}
// th: threshold: determne when the connection of two neighbor pixel
void regionGrowingBasedSegmentation(Mat para_src, int th)
{
String imageName("../media/cat.jpg"); // by default
Mat src_pre, src;
if (para_src.empty())
{
// Load the default image
src_pre = imread(samples::findFile(imageName), IMREAD_GRAYSCALE);
}
else {
src_pre = para_src.clone();
}
// Convert the image to Gray
cvtColor(src_pre, src, COLOR_BGR2GRAY);
int randX1 = rInt(src.rows/2, src.rows - 1);
int randY1 = rInt(src.cols/2, src.cols - 1);
int randX2 = rInt(10, src.rows / 2);
int randY2 = rInt(10, src.cols / 2);
#ifdef DEBUG
cout << "src.rows = " << src.rows <<
"\nsrc.cols = " << src.cols << endl;
cout << "randX1 = " << randX1 << endl
<< "randY1 = " << randY1 << endl;
cout << "randX2 = " << randX2 << endl
<< "randY2 = " << randY2 << endl;
#endif // DEBUG
Point2i pt;
Point2i ptGrowing; // The point to grow
int nGrowLable = 0; // Check if it has been growed before
int nSrcValue = 0; // The growing point value
int nCurValue = 0; // The current pixel's value
Mat matDst = Mat::zeros(src.size(), CV_8UC1); // Create a new region filled with black
// Set the growing direction
int DIR[8][2] = { {-1,-1}, {0,-1}, {1,-1}, {1,0}, {1,1}, {0,1}, {-1,1}, {-1,0} };
stack<Point2i> vcGrowPt; // Create a gorwing point stack
for (int times = 0 ; times < 2 ; ++times) {
if (times == 0) {
pt = Point2i(randX1, randY1);
}
else {
pt = Point2i(randX2, randY2);
}
vcGrowPt.push(pt); // Push the growing point into the stack
matDst.at<uchar>(pt.y, pt.x) = 255; // Mark the growing point
nSrcValue = src.at<uchar>(pt.y, pt.x); // Update the growing point's gray scale value
while (!vcGrowPt.empty()) // Determine when to end the process
{
pt = vcGrowPt.top(); // Get one growing point out
vcGrowPt.pop();
// Try to connect the 8 neighbor pixels
for (int i = 0; i < 8; ++i)
{
// Set the next point to grow
ptGrowing.x = pt.x + DIR[i][0];
ptGrowing.y = pt.y + DIR[i][1];
// Check the boundaries
if (ptGrowing.x < 0 || ptGrowing.y < 0 ||
ptGrowing.x >(src.cols - 1) || (ptGrowing.y > src.rows - 1))
continue;
// Get the grayScale value of the current pixel
nGrowLable = matDst.at<uchar>(ptGrowing.y, ptGrowing.x);
// The pixels has not been accessed before
if (nGrowLable == 0)
{
nCurValue = src.at<uchar>(ptGrowing.y, ptGrowing.x);
if (abs(nSrcValue - nCurValue) < th) // If the pixel grayscale value is close to threshold enough
{
matDst.at<uchar>(ptGrowing.y, ptGrowing.x) = 255; // set the pixel's grayscale to 255
vcGrowPt.push(ptGrowing); // Continue the BFS process
}
}
}
}
}
imshow("GROWING RESULT", matDst);
waitKey();
}
otherSegmentationMethods.h
#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int otherSegmentationTransformation(const Mat & para_src);
otherSegmentationMethods.cpp
#include "otherSegmentationMethods.h"
int otherSegmentationTransformation(const Mat & para_src)
{
String imageName("../media/cat.jpg"); // by default
Mat src;
if (para_src.empty())
{
// Load the default image
src = imread(samples::findFile(imageName), IMREAD_COLOR);
}
else {
src = para_src.clone();
}
// Show source image
imshow("Source Image", src);
// Change the background from white to black, since that will help later to extract
// better results during the use of Distance Transform
for (int i = 0; i < src.rows; i++) {
for (int j = 0; j < src.cols; j++) {
if (src.at<Vec3b>(i, j) == Vec3b(255, 255, 255))
{
src.at<Vec3b>(i, j)[0] = 0;
src.at<Vec3b>(i, j)[1] = 0;
src.at<Vec3b>(i, j)[2] = 0;
}
}
}
// Show output image
imshow("Black Background Image", src);
// Create a kernel that we will use to sharpen our image
Mat kernel = (Mat_<float>(3, 3) <<
1, 1, 1,
1, -8, 1,
1, 1, 1); // an approximation of second derivative, a quite strong kernel
// do the laplacian filtering as it is
// well, we need to convert everything in something more deeper then CV_8U
// because the kernel has some negative values,
// and we can expect in general to have a Laplacian image with negative values
// BUT a 8bits unsigned int (the one we are working with) can contain values from 0 to 255
// so the possible negative number will be truncated
Mat imgLaplacian;
filter2D(src, imgLaplacian, CV_32F, kernel);
Mat sharp;
src.convertTo(sharp, CV_32F);
Mat imgResult = sharp - imgLaplacian;
// convert back to 8bits gray scale
imgResult.convertTo(imgResult, CV_8UC3);
imgLaplacian.convertTo(imgLaplacian, CV_8UC3);
// imshow( "Laplace Filtered Image", imgLaplacian );
imshow("New Sharped Image", imgResult);
// Create binary image from source image
Mat bw;
cvtColor(imgResult, bw, COLOR_BGR2GRAY);
threshold(bw, bw, 40, 255, THRESH_BINARY | THRESH_OTSU);
imshow("Binary Image", bw);
// Perform the distance transform algorithm
Mat dist;
distanceTransform(bw, dist, DIST_L2, 3);
// Normalize the distance image for range = {0.0, 1.0}
// so we can visualize and threshold it
normalize(dist, dist, 0, 1.0, NORM_MINMAX);
imshow("Distance Transform Image", dist);
// Threshold to obtain the peaks
// This will be the markers for the foreground objects
threshold(dist, dist, 0.4, 1.0, THRESH_BINARY);
// Dilate a bit the dist image
Mat kernel1 = Mat::ones(3, 3, CV_8U);
dilate(dist, dist, kernel1);
imshow("Peaks", dist);
// Create the CV_8U version of the distance image
// It is needed for findContours()
Mat dist_8u;
dist.convertTo(dist_8u, CV_8U);
// Find total markers
vector<vector<Point> > contours;
findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
// Create the marker image for the watershed algorithm
Mat markers = Mat::zeros(dist.size(), CV_32S);
// Draw the foreground markers
for (size_t i = 0; i < contours.size(); i++)
{
drawContours(markers, contours, static_cast<int>(i), Scalar(static_cast<int>(i) + 1), -1);
}
// Draw the background marker
circle(markers, Point(5, 5), 3, Scalar(255), -1);
// Something went wrong with the following line code
// So I just made it a line of comment
// imshow("Markers", markers * 10000);
// Perform the watershed algorithm
watershed(imgResult, markers);
Mat mark;
markers.convertTo(mark, CV_8U);
bitwise_not(mark, mark);
// imshow("Markers_v2", mark); // uncomment this if you want to see how the mark
// image looks like at that point
// Generate random colors
vector<Vec3b> colors;
for (size_t i = 0; i < contours.size(); i++)
{
int b = theRNG().uniform(0, 256);
int g = theRNG().uniform(0, 256);
int r = theRNG().uniform(0, 256);
colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
}
// Create the result image
Mat dst = Mat::zeros(markers.size(), CV_8UC3);
// Fill labeled objects with random colors
for (int i = 0; i < markers.rows; i++)
{
for (int j = 0; j < markers.cols; j++)
{
int index = markers.at<int>(i, j);
if (index > 0 && index <= static_cast<int>(contours.size()))
{
dst.at<Vec3b>(i, j) = colors[index - 1];
}
}
}
// Visualize the final image
imshow("Final Result", dst);
waitKey();
return 0;
}
thresholdBasedSegmentation.cpp
// thresholdBasedSegmentation.cpp
#include "thresholdBasedSegmentation.h"
static int threshold_value = 0;
static int threshold_type = 3;
static int const max_value = 255;
static int const max_type = 4;
static int const max_binary_value = 255;
static Mat src, src_gray, dst;
static const char* window_name = "Threshold Demo";
static const char* trackbar_type = "Type: \n 0: Binary \n 1: Binary Inverted \n 2: Truncate \n 3: To Zero \n 4: To Zero Inverted";
static const char* trackbar_value = "Value";
// Define the callback function
static void Threshold_Demo(int, void*)
{
/* 0: Binary
1: Binary Inverted
2: Threshold Truncated
3: Threshold to Zero
4: Threshold to Zero Inverted
*/
threshold(src_gray, dst, threshold_value, max_binary_value, threshold_type);
imshow(window_name, dst);
}
int thresholdBasedSegmentation(const Mat & para_src)
{
String imageName("../media/cat.jpg"); // by default
Mat src;
if (para_src.empty())
{
// Load the default image
src = imread(samples::findFile(imageName), IMREAD_COLOR);
}
else {
src = para_src.clone();
}
// Convert the image to Gray
cvtColor(src, src_gray, COLOR_BGR2GRAY);
// Create a window to display results
namedWindow(window_name, WINDOW_AUTOSIZE);
// Create a Trackbar to choose type of Threshold
createTrackbar(trackbar_type,
window_name, &threshold_type,
max_type, Threshold_Demo);
// Create a Trackbar to choose Threshold value
createTrackbar(trackbar_value,
window_name, &threshold_value,
max_value, Threshold_Demo);
// Call the callback function to initialize
Threshold_Demo(0, 0);
waitKey();
return 0;
}
thresholbasedSegmentation.h
#pragma once
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int thresholdBasedSegmentation( const Mat & para_src );