实际比较filter2D和imfilter之间的关系

?

卷积运算是图像处理和增强中经常遇到的一种算法。由于很多优秀的开源算法都是采用matlab编写的,在我改写为c语言的时候就必然会遇到改写卷积算法的问题。在matlab中,卷积可以由imfilter来实现,在opencv中则是由filter2D来实现。它们之间的具体转化过程是什么?我通过一系列实验来研究。

一、实验准备

为了方便观察,仍然是采用分开来研究的方法。1)是输入数字作为卷积内容,直接观察结果;2)是采用小块图片作为卷积内容,仍然是比较结果;3)是采用真实的图片和真实的卷积核作为输入,对比最后处理图片的效果。

那么,首先需要了解的就是在matlab中和opencv中如何将矩阵的内容进行比对?

在matlab中可以直接打印到矩阵变量中去,而在opencv中可以这样直接打印到屏幕上面。然后将两者在matlab中做减法,直观地比较最后的结果。

二、过程

1)是输入数字作为卷积内容

int _tmain(int argc, _TCHAR* argv[])

{
    //filter2d的卷积方法
    printf(    "filter2d的卷积方法\n");
    Mat srcMat(10,10,CV_32F);
    Mat dstMat(10,10,CV_32F);
    Mat srcH(3,3,CV_32F);
    srcH.at<float>(0,0) = -2;
    srcH.at<float>(0,1) = -1;
    srcH.at<float>(0,2) = 4;
    srcH.at<float>(1,0) = 3;
    srcH.at<float>(1,1) = 3;
    srcH.at<float>(1,2) = 3;
    srcH.at<float>(2,0) = 3;
    srcH.at<float>(2,1) = 2;
    srcH.at<float>(2,2) = 1;
    printf(    "卷积核\n");
    for (int i=0;i<srcH.rows;i++){
        for (int j=0;j<srcH.cols;j++){
            printf("%f ",srcH.at<float>(i,j) );
        }
            printf("\n");
    }
    printf(    "输入\n");
    for (int i = 0; i < 10; i++){
        for (int j = 0; j < 10; j++)
          srcMat.at<float>(i,j) = i+1;
    }
    for (int i = 0; i < 10; i++){
        for (int j = 0; j < 10; j++){
            printf("%.1f ",srcMat.at<float>(i,j));
        }
        printf("\n");
    }
    printf(    "输出\n");
    filter2D(srcMat,dstMat,srcMat.depth(),srcH);
    printf("\n"); printf("\n");
    for (int i = 0; i < 10; i++){
        for (int j = 0; j < 10; j++){
            printf("%.1f ",dstMat.at<float>(i,j));
        }
        printf("\n");
    }
    waitKey(100);
    return 0;
}

而在matlab中也有相关输入

>> clear

>> H = [-2.000000 -1.000000 4.000000

3.000000 3.000000 3.000000

3.000000 2.000000 1.000000]

H =

-2    -1     4

3     3     3

3     2     1

>> I=[1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0

2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0 2.0

3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0 3.0

4.0 4.0 4.0 4.0 4.0 4.0 4.0 4.0 4.0 4.0

5.0 5.0 5.0 5.0 5.0 5.0 5.0 5.0 5.0 5.0

6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0 6.0

7.0 7.0 7.0 7.0 7.0 7.0 7.0 7.0 7.0 7.0

8.0 8.0 8.0 8.0 8.0 8.0 8.0 8.0 8.0 8.0

9.0 9.0 9.0 9.0 9.0 9.0 9.0 9.0 9.0 9.0

10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0 10.0]

I =

1     1     1     1     1     1     1     1     1     1

2     2     2     2     2     2     2     2     2     2

3     3     3     3     3     3     3     3     3     3

4     4     4     4     4     4     4     4     4     4

5     5     5     5     5     5     5     5     5     5

6     6     6     6     6     6     6     6     6     6

7     7     7     7     7     7     7     7     7     7

8     8     8     8     8     8     8     8     8     8

9     9     9     9     9     9     9     9     9     9

10    10    10    10    10    10    10    10    10    10

>> rst = imfilter(I,H)

rst =

12    21    21    21    21    21    21    21    21    16

24    37    37    37    37    37    37    37    37    24

36    53    53    53    53    53    53    53    53    32

48    69    69    69    69    69    69    69    69    40

60    85    85    85    85    85    85    85    85    48

