Computer Vision: Algorithms and ApplicationsのImage processing

实在是太喜欢Richard Szeliski的这本书了。每一章节(after chapter3)都详述了该研究方向比較新的成果。还有很多很多的reference,假设你感兴趣。全然能够看那些參考论文

Point operators(点算子)

点运算是最简单的一类图像处理运算。如简单的对照度变换,亮度变换

Pixel transform(像素变换)

g(x) = af (x) +b    a和b有时被觉得用来控制对照度和亮度,在我的opencv栏目有个样例是简单的对照度和亮度变换,用的就是这个公式

g(x) = a(x)(x) + b(x)    a,b不一定是常数。能够是空间上的函数

g(x) = (1 α)f0(x) + αf1(x)   α from0能够实现两幅图像的淡入淡出

在OpenCV里有addWeighted( src1, alpha, src2, beta, 0.0, dst);这个函数,就是实现这个式子的

g(x) = [f(x)]1    这是伽马校正属于幂变换。通经常使用于图像预处理阶段。对于大多数数字摄像机来说γ2.

除了伽马校正。幂变换在控制对照度也非常实用,能够取不同的γ试一试

除了上面这些,我知道的还有:

g(x) = L -1 -f(x)  灰度级属于[0,L-1]   这是图像反转  可用于增强嵌入与图像暗色区域的白色或灰色细节

g(x) = clog(1+f(x))  对数变换


Color transform(彩色变换)

好像没有讲什么 = =


Compositing and matting(合成与抠图)

就是把一张图片里的东西(比方人)挖下来。然后合成到还有一张图片中
从一个场景中裁剪出前景物体叫抠图,将物体插入到还有一幅图片中叫合成

C=(1α)B+αF.  (覆盖算子)

这个算子通过(1α)因子减弱了背景图像的影响,增加了相应于前景图像的彩色值(和不透明度)
作者说,在这本书中。关心另外一个经常使用的算子(在Ex2中,等我做了再更新这里)

Histogram equalization(直方图均衡化)

我对这个非常熟悉。由于之前的图像处理课写过,还看过Pizer那有名的论文也用Java实现了CLAHE
直方图均衡化的思想就是这种。假设我有灰度级255的图像,可是都是属于[100,110]的灰度,图像对照度就非常低。我应该尽可能拉到整个[0,255]
以下是直方图均衡化的代码。有个累积函数的概念,事实上非常easy。

我先计算出每一个灰度级g(0),g(1)......g(255)点的个数,sum为图像width*height
那么累计函数c(0) = g(0)/sum
c(1) = (g(0)+g(1))/sum
......
c(255) = 1
public int[][] Histogram_Equalization(int[][] oldmat)
	{
		int[][] new_mat = new int[height][width];
		int[] tmp = new int[256];
		for(int i = 0;i < width;i++){
			for(int j = 0;j < height;j++){
				//System.out.println(oldmat[j][i]);
				int index = oldmat[j][i];
				tmp[index]++;
			}
		}
		
		float[] C = new float[256];
		int total = width*height;
		//计算累积函数
		for(int i = 0;i < 256 ; i++){
			if(i == 0)
				C[i] = 1.0f * tmp[i] / total;
			else
				C[i] = C[i-1] + 1.0f * tmp[i] / total;
		}
		
		for(int i = 0;i < width;i++){
			for(int j = 0;j < height;j++){
				new_mat[j][i] = (int)(C[oldmat[j][i]] * 255);
				new_mat[j][i] = new_mat[j][i] + (new_mat[j][i] << 8) + (new_mat[j][i] << 16);
				//System.out.println(new_mat[j][i]);
			}
		}
		return new_mat;
	}



这是效果图。能够看到原来的图像被拉伸了

自适应直方图均衡化
AHE算法通过计算图像的局部直方图,然后又一次分布亮度来来改变图像对照度。因此。该算法更适合于改进图像的局部对照度以及获得很多其它的图像细节。


想像以下一幅图像。左上角是黑乎乎的一团。可是其它区域非常正常,假设仅仅用HE,那么黑乎乎的那团是没法有多大改进的。

于是。你能够把那黑乎乎的一团当作一张图片,对那一部分进行HE,事实上这就是AHE了。就是把图片分片处理,8*8是经常使用的选择。
然后。你就能够写一个循环来操作。算法和HE是一模一样的,当然能够工作,仅仅是速度比較慢。

