图像分割之水平集(Level Set)分割

几何活动轮廓模型——水平集分割:Active Contours Without Edges

水平集方法

        水平集是跟踪轮廓和表面运动的一种数字化方法,它不直接对轮廓进行操作,而是将轮廓设置成一个高维函数的零水平集。这个高维函数叫做水平集函数。然后对该水平集函数进行微分,通过从输出中提取零水平集来得到运动的轮廓。使用水平集的主要优点是可以对任意复杂的结构进行模式化和拓扑变换。
        水平集(Level Set)方法是由Sethian和Osher于1988年提出。简单来说,水平集方法把低维的一些计算上升到更高一维,把N维的描述看成是N+1维的一个水平。比如,一个二维平面的圆,如x2+y2=1,可以看成三维中二元函数f(x,y) = x2+y2的1的水平集。如下图
在这里插入图片描述
专业描述:水平集方法将平面闭合曲线隐含地表达为连续函数曲面

几何活动轮廓模型

        这里具体介绍2001年的文献《Active Contours Without Edges》,即Chan-Vese模型。作者提出了基于Mumford–Shah分割技术和水平集方法的活动轮廓模型,该模型不依赖边缘函数,即图像梯度。能量函数有fitting项和一些正则项组成。
在这里插入图片描述
作者把上面关于曲线C的能量函数写成关于水平集函数ϕ的能量函数,然后求解。
在这里插入图片描述
其中Heaviside函数和Delata函数,如下
在这里插入图片描述
最后得到水平集函数的迭代公式:
在这里插入图片描述
在这里插入图片描述

示例演示

        下面基于OpenCV实现该算法。完整工程代码
头文件

/**********************************************************************

Copyright (c) Mr.Bin. All rights reserved.
For more information visit: http://blog.csdn.net/webzhuce

**********************************************************************/
/**
* @brief   This is a filter that implements  Tony F. Chan. Active 
*          Contours Without Edges[J].2001
*/
#pragma once
#include "opencv2/opencv.hpp"

class LevelSet
{
public:
	LevelSet(const cv::Mat &src);
	//initialize level set
	void initializePhi(cv::Point2f center, float radius);
	void evolving();	

private:
	int iterationnum_ = 100;
	float lambda1_ = 1.0f;	 //weight for c1 fitting term
	float lambda2_ = 1.0f;   //weight for c2 fitting term
	float mu_ = 0.1 * 255 * 255;	//μ: weight for length term
	float nu_ = 0.0;  //ν: weight for area term, default value 0 
	float timestep_ = 0.1; //time step: δt
	//ε: parameter for computing smooth Heaviside and dirac function
	float epsilon_ = 1.0;	
	float c1_;	//average(u0) inside level set
	float c2_;	//average(u0) outside level set
	cv::Mat phi_;		//level set: φ
	cv::Mat dirac_;		//dirac level set: δ(φ)
	cv::Mat heaviside_;	//heaviside:Н(φ)
	cv::Mat curv_;		
	cv::Mat src_;
	cv::Mat image_; //for showing
	
	void gradient(const cv::Mat &src, cv::Mat &gradx, cv::Mat &grady);
	//Dirac function
	void dirac();
	//Heaviside function
	void heaviside();
	void calculateCurvature();
	//calculate c1 and c2
	void calculatC();
	//show evolving
	void showEvolving();
};

源文件

/**********************************************************************

Copyright (c) Mr.Bin. All rights reserved.
For more information visit: http://blog.csdn.net/webzhuce

**********************************************************************/
#include "levelset.h"

using namespace cv;
using namespace std;

LevelSet::LevelSet(const cv::Mat &src)
{
	if (src.channels() == 3)
	{
		cv::cvtColor(src, src_, CV_BGR2GRAY);
		src.copyTo(image_);
	}
	else
	{
		src.copyTo(src_);
		cv::cvtColor(src, image_, CV_GRAY2BGR);
	}
	src_.convertTo(src_, CV_32FC1);
	phi_ = Mat::zeros(src_.size(), CV_32FC1);
	dirac_ = Mat::zeros(src_.size(), CV_32FC1);
	heaviside_ = Mat::zeros(src_.size(), CV_32FC1);
}

