2017-06-12 22:29:15 coming_is_winter 阅读数 9230
  • Java经典算法讲解

    在面试中,算法题目是必须的,通过算法能够看出一个程序员的编程思维,考察对复杂问题的设计与分析能力,对问题的严谨性都能够体现出来。一个算法的好坏,直接影响一个方法调用的性能,进而影响软件的整体性能。算法是学习所有编程语言的基础,在Java的学习过程中首先也会选择以算法起步,本次课程重点讲解Java开发中常用的基本算法。

    30445 人正在学习 去看看 张中强

图像处理之图像分割(一)之活动轮廓模型:Snake算法简单梳理


  Snake算法,应该也可以翻译成蛇形算法,或者是包含曲折前进的意思。具体函数背景原理介绍参考:zouxy09,http://blog.csdn.net/zouxy09/article/details/8712287,图像分割之(五)活动轮廓模型之Snake模型简介,这里还是就几个自己思考的重点拓展一下。

  

 1. 这段中讲v(s)的表示,s∈[0,1],和s是以傅里叶变换形式描述边界的自变量。自己理解不是很明白,查了点资料,整合一下,说法有可能不严谨。首先:




   2.Snake模型到底是怎么变化的?



  弹性力量和弯曲力量合成内部力量,,保持固有的自身形状,外部力量改变其固有的自身形状,在合适的范围内受某种力而变化,这里举个例子:
 


  上面图中这种特大气泡(或想一下气球)的形状是有什么决定的呢?其中的弹性力量是曲线的斜率使其保持大概的圆形(可以理解为材料的张力,失重条件下水会变成水球),虽然现在上图的圆形变形严重,而弯曲力量是曲线斜率的斜率,这里理解为收缩的力量,可以想象一下气球紧绷的力量(这是气球本身材质决定,与外力无关),如果有漏气现象,紧绷的力量会使气泡或气球逐渐收缩,直至最小。外部力量这里的是空气的压力,这在上图中可以看到气泡的不规则凹凸,可以说是大气的压力的力量,形象的说是空气力量梯度的变化,在Snake中表现为像素灰度值梯度的变化。联系再丰富一点,Snake迭代的过程可以想一下真空包装:



  一开始先验知识人为划定Snake框架,相当于还未抽气时的包装袋,抽气开始在内力(包装袋的形状与材质决定)与外力(大气压力)的作用下,不断迭代,最终的轮廓收缩到鸡腿形状,能量最小达到平衡。

  3.泛函与变分
  函数的函数为泛函,我们求解的能量函数公式最终表现为函数的函数,怎样求公式最小值,一般求导找零点。函数的函数求导为变分。

  参考文献:
  [数字图像处理(第二版)].(美)冈萨雷斯.扫描版。

2017-01-23 09:11:04 u014798482 阅读数 1942
  • Java经典算法讲解

    在面试中,算法题目是必须的,通过算法能够看出一个程序员的编程思维,考察对复杂问题的设计与分析能力,对问题的严谨性都能够体现出来。一个算法的好坏,直接影响一个方法调用的性能,进而影响软件的整体性能。算法是学习所有编程语言的基础,在Java的学习过程中首先也会选择以算法起步,本次课程重点讲解Java开发中常用的基本算法。

    30445 人正在学习 去看看 张中强

          图像变形是图像处理中不太常用的算法,因为不应用不广范。但这种算法还是挺有意思的。把矩形图像变形到一个指定的轮廓的区域中,图像的内容适应到目标区域后,会发生比较奇特的扭曲挤压效果,当然,前提是这个轮廓必须是单连通封闭曲线。

哥2015实现的,看几张效果图:

                                                   几种效果集锦



这个算法的结果都不太容易预料,这一点是优点从某种角度上来说也算是缺点。优点是往往能得到比较令人惊喜的效果。