72   101   101   101   101   101   101   101   101    56

84   117   117   117   117   117   117   117   117    64

96   133   133   133   133   133   133   133   133    72

108   149   149   149   149   149   149   149   149    80

87    99    99    99    99    99    99    99    99    33

则计算两者之差

rst3 =

-11    -2    -2    -2    -2    -2    -2    -2    -2    -7

-13     0     0     0     0     0     0     0     0   -13

-17     0     0     0     0     0     0     0     0   -21

-21     0     0     0     0     0     0     0     0   -29

-25     0     0     0     0     0     0     0     0   -37

-29     0     0     0     0     0     0     0     0   -45

-33     0     0     0     0     0     0     0     0   -53

-37     0     0     0     0     0     0     0     0   -61

-41     0     0     0     0     0     0     0     0   -69

-66   -54   -54   -54   -54   -54   -54   -54   -54  -120

结论是在边界会有所不同,这个应该是不同算法对于边界的处理不同而已。那么主体成分是完全一样的。

2)是采用小块图片作为卷积内容

那么准备了小块的灰度图片作为卷积内容

//读取图片的处理的方法

Mat gray = imread("test.jpg",0);
    imwrite("gray.jpg",gray);
    gray.convertTo(gray,CV_32F);
    Mat dst;
    filter2D(gray,dst,gray.depth(),srcH);
    for (int i = 0; i < gray.rows; i++){
        for (int j = 0; j < gray.cols; j++){
            printf("%f ",dst.at<float>(i,j));
        }
        printf("\n");
    }

同样matlab

I = im2double(imread(‘gray.jpg‘))

H =

-2    -1     4

3     3     3

3     2     1

>> rst = imfilter(I,H)

对比相关结果,这里可以发现,在matlab中图像是归一化存储的。

在输入之前归一化,这样就会得到比较好的结果。

结果比较像

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace std;
using namespace cv;
int _tmain(int argc, _TCHAR* argv[])
{
    ////filter2d的卷积方法
    printf(    "filter2d的卷积方法\n");
    //Mat srcMat(10,10,CV_32F);
    //Mat dstMat(10,10,CV_32F);
    Mat srcH(3,3,CV_32F);
    srcH.at<float>(0,0) = -2;
    srcH.at<float>(0,1) = -1;
    srcH.at<float>(0,2) = 4;
    srcH.at<float>(1,0) = 3;
    srcH.at<float>(1,1) = 3;
    srcH.at<float>(1,2) = 3;
    srcH.at<float>(2,0) = 3;
    srcH.at<float>(2,1) = 2;
    srcH.at<float>(2,2) = 1;
    printf(    "卷积核\n");
    for (int i=0;i<srcH.rows;i++){
        for (int j=0;j<srcH.cols;j++){
            printf("%f ",srcH.at<float>(i,j) );
        }
        printf("\n");
    }
    //读取图片的处理的方法
    Mat gray = imread("test.jpg",0);
    imwrite("gray.jpg",gray);
    gray.convertTo(gray,CV_32F);
    gray = gray/255; //归一化处理
    fstream ftxt;
    ftxt.open("src.txt",ios::out); //写入的方式,同时是append模式的就不会覆盖掉前面的东西了。 
    for (int i = 0; i < gray.rows; i++){
        for (int j = 0; j < gray.cols; j++){
            ftxt<<gray.at<float>(i,j)<<" ";
        }
        ftxt<<endl;
    }
    ftxt.close();
    Mat dst;
    filter2D(gray,dst,gray.depth(),srcH);
    
    ftxt.open("rst.txt",ios::out); //写入的方式,同时是append模式的就不会覆盖掉前面的东西了。 
    for (int i = 0; i < gray.rows; i++){
        for (int j = 0; j < gray.cols; j++){
             ftxt<<dst.at<float>(i,j)<<" ";
        }
         ftxt<<endl;
    }
    ftxt.close();
    imshow("dst",dst);
    waitKey();
    return 0;
}

结果令人满意

3)是采用真实的图片和真实的卷积核作为输入

结论除了在图片的边界有差异外,在其他的地方,这个差异在小数点后4位,应该说是非常相似的,可以用于实际生成。

三、小结和反思

最后的结论是可以正常使用,但是在输入之前,需要将图片归一化处理。那么,通过这个实验,除了获得面上的这个知识之外,更多的应该是一种实验的方法。很多时候,无论是做改写还是其他的事情之前,将相关可能发生的情况设计好;力图获得稳定准确的结果。这些对于最终获得设计之中的结论是非常重要和有价值的。

