Computer Vision: Algorithms and ApplicationsのImage processing
如痴如醉地钟爱着Richard Szeliski所著的此书,在其后继章节(从第4章开始)中对当前研究领域的最新动态进行了详细阐述,并提供了丰富的参考文献。如需深入了解相关内容,请深入阅读这些相关文献。
Point operators(点算子)
点运算是最简单的一类图像处理运算,如简单的对比度变换,亮度变换
Pixel transform(像素变换)
g(x) = af(x) + b中,参数a和b通常被用来调节对比度与亮度。例如,在OpenCV的某个示例中就展示了这种简单的对比度与亮度变换的应用场景,并且该公式的应用非常广泛
g(x) = a(x)f (x) + b(x) a,b不一定是常数,可以是空间上的函数
g(x) = (1 − α)f0(x) + αf1(x) α from0→1 可以实现两幅图像的淡入淡出
在OpenCV中存在addWeighted( src1, alpha, src2, beta, 0.0, dst)此函数旨在实现以下公式。
g(x) = [f(x)]^{1/γ} 是伽马校正的一种表现形式,在图像处理领域中被视为一种幂次变换方案,在实际应用中尤其常被应用于图像预处理环节。其中参数γ通常取值约为2.2
除了伽马校正,幂变换在控制对比度也很有用,可以取不同的γ试一试
除了上面这些,我知道的还有:
g(x)等于L减一减去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]全范围内以实现更好的视觉效果。
下面是直方图均衡化的代码,有个累积函数的概念,其实很简单。
我先计算出每个灰度级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处理的话,在提升暗部细节方面存在明显局限性。
于是,在图像处理中,我们可以将这一模糊区域模拟成一个图像。在这一区域内执行特定的处理操作后发现其效果与AHE一致。这等同于将整个图像划分为若干块,并且其中一种常见策略是选择8×8的小块分割。
然后就能编写一个循环来进行操作;同时该算法与HE几乎完全相同并且同样能够运行只是其运行速度较慢
正如我下面代码所写的,利用双线性插值。
由于之前阅读关于CLAHE的博客已经无法找到(T_T),该资源仍然显得不够完善。相比之下,现有的资源仍然显得不够完善。对于那些希望深入研究这一领域的读者而言,查阅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
Develop a straightforward application designed to alter an image's color balance by scaling each color component with its own user-defined multiplier. If you aim for more complexity, integrating interactive sliders into the application would be an excellent choice.
我只是很简单地将颜色乘以系数,有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 = f ⊗ h
卷积: g = f ∗ h

暂时不考虑边缘,所以88的图形进行相关或卷积操作后就得到66的图形
由于我们的h(有时被称作kernel function)关于原点对称,在这种情况下相关运算与卷积操作的输出结果完全一致。
那不一样呢?看下面的例子,用个一维的例子,{x,y}是核函数,{a,b,c,d,e}是数据
这里构造核

与数据列表的卷积.
|In[1]:=|

|
|---|---|
|Out[1]=|

|
|---|---|
这里构造相关.
|In[2]:=|

|
|---|---|
|Out[2]=|

|
|---|---|
Padding(border effects)
之前提到过,88的图像用33的核处理会成6*6,那么边界要怎么处理呢?