2018-12-11 17:40:06 shanwenkang 阅读数 1276
  • Java经典算法讲解

    在面试中,算法题目是必须的,通过算法能够看出一个程序员的编程思维,考察对复杂问题的设计与分析能力,对问题的严谨性都能够体现出来。一个算法的好坏,直接影响一个方法调用的性能,进而影响软件的整体性能。算法是学习所有编程语言的基础,在Java的学习过程中首先也会选择以算法起步,本次课程重点讲解Java开发中常用的基本算法。

    30445 人正在学习 去看看 张中强

Snakes算法

上一讲我们讲的图像分割算法主要是基于像素的,这一讲主要是基于曲线的。我们希望能得到一个能够包围住图像轮廓的平滑的曲线,snakes算法就是一个很有用的算法。首先我们将曲线的坐标x、y同一用参数s表示,s范围从0-1代表从起点绕曲线一周再回到原点

我们假定初始化的时候这个曲线已经给定,我们定义这个曲线的能量函数,曲线衍进的过程就是让能量函数降低的的过程,能量函数分为外部能量和内部能量

如下是内部能量的定义,内部能量只与曲线的形状有关。能量由两部分组成:一阶导部分与二阶导部分。一阶导代表曲线的“弹性”,也就是曲线是否被拉伸的非常长;二阶导代表曲线曲折程度,也就是曲线是否弯曲不平滑。曲线弹性越低,弯曲越少能量越低

第二部分是曲线的外部能量,代表曲线与边缘的重合程度。重合程度大的时候能量低。

我们有了能量函数以后如何求它的最小值呢?这涉及到变分法,在此就不细讲了。对于数字图像,我们一般用一定数量的点对曲线进行逼近,之后我们可以用梯度下降法来求得函数的最小值

在曲线衍进的时候还需要注意有时我们需要对曲线的点进行重排(再采样)来保证下一次循环(衍进)时我们有更好的效果

当然这种方法也存在问题,我们的活动轮廓无法看到远处的边缘,因此当周围像素值都差不多的时候曲线可能不会继续向内收敛;第二点是当图像中存在噪声时轮廓很可能和噪声点重合。为了解决这个问题我们可以对图像先进行模糊处理,这相当于将原本的边界变得更加模糊了(即扩大的边界的影响范围),这样就能使轮廓有更好的收敛性

 

梯度向量流

采用模糊处理的效果实际上并不是很好,更好的方法是采用梯度向量流(gradient vector flow)。梯度向量流用一个新的矢量场来代替梯度场,从而替换外部能量的部分。

我们用如下方式定义这个新的矢量场v,当梯度值较大的时候,我们另这个矢量场与梯度场的值大致相同;当梯度值较小的时候我们让v的值尽量平滑的变化。这也就意味着在图像边缘处,v的值与梯度场的值相同,当我们逐渐远离边界的时候,v的值不像梯度场一样立马变小,而是有一个逐渐变小的过程。实际上这个新的场v也是扩大了边界的影响范围,使得轮廓能够在更远的地方捕捉到边缘

如果我们将这个场画出来可以得到如下结果。我们可以看出在这种情况下如果初始轮廓在边界外部,那么轮廓将会收缩;如果初始轮廓在边界内部,那么轮廓将会外扩,即朝着向v的模大的地方衍进

Snakes算法也有很多令人头疼的地方,例如追踪每一个点不是一件容易的事,例如snake无法包围多个物体等等

除此以外由于这种算法是基于边缘的,它还会使曲线与我们不需要的边缘重合,例如以下这个例子:曲线并不能很好地包围手掌,而是被背景木桌的条纹所吸引了

 

水平集

为了解决snakes算法不能包围多个物体与洞的问题,我们有水平集(level set)这个算法。与定义一条曲线不同的是,我们定义了一个三维的函数,二维平面上的曲线实际上是这个函数在z=0的横截面

我们用这种方法可以很好地表示包含多个物体或洞的曲线

举个例子,下面右图是我们需要的曲线,左边是这个曲线对应的函数

这种算法还有很多细节我们就不在这讨论了

我们在以上讨论的算法都是基于图像边缘的,当然我们还有基于图像区域的算法,这种算法的能量只与曲线本身的性质以及两个区域块内的像素有关,而与图像的边界无关