正如我以下代码所写的,利用双线性插值。
我曾经写CLAHE时候看的博客找不到了T_T   http://m.blog.csdn.net/blog/gududeyhc/8997009这里有可是远远没我曾经看的那篇讲的清楚。假设你去看Pizer的论文预计要花非常多的时间。

以下是我用Java写的CLAHE.

CLAHE比AHE多了裁剪补偿的操作
/*
	 * CLAHE
	 * 自适应直方图均衡化
	 */
	public int[][] AHE(int[][] oldmat,int pblock)
	{
		int block = pblock;
		//将图像均匀分成等矩形大小,8行8列64个块是经常使用的选择
		int width_block = width/block;
		int height_block = height/block;
		
		//存储各个直方图
		int[][] tmp = new int[block*block][256];
		//存储累积函数
		float[][] C = new float[block*block][256];
		
		//计算累积函数
		for(int i = 0 ; i < block ; i ++)
		{
			for(int j = 0 ; j < block ; j++)
			{
				int start_x = i * width_block;
				int end_x = start_x + width_block;
				int start_y = j * height_block;
				int end_y = start_y + height_block;
				int num = i+block*j;
				int total = width_block * height_block;
				for(int ii = start_x ; ii < end_x ; ii++)
				{
					for(int jj = start_y ; jj < end_y ; jj++)
					{
						int index = oldmat[jj][ii];
						tmp[num][index]++;
					}
				}
				
				
				//裁剪操作
				int average = width_block * height_block / 255;
				int LIMIT = 4 * average;
				int steal = 0;
				for(int k = 0 ; k < 256 ; k++)
				{
					if(tmp[num][k] > LIMIT){
						steal += tmp[num][k] - LIMIT;
						tmp[num][k] = LIMIT;
					}
					
				}
				
				int bonus = steal/256;
				//hand out the steals averagely
				for(int k = 0 ; k < 256 ; k++)
				{
					tmp[num][k] += bonus;
				}
				
				//计算累积分布直方图
				for(int k = 0 ; k < 256 ; k++)
				{
					if( k == 0)
						C[num][k] = 1.0f * tmp[num][k] / total;
					else
						C[num][k] = C[num][k-1] + 1.0f * tmp[num][k] / total;
				}
				
			}
		}
		
		int[][] new_mat = new int[height][width];
		//计算变换后的像素值
		//依据像素点的位置,选择不同的计算方法
		for(int  i = 0 ; i < width; i++)
		{
			for(int j = 0 ; j < height; j++)
			{
				//four coners
				if(i <= width_block/2 && j <= height_block/2)
				{
					int num = 0;
					new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);
				}else if(i <= width_block/2 && j >= ((block-1)*height_block + height_block/2)){
					int num = block*(block-1);
					new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);
				}else if(i >= ((block-1)*width_block+width_block/2) && j <= height_block/2){
					int num = block-1;
					new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);
				}else if(i >= ((block-1)*width_block+width_block/2) && j >= ((block-1)*height_block + height_block/2)){
					int num = block*block-1;
					new_mat[j][i] = (int)(C[num][oldmat[j][i]] * 255);
				}
				
				//four edges except coners
				else if( i <= width_block/2 )
				{
					//线性插值
					int num_i = 0;
					int num_j = (j - height_block/2)/height_block;
					int num1 = num_j*block + num_i;
					int num2 = num1 + block;
					float p =  (j - (num_j*height_block+height_block/2))/(1.0f*height_block);
					float q = 1-p;
					new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);
				}else if( i >= ((block-1)*width_block+width_block/2)){
					//线性插值
					int num_i = block-1;
					int num_j = (j - height_block/2)/height_block;
					int num1 = num_j*block + num_i;
					int num2 = num1 + block;
					float p =  (j - (num_j*height_block+height_block/2))/(1.0f*height_block);
					float q = 1-p;
					new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);
				}else if( j <= height_block/2 ){
					//线性插值
					int num_i = (i - width_block/2)/width_block;
					int num_j = 0;
					int num1 = num_j*block + num_i;
					int num2 = num1 + 1;
					float p =  (i - (num_i*width_block+width_block/2))/(1.0f*width_block);
					float q = 1-p;
					new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);
				}else if( j >= ((block-1)*height_block + height_block/2) ){
					//线性插值
					int num_i = (i - width_block/2)/width_block;
					int num_j = block-1;
					int num1 = num_j*block + num_i;
					int num2 = num1 + 1;
					float p =  (i - (num_i*width_block+width_block/2))/(1.0f*width_block);
					float q = 1-p;
					new_mat[j][i] = (int)((q*C[num1][oldmat[j][i]]+ p*C[num2][oldmat[j][i]])* 255);
				}
				
				//inner area
				else{
					int num_i = (i - width_block/2)/width_block;
					int num_j = (j - height_block/2)/height_block;
					int num1 = num_j*block + num_i;
					int num2 = num1 + 1;
					int num3 = num1 + block;
					int num4 = num2 + block;
					float u = (i - (num_i*width_block+width_block/2))/(1.0f*width_block);
					float v = (j - (num_j*height_block+height_block/2))/(1.0f*height_block);
					new_mat[j][i] = (int)((u*v*C[num4][oldmat[j][i]] + 
							(1-v)*(1-u)*C[num1][oldmat[j][i]] +
							u*(1-v)*C[num2][oldmat[j][i]] +
							v*(1-u)*C[num3][oldmat[j][i]]) * 255);

				}
				
				new_mat[j][i] = new_mat[j][i] + (new_mat[j][i] << 8) + (new_mat[j][i] << 16);		
			}
		}
		
		return new_mat;
		
	}