void LevelSet::initializePhi(cv::Point2f center, float radius)
{
	const float c = 2.0f;
	float value = 0.0;
	for (int i = 0; i < src_.rows; i++)
	{
		for (int j = 0; j < src_.cols; j++)
		{
			value = -sqrt(pow((j - center.x), 2) + pow((i - center.y), 2)) + radius;
			if (abs(value) < 0.1) //
				phi_.at<float>(i, j) = 0;
			else if (value >=0.1) //inside
				phi_.at<float>(i, j) = c;
			else
				phi_.at<float>(i, j) = -c;
		}
	}
}

void LevelSet::gradient(const cv::Mat &src, cv::Mat &gradx, cv::Mat &grady)
{
	if (src.rows < 2 || src.cols < 2)
		return;
	src.copyTo(gradx);
	src.copyTo(grady);
	for (int i = 0; i < src.rows; i++)
	{
		for (int j = 0; j < src.cols; j++)
		{
			if (j == 0)
				gradx.at<float>(i, j) = src.at<float>(i, j + 1) - src.at<float>(i, j);
			else if (j == src.cols - 1)
				gradx.at<float>(i, j) = src.at<float>(i, j) - src.at<float>(i, j - 1);
			else
				gradx.at<float>(i, j) = (src.at<float>(i, j + 1) - src.at<float>(i, j - 1)) / 2.0;
		}
	}
	for (int j = 0; j < src.cols; j++)
	{
		for (int i = 0; i < src.rows; i++)
		{
			if (i == 0)
				grady.at<float>(i, j) = src.at<float>(i + 1, j) - src.at<float>(i, j);
			else if (i == src.rows - 1)
				grady.at<float>(i, j) = src.at<float>(i, j) - src.at<float>(i - 1, j);
			else
				grady.at<float>(i, j) = (src.at<float>(i + 1, j) - src.at<float>(i - 1, j)) / 2.0;
		}
	}

}

void LevelSet::dirac()
{
	const float k1 = epsilon_ / CV_PI;
	const float k2 = epsilon_*epsilon_;
	for (int i = 0; i < src_.rows; i++)
	{
		for (int j = 0; j < src_.cols; j++)
		{
			dirac_.at<float>(i, j) = k1 / (k2 + pow(phi_.at<float>(i, j),2));
		}
	}
}

void LevelSet::heaviside()
{
	const float k3 = 2 / CV_PI;
	for (int i = 0; i < src_.rows; i++)
	{
		for (int j = 0; j < src_.cols; j++)
		{
			heaviside_.at<float>(i, j) = 0.5 * (1 + k3 * atan(phi_.at<float>(i, j) / epsilon_));
		}
	}
}

void LevelSet::calculateCurvature()
{
	Mat dx, dy;
	gradient(src_, dx, dy);
	Mat norm;
	pow(dx.mul(dx) + dy.mul(dy), 0.5, norm);
	Mat dxx, dxy, dyx, dyy;
	gradient(dx / norm, dxx, dxy);
	gradient(dy / norm, dyx, dyy);
	curv_ = dxx + dyy;
}

void LevelSet::calculatC()
{
	c1_ = 0.0f;
	c2_ = 0.0f;
	float sum1 = 0.0f;
	float h1 = 0.0f;
	float sum2 = 0.0f;
	float h2 = 0.0f;
	int outsidenum = 0;
	float value = 0.0f;
	float h = 0.0f;
	for (int i = 0; i < src_.rows; i++)
	{
		for (int j = 0; j < src_.cols; j++)
		{
			value = src_.at<float>(i, j);
			h = heaviside_.at<float>(i, j);
			h1 += h;
			sum1 = h *  value;
			h2 += (1 - h);
			sum2 += (1 - h) * value;
		}
	}
	c1_ = sum1 / (h1 + 1e-10);
	c2_ = sum2 / (h2 + 1e-10);
}