比如以下的例子,我们运用基于区域的算法可以让曲线很好地收敛到斑马周围,因为这种情况下曲线外部的颜色基本是绿色,曲线内部基本是黑白的。但如果我们采用基于边缘的算法来计算,那么这个曲线就会收敛到斑马身上的条纹上

 

 

 

 

2015-04-14 12:38:49 Trent1985 阅读数 1966
  • Java经典算法讲解

    在面试中,算法题目是必须的,通过算法能够看出一个程序员的编程思维,考察对复杂问题的设计与分析能力,对问题的严谨性都能够体现出来。一个算法的好坏,直接影响一个方法调用的性能,进而影响软件的整体性能。算法是学习所有编程语言的基础,在Java的学习过程中首先也会选择以算法起步,本次课程重点讲解Java开发中常用的基本算法。

    30445 人正在学习 去看看 张中强


[函数名称]

  二值图像轮廓提取         ContourExtraction(WriteableBitmap src) 

[算法说明]

  二值图像的轮廓提取对于图像识别,图像分割有着重要意义。该算法的核心就是将图像目标的内部点消除。所谓内部点,我们要根据当前像素点的邻域来进行判断,假设邻域窗口为3*3窗口,如果当前像素P(x,y)的八个邻域像素满足如下条件,则该点即内部点:

  1P(x,y)为目标像素,假设目标像素为黑色0,背景像素为白色255,那么P(x,y)=0;

  2P(x,y)的八个邻域像素均为目标像素0

  我们把满足条件的内部点删除,换为背景点255,即可得到图像轮廓。

  内部点如下图所示:

[函数代码]

        /// <summary>
        /// Contour Extraction process.
        /// </summary>
        /// <param name="src">The source image.</param>
        /// <returns></returns>
        public static WriteableBitmap ContourExtraction(WriteableBitmap src)
        {
            if (src != null)
            {
                int w = src.PixelWidth;
                int h = src.PixelHeight;
                WriteableBitmap srcImage = new WriteableBitmap(w, h);
                byte[] temp = src.PixelBuffer.ToArray();
                byte[] tempMask = (byte[])temp.Clone();
                for (int j = 1; j < h-1; j++)
                {
                    for (int i = 4; i < w*4-4; i+=4)
                    {
                        if ((tempMask[i + j * w * 4] == 0) && (tempMask[i - 4 + j * w * 4] == 0) && (tempMask[i + 4 + j * w * 4] == 0) && (tempMask[i - 4 + (j - 1) * w * 4] == 0)
                            && (tempMask[i - 4 + (j + 1) * w * 4] == 0) && (tempMask[i + (j - 1) * w * 4] == 0) && (tempMask[i + (j + 1) * w * 4] == 0)
                            && (tempMask[i + 4 + (j - 1) * w * 4] == 0) && (tempMask[i + 4 + (j + 1) * w * 4] == 0))
                        {
                            temp[i + j * w * 4] = (byte)255;
                            temp[i + 1 + j * w * 4] = (byte)255;
                            temp[i + 2 + j * w * 4] = (byte)255;
                        }
                    }
                }
                Stream sTemp = srcImage.PixelBuffer.AsStream();
                sTemp.Seek(0, SeekOrigin.Begin);
                sTemp.Write(temp, 0, w * 4 * h);
                return srcImage;
            }
            else
            {
                return null;
            }
        }

2019-09-02 16:41:09 weixin_38419133 阅读数 292
  • Java经典算法讲解

    在面试中,算法题目是必须的,通过算法能够看出一个程序员的编程思维,考察对复杂问题的设计与分析能力,对问题的严谨性都能够体现出来。一个算法的好坏,直接影响一个方法调用的性能,进而影响软件的整体性能。算法是学习所有编程语言的基础,在Java的学习过程中首先也会选择以算法起步,本次课程重点讲解Java开发中常用的基本算法。

    30445 人正在学习 去看看 张中强

最近在实验室里遇到了一个问题,就是在有一张轮廓二值图的情况下,如何才能将轮廓进行细化,得到轮廓的骨架。

效果如图:

可以看到,右边图中的数字变瘦了,这就是细化算法的作用

下面我们来讲一下,Thining-Algorithm的算法原理。

一、八领域

我们先来介绍一下,八领域这一个概念

p9 p2 p3
p8 p1 p4
p7 p6 p5

 

如图,八领域是指包围了中心P1像素的八个像素点。在很多图像处理算法中,八领域的这个概念都极为常见,应用十分广泛。

二、算法原理

首先,我们来看一下以下的几个类型点:

          端点                                  孤立点                                内部点                                   内部点

                                         

 

可以看到,我们是通过八领域中的值,来确定这一点是内部点还是端点和孤立点,从而确定是否保留该点的像素值。

第一步:遍历考察所有的非零点,看是否满足一下四个条件:

   a. 2<= p2+p3+p4+p5+p6+p7+p8+p9<=6

   b. p2->p9的排列顺序中,01模式的数量为1,比如下面的图中,有p2p3 => 01, p6p7=>01,所以该像素01模式的数量为2。

 

之所以要01模式数量为1,是要保证删除当前像素点后的连通性。

 

    c. p2*p4*p6 = 0

    d. p4*p6*p8 = 0

将满足以上四个条件的点删除(像素值置为0)

第二步:还是通过四个条件来判断点的去留

a. 2<= p2+p3+p4+p5+p6+p7+p8+p9<=6

b. p2->p9的排列顺序中,01模式的数量(这里假设二值图非零值为1)为1。

c. p2*p4*p8 = 0

d. p2*p6*p8 = 0

可以看到其实在本质上,两大步骤中四个条件并没有很大的区别,只是在c、d两个条件上变成不同的方向。

合并c、d两个条件就可以看到,只需要p2、p4、p6、p8四个像素值有一个为零,中心像素p1就该删除。

好了,细化算法的原理就是这么多,其实还是比较简单的,但是有一点需要注意的是,我们在C++上编程,应该注意,其实不用将图片进行归一化,其实只需要简答的将所有的值乘以255就可以了,这样更加方便简单。

下面是源码:

#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