Application:Tonal adjustment(色调调整)

Ex3.1

Write a simple application to change the color balance of an imageby multiplying each color value by a different user-specified constant. If you want to getfancy, you can make this application interactive, with sliders. 

我仅仅是非常easy地将颜色乘以系数,有slider,比較方便~~

#include "opencv2/highgui/highgui.hpp"
#include <iostream>

using namespace cv;

int alpha = 50;
Mat image,new_image;
static void change_color(int, void*)
{
	for( int y = 0; y < image.rows; y++ )
			for( int x = 0; x < image.cols; x++ )
				for( int c = 0; c < 3; c++ )
          			new_image.at<Vec3b>(y,x)[c] = saturate_cast<uchar>( alpha/50.0 *( image.at<Vec3b>(y,x)[c] ));

    imshow("Image", new_image);
}

int main( int, char** argv )
{
   	image = imread( argv[1] );
   	new_image = Mat::zeros( image.size(), image.type() );
   	namedWindow("Image", 1);

	createTrackbar( "pick:", "Image", &alpha, 100, change_color);
	change_color(0, 0);

	waitKey();
	return 0;
}


Linear filtering(线性滤波)

相关: g=fh

卷积: g=f

暂时不考虑边缘。所以8*8的图形进行相关或卷积操作后就得到6*6的图形
由于我们的h(有时叫做核函数)是中心对称的,所以相关和卷积得到的结果是一样的
那不一样呢?看以下的样例,用个一维的样例。{x,y}是核函数。{a,b,c,d,e}是数据
这里构造核  与数据列表的卷积.
In[1]:=
Click for copyable input
Out[1]=
这里构造相关.
In[2]:=
Click for copyable input
Out[2]=
Padding(border effects)
之前提到过,8*8的图像用3*3的核处理会成6*6。那么边界要怎么处理呢?

  • 0填充,非常easy的处理方式
  • 常数填充
  • 夹取填塞(clamp),不断地复制边缘像素的值
  • 重叠填塞(wrap)。以环状形态围绕图像进行循环
  • 镜像填塞(mirror),像素围绕图像边界进行镜像反射
  • 延长(extend),通过在边缘像素值中减去镜像信号的方式延长信号
每种模式的公式要我们自己推导(Ex3.8)

Separable filtering(可分离的滤波)

二维卷积运算,更新一个像素点肯定须要K次运算(K是核函数的大小)
文中提出了一种加速的方法,先用一维行向量进行卷积,再用一维列向量进行卷积,假设一个卷积核能够採用这个方案计算,就是可分离的。(这样子就仅仅有2K次操作,非常神秘吧)

K =vhT  

将卷积核K拆分成列向量v和行向量h
当然。并非全部K都能被拆分。以下举能够拆分的一些核函数

最简单的平均滤波,[1,1,1......,1]*[1,1,1......,1]T   = K
再看第3个高斯核。[1,4,6,4,1]*[1,4,6,4,1]T = K
那么怎样推断核函数是不是可分离的呢?文中告诉我们用神秘值分解的方法。
我的想法是,样例都是中心对称的核函数,是不是必须要满足中心对称。仅仅是一个朴素的想法罢了~

原来。OpenCV帮我们实现了
sepFilter2D()
用分解的核函数对图像做卷积。
首先,图像的每一行与一维的核kernelX做卷积。然后,运算结果的每一列与一维的核kernelY做卷积。