感谢阅读到此,希望能够有所帮助。

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace std;
using namespace cv;

int _tmain(int argc, _TCHAR* argv[])
{
	////filter2d的卷积方法
	printf(	"filter2d的卷积方法\n");
	//Mat srcMat(10,10,CV_32F);
	//Mat dstMat(10,10,CV_32F);
	Mat srcH(3,3,CV_32F);
	srcH.at<float>(0,0) = -2;
	srcH.at<float>(0,1) = -1;
	srcH.at<float>(0,2) = 4;
	srcH.at<float>(1,0) = 3;
	srcH.at<float>(1,1) = 3;
	srcH.at<float>(1,2) = 3;
	srcH.at<float>(2,0) = 3;
	srcH.at<float>(2,1) = 2;
	srcH.at<float>(2,2) = 1;
	printf(	"卷积核\n");
	for (int i=0;i<srcH.rows;i++){
		for (int j=0;j<srcH.cols;j++){
			printf("%f ",srcH.at<float>(i,j) );
		}
		printf("\n");
	}
	//printf(	"输入\n");
	//for (int i = 0; i < 10; i++){
	//	for (int j = 0; j < 10; j++)
	//	  srcMat.at<float>(i,j) = i+1;
	//}
	//for (int i = 0; i < 10; i++){
	//	for (int j = 0; j < 10; j++){
	//		printf("%.1f ",srcMat.at<float>(i,j));
	//	}
	//	printf("\n");
	//}
	//printf(	"输出\n");
	//filter2D(srcMat,dstMat,srcMat.depth(),srcH);
	//printf("\n"); printf("\n");
	//for (int i = 0; i < 10; i++){
	//	for (int j = 0; j < 10; j++){
	//		printf("%.1f ",dstMat.at<float>(i,j));
	//	}
	//	printf("\n");
	//}
	//读取图片的处理的方法
	Mat gray = imread("test.jpg",0);
	imwrite("gray.jpg",gray);
	gray.convertTo(gray,CV_32F);
	gray = gray/255; //归一化处理
	fstream ftxt;
	ftxt.open("src.txt",ios::out); //写入的方式,同时是append模式的就不会覆盖掉前面的东西了。
	for (int i = 0; i < gray.rows; i++){
		for (int j = 0; j < gray.cols; j++){
			ftxt<<gray.at<float>(i,j)<<" ";
			//printf("%.1f ",dst.at<float>(i,j));
		}
		ftxt<<endl;
		//printf("\n");
	}
	ftxt.close();
	Mat dst;
	filter2D(gray,dst,gray.depth(),srcH);

	ftxt.open("rst.txt",ios::out); //写入的方式,同时是append模式的就不会覆盖掉前面的东西了。
	for (int i = 0; i < gray.rows; i++){
		for (int j = 0; j < gray.cols; j++){
			 ftxt<<dst.at<float>(i,j)<<" ";
			//printf("%.1f ",dst.at<float>(i,j));
		}
		 ftxt<<endl;
		//printf("\n");
	}
	ftxt.close();
	imshow("dst",dst);

	waitKey();
	return 0;
}

p.s 转一篇有用博文,时间久了原始链接已经丢失,抱歉

Overview:

imfill是matlab的一个函数,在http://www.mathworks.cn/cn/help/images/ref/imfill.html 中有详细的讲解。这个函数有好几种不同的签名。在这里我的侧重点是imfill(m, ‘holes‘),以及如何用openCV来实现imfill一样的功能。本文有三部分组成。

1. 使用Matlab 的imfill 进行填充图像

在Matlab中简单的几行代码就能实现:


1
2
3
4
5
6
7
8
 
clc;
clear;
BW = im2bw( imread(‘imfilltest.tif‘));
imshow(BW);
holes = imfill(BW, ‘holes‘);

BW(~holes) = 1; 
figure,imshow(holes);

左图为填充前的图像,右图是填充后的图像:

2. 用opencv来实现imfill(bw, ‘holes‘)

opencv 在这里不像matlab那么好用了,matlab调用下。

C++ Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 
#include 
#include 
#include

using namespace std;
using namespace cv;