using namespace std;
using namespace cv;
int main()
{
	//读入图像
	cv::Mat img = cv::imread("in.png");
	cv::Mat gray;
	cv::Mat Binary;
	//进行二值化
	cv::cvtColor(img, gray, CV_BGR2GRAY);
	cv::inRange(gray, 200, 255, Binary);
	cv::Mat CopyImg;
	int rows = Binary.rows;
	int cols = Binary.cols;


	std::vector<cv::Point2l> PointSaver1;
	//开始第一轮判断
	for (int i{ 1 };i<rows-1;i++)
	{
		uchar* high = Binary.ptr<uchar>(i-1);
		uchar* mid = Binary.ptr<uchar>(i);
		uchar* low = Binary.ptr<uchar>(i+1);

		for (int j{1};j<cols-1;j++)
		{
			int a1 = mid[j];
			int a2 = high[j];
			int a3 = high[j+1];
			int a4 = mid[j+1];
			int a5 = low[j+1];
			int a6 = low[j];
			int a7 = low[j-1];
			int a8 = mid[j-1];
			int a9 = high[j-1];
			int a[9] = { a1,a2,a3,a4,a5,a6,a7,a8,a9 };

			bool req1 = true;
			bool req2 = true;
			bool req3 = true;
			bool req4 = true;

			//条件1 八领域的和
			if (a[0 == 255])
			{
				int req1_sum{ 0 };
				req1_sum = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8];

				//std::cout << "req1_sum: " << req1_sum << std::endl;
				if (req1_sum >= 510 && req1_sum <= 1530)
				{
					//std::cout << "66666" << std::endl;
					req1 = true;
				}
				else req1 = false;

				//条件2  01的模式
				int req2_sum{ 0 };
				//	std::cout << "req2_sum: " << req2_sum << std::endl;
				for (int k = 2; k < 9; k++)
				{
					if (a[k] == 255 && a[k - 1] == 0)
					{
						req2_sum += 1;
					}
				}
				if (req2_sum == 1) req2 = true;
				else req2 = false;
				//条件三
				int req3_sum = a[1] * a[3] * a[5];
				//std::cout << "req3_sum: " << req3_sum << std::endl;

				if (req3_sum == 0) req3 = true;
				else req3 = false;
				//条件四
				int req4_sum = a[3] * a[5] * a[7];
				//	std::cout << "req3_sum: " << req3_sum << std::endl;

				if (req4_sum == 0) req4 = true;
				else req4 = false;
				if (req1 && req2 && req3 && req4)
				{
					PointSaver1.push_back(Point2l(i, j));
				}
			}
		}
	}
	for (int l=0;l<PointSaver1.size();l++)
	{
		uchar* ptr = Binary.ptr<uchar>(PointSaver1[l].x);
		std::cout << "PointSaver1[l].x: " << PointSaver1[l].x << "PointSaver1[l].y: " << PointSaver1[l].y << std::endl;
		ptr[PointSaver1[l].y] = 0;
	}

	//第二轮判断
	std::vector<cv::Point2l> PointSaver2;
	for (int i{ 1 }; i < rows - 1; i++)
	{
		uchar* high = Binary.ptr<uchar>(i - 1);
		uchar* mid = Binary.ptr<uchar>(i);
		uchar* low = Binary.ptr<uchar>(i + 1);

		for (int j{ 1 }; j < cols - 1; j++)
		{
			int a1 = mid[j];
			int a2 = high[j];
			int a3 = high[j + 1];
			int a4 = mid[j + 1];
			int a5 = low[j + 1];
			int a6 = low[j];
			int a7 = low[j - 1];
			int a8 = mid[j - 1];
			int a9 = high[j - 1];
			int a[9] = { a1,a2,a3,a4,a5,a6,a7,a8,a9 };

			bool req1 = true;
			bool req2 = true;
			bool req3 = true;
			bool req4 = true;

			//条件1 八领域的和
			if (a[0 == 255])
			{
				int req1_sum{ 0 };
				req1_sum = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8];

				//std::cout << "req1_sum: " << req1_sum << std::endl;
				if (req1_sum >= 510 && req1_sum <= 1530)
				{
					//std::cout << "66666" << std::endl;
					req1 = true;
				}
				else req1 = false;

				//条件2  01的模式
				int req2_sum{ 0 };
				//	std::cout << "req2_sum: " << req2_sum << std::endl;
				for (int k = 2; k < 9; k++)
				{
					if (a[k] == 255 && a[k - 1] == 0)
					{
						req2_sum += 1;
					}
				}
				if (req2_sum == 1) req2 = true;
				else req2 = false;
				//条件三
				int req3_sum = a[1] * a[3] * a[5];
				//std::cout << "req3_sum: " << req3_sum << std::endl;

				if (req3_sum == 0) req3 = true;
				else req3 = false;
				//条件四
				int req4_sum = a[1] * a[3] * a[7];
				//	std::cout << "req3_sum: " << req3_sum << std::endl;

				if (req4_sum == 0) req4 = true;
				else req4 = false;
				if (req1 && req2 && req3 && req4)
				{
					PointSaver2.push_back(Point2l(i, j));
				}
			}
		}
	}
	for (int l = 0; l < PointSaver2.size(); l++)
	{
		uchar* ptr = Binary.ptr<uchar>(PointSaver2[l].x);
		std::cout << "PointSaver[l].x: " << PointSaver2[l].x << "PointSaver[l].y: " << PointSaver2[l].y << std::endl;
		ptr[PointSaver2[l].y] = 0;
	}

	cv::imshow("SrcImg", Binary);
	std::cout << "saver_size: " << PointSaver2.size() << std::endl;
	cv::waitKey(0);
}

我的github地址:https://github.com/Dylanin1999/ISP_Algorithm

感谢各位看官。有啥疑惑或看到本人有啥错漏的话,欢迎留言交流!

 

 

 

图像处理基本知识

阅读数 12458

06.OpenCV-图像处理

阅读数 41

没有更多推荐了,返回首页