Examples of linear filtering(线性滤波演示样例)

如上图
  • box,非常easy,就是平均
  • bilinear,双线性核
  • Gaussian,非常有名,高斯核
  • Sobel算子,有效突出水平边缘(拉普拉斯算子也用于边缘提取,canny检測是边缘提取经常使用的算法)
  • corner,简单的角点检測器。同一时候寻找水平和垂直方向的二阶导数,这种算子不仅对正方形的角点有响应。并且对沿对角线方向的边缘也有响应
拉普拉斯算子是这种
1 1 1
1 -8 1
1 1 1
或者以下
0 1 0
1 -4 1
0 1 0

在Opencv里,都有各自相应的函数
boxFilter()
就是滑动窗体平均滤波的二维版。



GaussianBlur()
高斯平均。也就是高斯模糊。

medianBlur()
中值滤波,有效去除椒盐噪声。

bilateralFilter()
双线性滤波。
 

以下我们来看看sobel算子和Laplace算子的边缘提取效果
Sobel demo
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

int main( int, char** argv )
{

  Mat src, src_gray;
  Mat grad;
  const char* window_name = "Sobel Demo - Simple Edge Detector";
  int scale = 1;
  int delta = 0;
  int ddepth = CV_16S;

  /// Load an image
  src = imread( argv[1] );

  if( !src.data )
    { return -1; }

  GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );

  /// Convert it to gray
  cvtColor( src, src_gray, CV_RGB2GRAY );

  /// Create window
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );

  /// Generate grad_x and grad_y
  Mat grad_x, grad_y;
  Mat abs_grad_x, abs_grad_y;

  /// Gradient X
  Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
  convertScaleAbs( grad_x, abs_grad_x );

  /// Gradient Y
  Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
  convertScaleAbs( grad_y, abs_grad_y );

  /// Total Gradient (approximate)
  addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad );

  imshow( window_name, grad );

  waitKey(0);

  return 0;
}



Laplace demo
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

int main( int, char** argv )
{

  Mat src, src_gray, dst;
  int kernel_size = 3;
  int scale = 1;
  int delta = 0;
  int ddepth = CV_16S;
  const char* window_name = "Laplace Demo";

  /// Load an image
  src = imread( argv[1] );

  if( !src.data )
    { return -1; }

  /// Remove noise by blurring with a Gaussian filter
  GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );

  /// Convert the image to grayscale
  cvtColor( src, src_gray, CV_RGB2GRAY );

  /// Create window
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );

  /// Apply Laplace function
  Mat abs_dst;

  Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
  convertScaleAbs( dst, abs_dst );

  /// Show what you got
  imshow( window_name, abs_dst );

  waitKey(0);

  return 0;
}


假设你想实现自己的滤波器,在OpenCV里要怎么做呢,非常easy
filter2D(image, image, image.depth(), (Mat<float>(3,3)<<-1, -1, -1, -1, 9, -1, -1, -1, -1), Point(1,1), 128);
构造了一个例如以下所看到的的核对图像做卷积:
-1 -1 -1
-1 9 -1
-1 -1 -1
核的锚点在(1,1)位置。卷积之后每一个像素加上128.
核的锚点表示一个被滤波的点在核内的位置。

锚点应该处于核内部。缺省值 (-1,-1) 表示锚点在核中心。

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

int main ( int, char** argv )
{
  /// Declare variables
  Mat src, dst;

  Mat kernel;
  Point anchor;
  double delta;
  int ddepth;
  int kernel_size;
  const char* window_name = "filter2D Demo";

  int c;

  /// Load an image
  src = imread( argv[1] );

  if( !src.data )
    { return -1; }

  /// Create window
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );

  /// Initialize arguments for the filter
  anchor = Point( -1, -1 );
  delta = 0;
  ddepth = -1;

  /// Loop - Will filter the image with different kernel sizes each 0.5 seconds
  int ind = 0;
  for(;;)
       {
         c = waitKey(500);
         /// Press 'ESC' to exit the program
         if( (char)c == 27 )
           { break; }

         /// Update kernel size for a normalized box filter
         kernel_size = 3 + 2*( ind%5 );
         kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);

         /// Apply filter
         filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
         imshow( window_name, dst );
         ind++;
       }

  return 0;
}

Band-pass and steerable filters(带通和导向滤波器)