void LevelSet::showEvolving()
{
	Mat image = image_.clone();
	Mat mask = phi_ >= 0;
	cv::dilate(mask, mask, Mat(), Point(-1, -1), 3);
	cv::erode(mask, mask, Mat(), Point(-1, -1), 3);
	vector<vector<Point> > contours;
	findContours(mask, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
	drawContours(image, contours, -1, CV_RGB(0, 255,0), 2);
	imshow("Evolving", image);
	waitKey(0);
}

void LevelSet::evolving()
{
	showEvolving();
	//iteration
	for (int i = 0; i < iterationnum_; i++)
	{
		heaviside();
		dirac();
		calculateCurvature();
		calculatC();

		//update phi
		for (int i = 0; i < src_.rows; i++)
		{
			for (int j = 0; j < src_.cols; j++)
			{
				float curv = curv_.at<float>(i, j);
				float dirac = dirac_.at<float>(i, j);
				float u0 = src_.at<float>(i, j);;

				float lengthTerm = mu_* dirac * curv;
				float areamterm = nu_ * dirac;
				float fittingterm = dirac * (-lambda1_ * pow(u0 - c1_, 2) + lambda2_ * pow(u0 - c2_, 2));
				float term = lengthTerm + areamterm + fittingterm;
				phi_.at<float>(i, j) = phi_.at<float>(i, j) + timestep_ * term;
			}
		}
		//just for showing
		showEvolving();
	}
}

运行结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

参考资料

  • 20
    点赞
  • 162
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
水平集图像分割是一种基于水平集方法的图像分割技术,可以用 Matlab 实现。 步骤如下: 1. 读取图像:使用 Matlab 自带的 imread 函数读取需要分割的图像。 2. 预处理:对图像进行预处理,例如去噪、平滑等。 3. 初始化:初始化水平集函数、速度函数、阈值等参数。 4. 迭代:使用迭代方法不断更新水平集函数,直到达到停止条件。 5. 后处理:对分割结果进行后处理,例如去除小区域、边缘平滑等。 6. 可视化:使用 Matlab 自带的 imshow 函数将分割结果可视化。 下面是一个简单的 Matlab 代码实现: ```matlab % 读取图像 img = imread('example.png'); % 预处理 img = im2double(img); img = imgaussfilt(img, 2); % 初始化参数 phi0 = ones(size(img)); phi0(50:150, 50:150) = -1; v = 1; mu = 0.1; nu = 0.001; epsilon = 1; % 迭代更新水平集函数 phi = phi0; for i = 1:100 phi = levelset(phi, img, v, mu, nu, epsilon); end % 后处理 bw = phi > 0; bw = bwareaopen(bw, 50); bw = imfill(bw, 'holes'); bw = edge(bw); % 可视化 figure; imshow(img); hold on; visboundaries(bw, 'Color', 'r'); % 水平集函数更新函数 function phi = levelset(phi, img, v, mu, nu, epsilon) [phi_x, phi_y] = gradient(phi); s = sqrt(phi_x.^2 + phi_y.^2); curvature = divergence(phi_x./s, phi_y./s); dirac = @(x, epsilon) (epsilon/pi) ./ (epsilon^2 + x.^2); dirac_phi = dirac(phi, epsilon); Heaviside = @(x) 0.5 * (1 + (2/pi) * atan(x./epsilon)); H_phi = Heaviside(phi); H_phi_c = 1 - H_phi; img_c = 1 - img; F = (v - img).^2 - (v - img_c).^2; dphi = dirac_phi .* (mu * curvature - nu + F.*s) .* H_phi_c; phi = phi + dphi; end ``` 在这个例子中,我们读取了名为 `example.png` 的图像,对其进行了高斯平滑,并初始化了水平集函数、速度函数以及其他参数。然后使用迭代方法不断更新水平集函数,最终得到了分割结果。最后对分割结果进行了后处理,并可视化了结果。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值