void my_imfillholes(Mat &src)
{
   // detect external contours
   //
   vector > contours;
   vector hierarchy;
   findContours(src, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
   //
   // fill external contours
   //
   if( !contours.empty() && !hierarchy.empty() )
   {
      for (int idx=0;idx < contours.size();idx++)
      {
         drawContours(src,contours,idx,Scalar::all(255),CV_FILLED,8);
      }
   }
}

void test_my_imfillholes()
{
   Mat m = imread(filltestName,IMREAD_GRAYSCALE);
   //threshold, (i,j)>100 -->255
   Mat th_m;
   threshold(m, th_m, 100, 255, THRESH_BINARY);
   my_imfillholes(th_m);
   namedWindow(WinName, CV_WINDOW_AUTOSIZE);
   imshow(WinName, th_m);
   waitKey(0); 
}

void main()
{
   test_my_imfillholes();
   system("pause");
}

3. imfill 和opencv实现的imfill 对矩阵进行操作的对比

我仍有点不放心,觉得尽管2幅图看起来差不多,但是是不是完全一样呢,然后我觉得用个矩阵试一下。

m = [1, 1, 1, 0, 0, 0, 0, 0;

1, 0, 1, 0, 1, 1, 0, 0;

1, 0, 1, 0, 1, 1, 0, 0;

1, 1, 1, 0, 1, 0, 1, 0;

1, 0, 1, 0, 1, 0, 1, 0;

1, 1, 1, 0, 1, 0, 1, 0;

1, 0, 1, 0, 0, 1, 1, 0;

1, 1, 1, 0, 0, 0, 0, 0];

without_holes = imfill(m, ‘holes‘)

得到结果:

without_holes =
     1     1     1     0     0     0     0     0
     1     1     1     0     1     1     0     0
     1     1     1     0     1     1     0     0
     1     1     1     0     1     1     1     0
     1     1     1     0     1     1     1     0
     1     1     1     0     1     1     1     0
     1     1     1     0     0     1     1     0
     1     1     1     0     0     0     0     0

然后用第2部分所说的opencv的方法也试一下,结果发现是这样的:

without_holes =
     0     0     0     0     0     0     0     0
     0     1     1     0     1     1     0     0
     0     1     1     0     1     1     0     0
     0     1     1     0     1     1     1     0
     0     1     1     0     1     1     1     0
     0     1     1     0     1     1     1     0
     0     1     1     0     0     1     1     0
     0     0     0     0     0     0     0     0

是不一样的。这个问题折腾了我一个晚上,终于,我在

http://docs.opencv.org/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html#findcontours

中的 findContours找到了这样的一个note:

Note:

Source image is modified by this function. Also, the function does not take into account 1-pixel border of the image (it’s filled with 0’s and used for neighbor analysis in the algorithm), therefore the contours touching the image border will be clipped.

它的意思是,findCountours 是不会包含1-pixel的边界的。所以这就是为啥opencv计算的结果中边界的1都消失的原因。我想,既然边界不被包含,那么我给它上下左右加一个1-pixel的框,这样边界点就变成了内部点,就能成功的用findCountours了。于是乎:我写了下面的code:

C++ Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
 
#include 
#include 
#include

using namespace std;
using namespace cv;

void my_imfillholes_v2()
{
   //step 1: make a border
   Mat m(8, 8, CV_8UC1, data);
   Mat m_with_border;
   copyMakeBorder(m, m_with_border, 1, 1, 1, 1, BORDER_CONSTANT, Scalar());
   cout<<m_with_border<<endl;

//setp 2: find the contour fill holes
   vector > contours;
   vector hierarchy;
   findContours(m_with_border, contours, hierarchy, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);
   //
   // fill external contours
   // 
   if( !contours.empty() && !hierarchy.empty() )
   {
      for (int idx=0;idx < contours.size();idx++)
      {
         drawContours(m_with_border,contours,idx,Scalar::all(1),CV_FILLED,8);
      }
   }
   //cout<<m_with_border<<endl;
   //step 3: remove the border
   m_with_border = m_with_border.rowRange(Range(1, m_with_border.rows-1));
   //cout<<m_with_border<<endl;
   m_with_border = m_with_border.colRange(Range(1, m_with_border.cols-1));
   cout<<m_with_border<<endl;
}

void main()
{
   my_imfillholes_v2();
   system("pause");
}

先加一个全0的1-pixel的框,然后在findCountours填充,最后把框给去了。这样结果就完全和matlab中的imfill一致了。

result =

1     1     1     0     0     0     0     0

1     1     1     0     1     1     0     0

1     1     1     0     1     1     0     0

1     1     1     0     1     1     1     0

1     1     1     0     1     1     1     0

1     1     1     0     1     1     1     0

1     1     1     0     0     1     1     0

1     1     1     0     0     0     0     0

时间: 2024-12-15 01:51:30

实际比较filter2D和imfilter之间的关系的相关文章

深入Linux内核架构 - 内核之中数据结构之间的关系图 &amp; 设备驱动程序(转)

内核之中数据结构之间的关系图 设备驱动程序

Spring初学之bean之间的关系和bean的作用域

一.bean之间的关系 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.o

Spring学习--Bean 之间的关系

Bean 之间的关系:继承.依赖. Bean 继承: Spring 允许继承 bean 的配置 , 被继承的 bean 称为父 bean , 继承这个父 bean 的 bean 称为子 bean. 子 bean 从父 bean 中继承配置 , 包括 bean 的属性配置. 子 bean 也可以覆盖从父 bean 继承过来的配置. 父 bean 可以作为配置模板 , 也可以作为 bean 实例.若只想把父 bean 作为模板 , 可以设置 <bean> 的 abstract 属性为 true ,

Linux中的文件描述符与打开文件之间的关系

1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文件.链接文件和设备文件.文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符.程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误.如果此时去打开一个新的文件,它的文件描述符会是3.POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号

WEB组件之间的关系

WEB组件之间的关系: A:重定向的特点: 1:发生客户端 2:地址栏发生变化 3:两个WEB组件不共享request的数据. 服务端的方法:response.sendRedirect(); 服务端的方法:response.sendRedirect("相对路径");   相对路径 request.getContextpath+"路径" 客户端的方法: window.location.href='URL地址' B:请求转发: request.getRequestDis

oracle动态视图v$,v_$,gv$,gv_$与x$之间的关系

前言:在oracle运维的过程中,经常会使用到一些以V$开头的动态视图,比如V$session, 有一次偶然看到有人用V_$session, 初以为别人写错了,没想到desc v_$session以后能看到和v$session一样的结构,再以后又发现以gv$开头的视图等等.趁这次在一台Linux系统上装oracle的机会,终于弄清楚了这些动态视图与相应表之间的关系.这些都是由oracle自己管理的数据结构,得从v$fixed_table入手:[[email protected] admin]$

Unity3D 中 Generic 动画导入设置和 Root Motion 之间的关系

2条评论 Unity3D 的 Mecanim 动画系统可以直接复用 3DS MAX 中制作的动画文件中的位移,这个就是通过 applyRootMotion 来达成的,我们只需要在使用 Animator 控制动画播放的同时,设置 Animator 的 applyRootMotion 字段为 True 就 OK 了. 那么怎么来利用这个特性达成我们想要的一些效果呢?这个 applyRootMotion 到底指的是啥呢? ApplyRootMotion,从字面上理解来看,是『应用根节点的运动』,听起来

甲方、乙方、监理三者之间的关系及其在项目管理过程中的若干事项

1.甲方.乙方.监理三者之间的关系 甲方是工程项目的投资单位和受方单位,乙方是工程项目的承建单位和供方单位,监理是工程项目的监管单位和第三方单位.广义方面来讲,监理也属于乙方的范畴,是服务的提供者.从合同角度来讲,甲方与监理.甲方与乙方存在合同关系. 甲方与监理的合同关系:甲方根据合同委托和授权监理就工程项目的质量控制.进度控制.投资控制.信息管理.合同管理.安全管理.组织协调工作进行监理,监理在工程项目建设期间完成合同约定的上述工作内容. 甲方与乙方的合同关系:甲方根据合同委托和要求乙方就工程

面向对象中多个对象之间的关系

http://www.cnblogs.com/wing011203/archive/2012/06/23/2559223.html 当谈到面向对象的设计时,我们经常说面向对象是符合人们对现实世界的思维模式,即人们采用针对非程序设计领域存在的复杂问题的解决方式,来解决软件设计过程中各种错综复杂的关系.利用面向对象设计,特别是采用各种设计模式来解决问题时,会设计多个类,然后创建多个对象,这些对象,有些主要是数据模型,有些则是行为描述占主体.一个设计良好的类,应该是兼顾信息和行为,并且是高内聚.而不同