Sobel和Corner算子是带通和导向滤波器的简单样例
能够按例如以下方式构造更精细的核:首先用高斯滤波器平滑图像。再用一阶或二阶导数来构造更精细的核,跟之前的sobel demo,Laplace demo做得几乎相同
文中的意思是高阶的导向滤波器更适合检測边缘的结构

Summed area table(integral image)积分图像

什么是积分图像?
积分图像就是一个新的table s(width*height)
s(2,2) = f(1,1)+f(1,2)+f(2,1)+f(2,2)
s中的每一个点都是左上方全部数字与自己之和

非常easy发现有 s(i, j) = s(i1, j) +s(i, j1)s(i1, j1) +f(i, j)

有了sum table后,我们要求(1,1)到(4,4)这个矩形的积分就非常快了。仅仅利用上面4个紫色的点。48+3-13-14 = 24
非常明显,积分图像起到一个加快运算的功能,那么应用在哪呢?
  • 人脸检測利用积分图像来计算简单的多尺度上的底层特征
  • 立体视觉和运动算法中差分平方和(SSD)的求和计算
  • 可分离的移动平均滤波器

OpenCV有自带的计算积分图的函数integral 提供了很多其它选项,sum是和,sqsum是平方和图像。tilted是旋转45度的和

  • sum: the sum summation integral image

  • sqsum: the square sum integral image

  • tiltedimage is rotated by 45 degrees and then its integral is calculated


拭目以待

Recursive filtering(递归滤波器)
sum table的公式 s(i, j) = s(− 1, j) + s(i, j − 1) − s(− 1, j − 1) + f(i, j)是递归滤波器的一个样例
递归滤波器是指输出值取决于前一个滤波器的输出值

More neighborhood operators(很多其它的领域算子)

线性滤波能够实现大量的图像变换,然而非线性滤波有时能够达到更好的效果

Non linear filtering(非线性滤波)


median filter 中值滤波
中值滤波器选择邻域内的中值作为输出,当噪声(散粒噪声)变化幅度非常大,中值滤波就非常好用。可是对于高斯噪声就不好用了

Bilateral filter 双边滤波 
是结合图像的空间邻近度和像素值类似度的一种折衷处理,同一时候考虑空域信息和灰度类似性,达到保边去噪的目的。具有简单、非迭代、局部的特点。

双边滤波器的优点是能够做边缘保存(edge preserving)。一般过去用的维纳滤波或者高斯滤波去降噪,都会较明显地模糊边缘。对于高频细节的保护效果并不明显。

双边滤波器顾名思义比高斯滤波多了一个高斯方差sigma-d,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会太多影响到边缘上的像素值。这样就保证了边缘附近像素值的保存。可是由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净的滤掉,仅仅能够对于低频信息进行较好的滤波


//OpenCV双边滤波
   //src:输入图像
   //dst:输入图像
   //滤波模板半径
   //颜色空间标准差
   //坐标空间标准差
   bilateralFilter(src,dst,5,10.0,2.0);
//关于滤波,还能够參考这里


Iterated adaptive smoothing and anisotropic diffusion(迭代自适应平滑和各向异性扩散)

眼下还没看懂 = =

Morphology(形态学)

腐蚀,膨胀
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

/// Global variables
Mat src, erosion_dst, dilation_dst;

int erosion_elem = 0;
int erosion_size = 0;
int dilation_elem = 0;
int dilation_size = 0;
int const max_elem = 2;
int const max_kernel_size = 21;

/** Function Headers */
void Erosion( int, void* );
void Dilation( int, void* );

/**
 * @function main
 */
int main( int, char** argv )
{
  /// Load an image
  src = imread( argv[1] );

  if( !src.data )
    { return -1; }

  /// Create windows
  namedWindow( "Erosion Demo", CV_WINDOW_AUTOSIZE );
  namedWindow( "Dilation Demo", CV_WINDOW_AUTOSIZE );
  cvMoveWindow( "Dilation Demo", src.cols, 0 );

  /// Create Erosion Trackbar
  createTrackbar( "Element:
 0: Rect 
 1: Cross 
 2: Ellipse", "Erosion Demo",
          &erosion_elem, max_elem,
          Erosion );

  createTrackbar( "Kernel size:
 2n +1", "Erosion Demo",
          &erosion_size, max_kernel_size,
          Erosion );

  /// Create Dilation Trackbar
  createTrackbar( "Element:
 0: Rect 
 1: Cross 
 2: Ellipse", "Dilation Demo",
          &dilation_elem, max_elem,
          Dilation );

  createTrackbar( "Kernel size:
 2n +1", "Dilation Demo",
          &dilation_size, max_kernel_size,
          Dilation );

  /// Default start
  Erosion( 0, 0 );
  Dilation( 0, 0 );

  waitKey(0);
  return 0;
}