- 零填充
- 恒定填充
- 夹取填塞(clamp),通过不断复制边缘像素的值来实现图像边界的有效填充
- 循环填塞(wrap),采用环状方式环绕图像进行循环性填补
- 镜像复制(mirror),通过镜像反射的方式复制边界像素信息
- 延伸(extend),利用边缘像素值减去镜像信号的方式实现信号的有效延伸
每种模式的公式要我们自己推导(Ex3.8)
Separable filtering(可分离的滤波)
二维卷积运算,更新一个像素点肯定需要 K 2 次运算(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(线性滤波示例)
如上图
方框运算非常简单就是取平均值;
双线型核常用于图像插值;
高斯型核是非常有名的滤波核;
Sobel算子是一种有效的水平边缘检测方法(拉普拉斯算子同样用于边缘检测Canny算法是一种广泛应用的边缘检测方法);
角点探测器是一种简单的角点检测方法它通过同时计算水平和垂直方向的二阶导数来定位角点这种运算不仅能够检测到正方形等规则形状的角落还能够识别对角线方向上的边缘特征。
拉普拉斯算子是这样的
| 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里要怎么做呢,很简单
创建了一个如下所述的核与原始图像进行卷积操作。
如下的3x3矩阵:
\begin{bmatrix} -(-\frac{2}{45}) & \frac{9}{25} & -(−\frac{2}{45}) \\ -(−\frac{2}{45}) & \frac{8}{9} & -(−\frac{2}{45}) \\ -(−\frac{2}{45}) & \frac{9}{25} & -(−\frac{2}{45}) \end{bmatrix}
锚定在(基于尺寸计算的位置)坐标系中,在卷积运算完成后将每个像素值增加至平均亮度(即加权平均后的结果)。
该焦点位于核内部,并且已被滤波处理。 焦点应位于核内部区域。 默认设置下(-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演示和Laplace演示的效果
文中的意思是高阶的导向滤波器更适合检测边缘的结构
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中的每个点都是左上方所有数字与自己之和
很容易发现有 s(i, j) = s(i−1, j) +s(i, j−1)−s(i−1, j−1) +f(i, j).
当构建sum table之后,在(1,1)至(4,4)这一矩形区域内快速计算其积分为24,在此区域内仅使用位于该区域上方四个紫色标记的点进行计算即为24
很明显,积分图像起到一个加快运算的功能,那么应用在哪呢?
- 基于积分图像的人脸检测能够有效提取多层次的空间特征
- 在立体视觉与运动算法领域中,差分平方和(SSD)被广泛用于特征匹配过程
- 支持可分离运算的移动平均滤波器在实现效率上有显著提升
OpenCV内置了一个用于计算积分图矩阵的函数[integral](http://docs.opencv.org/2.4.8/modules/imgproc/doc/miscellaneous_transformations.html?highlight=integral#void integral(InputArray src,%spaceOutputArray%space sum,%spaceint%space sdepth\right)提供了多种选择。其中sum表示积分和矩阵,sum_sq表示平方和矩阵,tilted_sum表示倾斜45度方向上的累加矩阵
sum : the sum summation integral image
sqsum : the square sum integral image
tilted : image is rotated by 45 degrees and then its integral is calculated
拭目以待
Recursive filtering(递归滤波器)
sum table的公式 s ( i, j ) = s ( i − 1 , j ) + s ( i, j − 1) − s ( i − 1 , j − 1) + f ( i, j )是递归滤波器的一个例子
递归滤波器是指输出值取决于前一个滤波器的输出值
More neighborhood operators(更多的领域算子)
线性滤波能够完成大量的图像变换,
但是,在某些情况下,
非线性滤波可能会提供更优的效果。
线性滤波能够完成大量的图像变换,
但是,在某些情况下,
非线性滤波可能会提供更优的效果。
Non linear filtering(非线性滤波)

median filter 中值滤波
该滤波器采用邻域内数值的中间值来生成输出;在面对大幅波动的散粒噪声时会表现出良好的效果;相比之下,在面对高斯噪声时则会失去优势
Bilateral filter 双边滤波
是一种在图像空间邻近度与像素值相似度之间寻求折中的处理方法。该方法具备简洁性、无需迭代计算以及局部特性,并旨在实现保边去噪的效果。其优势在于能够有效保留边缘细节(edge preserving)。传统的维纳滤波或高斯滤波在降噪方面存在不足,在边缘附近往往会引入模糊效应,在高频细节保护方面表现欠佳。通过引入一个高斯方差σ-d参数来调节空间权重分布,则能显著提升对高频噪声的抑制能力的同时较好地保护低频信息。不过需要注意的是,在实际应用中由于保存了过多的高频信息量,在处理彩色图像时可能会导致无法彻底去除高频噪声的问题


//OpenCV双边滤波
//src:输入图像
//dst:输入图像
//滤波模板半径
//颜色空间标准差
//坐标空间标准差510.02.0
//关于滤波,还可以参考[这里]()
Repeated self-adaptive smoothing processes combined with anisotropic diffusion mechanisms
目前还没看懂 = =
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:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Erosion Demo",
&erosion_elem, max_elem,
Erosion );
createTrackbar( "Kernel size:\n 2n +1", "Erosion Demo",
&erosion_size, max_kernel_size,
Erosion );
/// Create Dilation Trackbar
createTrackbar( "Element:\n 0: Rect \n 1: Cross \n 2: Ellipse", "Dilation Demo",
&dilation_elem, max_elem,
Dilation );
createTrackbar( "Kernel size:\n 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:\n 0: Opening - 1: Closing \n 2: Gradient - 3: Top Hat \n 4: Black Hat", window_name, &morph_operator, max_operator, Morphology_Operations );
/// Create Trackbar to select kernel type
createTrackbar( "Element:\n 0: Rect - 1: Cross - 2: Ellipse", window_name,
&morph_elem, max_elem,
Morphology_Operations );
/// Create Trackbar to choose kernel size
createTrackbar( "Kernel size:\n 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 《计算机视觉:算法与应用》
*- 《OpenCV参考手册》 第二版第2.4.7版
近来刚开始接触Computer Vision: Algorithms and Applications这本书。读来深感其内容十分丰富。这里提供的文章我也会持续更新。如若你也热爱此领域,则欢迎随时探讨。