/**
 * @function Erosion
 */
void Erosion( int, void* )
{
  int erosion_type = 0;
  if( erosion_elem == 0 ){ erosion_type = MORPH_RECT; }
  else if( erosion_elem == 1 ){ erosion_type = MORPH_CROSS; }
  else if( erosion_elem == 2) { erosion_type = MORPH_ELLIPSE; }

  Mat element = getStructuringElement( erosion_type,
                       Size( 2*erosion_size + 1, 2*erosion_size+1 ),
                       Point( erosion_size, erosion_size ) );
  /// Apply the erosion operation
  erode( src, erosion_dst, element );
  imshow( "Erosion Demo", erosion_dst );
}

/**
 * @function Dilation
 */
void Dilation( int, void* )
{
  int dilation_type = 0;
  if( dilation_elem == 0 ){ dilation_type = MORPH_RECT; }
  else if( dilation_elem == 1 ){ dilation_type = MORPH_CROSS; }
  else if( dilation_elem == 2) { dilation_type = MORPH_ELLIPSE; }

  Mat element = getStructuringElement( dilation_type,
                       Size( 2*dilation_size + 1, 2*dilation_size+1 ),
                       Point( dilation_size, dilation_size ) );
  /// Apply the dilation operation
  dilate( src, dilation_dst, element );
  imshow( "Dilation Demo", dilation_dst );
}

开运算

dst=open(src,element)=dilate(erode(src,element),element)

闭运算

dst=close(src,element)=erode(dilate(src,element),element)

形态梯度

dst=morph_grad(src,element)=dilate(src,element)-erode(src,element)

"顶帽"

dst=tophat(src,element)=src-open(src,element)

"黑帽"

dst=blackhat(src,element)=close(src,element)-src

暂时图像 temp 在形态梯度以及对“顶帽”和“黑帽”操作时的 in-place 模式下须要。


#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <stdlib.h>
#include <stdio.h>

using namespace cv;

/// Global variables
Mat src, dst;

int morph_elem = 0;
int morph_size = 0;
int morph_operator = 0;
int const max_operator = 4;
int const max_elem = 2;
int const max_kernel_size = 21;

const char* window_name = "Morphology Transformations Demo";


/** Function Headers */
void Morphology_Operations( int, void* );

/**
 * @function main
 */
int main( int, char** argv )
{
  /// Load an image
  src = imread( argv[1] );

  if( !src.data )
    { return -1; }

  /// Create window
  namedWindow( window_name, CV_WINDOW_AUTOSIZE );

  /// Create Trackbar to select Morphology operation
  createTrackbar("Operator:
 0: Opening - 1: Closing  
 2: Gradient - 3: Top Hat 
 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );

  /// Create Trackbar to select kernel type
  createTrackbar( "Element:
 0: Rect - 1: Cross - 2: Ellipse", window_name,
          &morph_elem, max_elem,
          Morphology_Operations );

  /// Create Trackbar to choose kernel size
  createTrackbar( "Kernel size:
 2n +1", window_name,
          &morph_size, max_kernel_size,
          Morphology_Operations );

  /// Default start
  Morphology_Operations( 0, 0 );

  waitKey(0);
  return 0;
}

/**
 * @function Morphology_Operations
 */
void Morphology_Operations( int, void* )
{

  // Since MORPH_X : 2,3,4,5 and 6
  int operation = morph_operator + 2;

  Mat element = getStructuringElement( morph_elem, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );

  /// Apply the specified morphology operation
  morphologyEx( src, dst, operation, element );
  imshow( window_name, dst );
}



Reference

  • Richard Szeliski 《Computer Vision: Algorithms and Applications》
  • http://blog.csdn.net/xiaowei_cqu/article/details/7785365
  • 《The OpenCV Reference Manual 》 Release 2.4.7



我近期刚開始看Computer Vision: Algorithms and Applications这本书,发现这本书内容非常丰富。这里的文章我也会不断更新,假设你也非常感兴趣,谢谢与我一起讨论^^

【推广】 免费学中医,健康全家人
原文地址:https://www.cnblogs.com/ldxsuanfa/p/10566893.html