2019-06-25 02:07:02 JonNoah 阅读数 247

图像处理——去除图像上杂乱的干扰(庖丁解牛)

源码地址:图像处理——去除图像上杂乱的干扰

庖丁解牛

1. #include <opencv2/opencv.hpp>
2. #include <opencv2/highgui/highgui_c.h>
3. #include <iostream>

前三行是将库文件的头文件加载进来,头文件里包含着大量的声明,面向对象设计程序需要用到大量已经封装好的库函数,将头文件加载进来后才能有效调用这些库函数。其中,前两个是第三方库-计算机视觉开源库头文件,按理说包含第一个就可以,可是只包含第一个的话创建显示窗口的时候会报错,因此又加载了highgui模块,可能是我的环境变量搭建的有问题,但我不知道问题在哪。第三个是C++的标准输入输出流。

5. using namespace cv;
6. using namespace std;

声明使用标准命名空间,可不可以不声明标准命名空间?可以,但是,在后期编写程序的时候代码量写得繁琐,总是要写重复的东西。简单理解,命名空间就像一个屋子一样,里面有好多已经起好的名字,比如A屋里有张三李四王二麻子,要想准确的叫到A屋里的张三是不是要说:A屋里的张三过来一下。如果不加“A屋里的”限定 一下,是不是有可能别处的张三也跑过来了?如果不提前声明一下,是不是每次叫张三都需要带上“A屋里的”这几个字,是不是很麻烦?有一个简单的办法就是事先跟大家声明一下,我今天叫张三就是叫A屋里的张三,其他地方的张三不要过来,这样每次叫张三是不是就很简单了,叫一声张三他就跑过来了。

9. char filename[] = { 0 };

定义一个char(字符型,character的缩写)类型数组来存储用户输入的图像存储路径。关于变量的类型:1.变量就相当于一个容器,什么样的容器就装什么东西,比如说用麻袋去装水显然就不合适。一种类型的变量就存储一类数据。2.定义变量就是告诉编译器我这个类型的容器有多大,有多大就需要给分配多大的空间,分配的少了将来有东西来了(值传递过来)就装不下了。

11. int main(int argc, char** argv){}

主函数main(),在这里主函数中定义了两个形参,其实在vs中运行程序不定义这两个形参也可以。用户定义的函数是被主函数调用,而主函数是被编译器或者说系统调用的。第一个形式参数是整型,用来记录向主函数中传递命令的个数。例如,使用cmd指令时,输入notepad后回车,系统只会自动打开记事本,相当于此时的argc=1,告诉主函数只传递过来了一个指令,以防把一个指令错读、拆分成两个指令或其他。在这里插入图片描述
输入notepad && explorer后系统会打开记事本和资源管理器这两个应用程序,相当于此时的argc=2,告诉主函数传递过来了两个指令。在这里插入图片描述
第二个参数char** argv 等价于char* argv[],即定义了一个指针数组,并且是char类型的。指针数组也是数组,不是指针。如果没有* 则argv存储的是字符,即一个字符数组,由于* 的优先级高,将argv 指定为指针数组,即存放字符或字符串地址的数组,例如在上例中,argv[0]存储的是notepad这个字符串的地址,argv[1]存储的是explorerd的地址,这样在访问时便可以通过地址来访问,访问效率高,计算开支小。

13. Mat src, dest, gray, binary;

定义了四个Mat类型的变量,分别表示源图像source、目标图像destination、灰度图像、二值图像。Mat类是OpenCV中定义好的数据结构类型(如果没有前面的命名空间声明此句需要写成cv::Mat src, dest, gray, binary;即用两个冒号运算符告诉编译器这个Mat是cv中定义的Mat),被称之为基本图像容器(早期版本的OpenCV中没有这一结构类型)。类是在c++中引入的概念,可以与c语言中的结构体进行一个对比,但是类里面的内容更加丰富。

13. cout << "----------------------------------------------------------------" << endl << endl;
14. cout << "Please enter a image path as follows and press Enter." << endl << endl;
15. cout << "Eg: F:/cvpicture/flower.png" << endl << endl;
16.cout << "ps: Image preview will automatically turn off after 30 seconds!" << endl << endl;
17. cout << "----------------------------------------------------------------" << endl;
18. cout << "Please enter the path:";

使用C++中的标准输出流cout向屏幕输出提示信息,<<可以理解为信息的流向。endl是C++中的标准换行符,相当于c语言中的\n,这样写的好处是保证了代码在不同系统平台间的良好迁移性,在windows平台下的换行符是\r\n,在Linux平台下的换行符是\n,切记换行符是系统平台特征,而不是某一语言的特征。

20. cin >> filename;

使用标准输入流向变量中输入内容,cin是C++中的标准输入流,能实现C语言中scanf的功能,但比scanf的功能更加强大一些。(ps:在C++中scanf是非标准库函数,如果想要使用scanf需要这样来写scanf_s()。)

//调用imread()函数读取图像
22. src = imread(filename);
定义:CV_EXPORTS_W Mat imread( const String& filename, int flags = IMREAD_COLOR );
/** @brief Loads an image from a file.
@anchor imread
The function imread loads an image from the specified file and returns it. If the 
image cannot be read (because of missing file, improper permissions, unsupported or invalid format), the function returns an empty matrix ( Mat::data==NULL ).
Currently, the following file formats are supported:
-   Windows bitmaps - \*.bmp, \*.dib (always supported)
-   JPEG files - \*.jpeg, \*.jpg, \*.jpe (see the *Note* section)
-   JPEG 2000 files - \*.jp2 (see the *Note* section)
-   Portable Network Graphics - \*.png (see the *Note* section)
-   WebP - \*.webp (see the *Note* section)
-   Portable image format - \*.pbm, \*.pgm, \*.ppm \*.pxm, \*.pnm (always 
supported)
-   PFM files - \*.pfm (see the *Note* section)
-   Sun rasters - \*.sr, \*.ras (always supported)
-   TIFF files - \*.tiff, \*.tif (see the *Note* section)
-   OpenEXR Image files - \*.exr (see the *Note* section)
-   Radiance HDR - \*.hdr, \*.pic (always supported)
-   Raster and Vector geospatial data supported by GDAL (see the *Note* section)
@note
-   The function determines the type of an image by the content, not by the file 
extension.
-   In the case of color images, the decoded images will have the channels stored 
in **B G R** order.
-   When using IMREAD_GRAYSCALE, the codec's internal grayscale conversion will be used, if available.
Results may differ to the output of cvtColor()
-   On Microsoft Windows\* OS and MacOSX\*, the codecs shipped with an OpenCV 
image (libjpeg, libpng, libtiff, and libjasper) are used by default. So, OpenCV can always read JPEGs, PNGs,and TIFFs. On MacOSX, there is also an option to use native MacOSX image readers. But beware that currently these native image loaders give images with different pixel values because of the color management embedded into MacOSX.
-   On Linux\*, BSD flavors and other Unix-like open-source operating systems, 
OpenCV looks for codecs supplied with an OS image. Install the relevant packages (do not forget the development files, for example, "libjpeg-dev", in Debian\* and Ubuntu\*) to get the codec support or turn on the OPENCV_BUILD_3RDPARTY_LIBS flag in CMake.
-   In the case you set *WITH_GDAL* flag to true in CMake and @ref 
IMREAD_LOAD_GDAL to load the image, then the [GDAL](http://www.gdal.org) driver will be used in order to decode the image, supporting the following formats: [Raster](http://www.gdal.org/formats_list.html),[Vector](http://www.gdal.org/ogr_formats.html).
-   If EXIF information are embedded in the image file, the EXIF orientation will be taken into account and thus the image will be rotated accordingly except if the flag @ref 
IMREAD_IGNORE_ORIENTATION is passed.
-   Use the IMREAD_UNCHANGED flag to keep the floating point values from PFM 
image.
-   By default number of pixels must be less than 2^30. Limit can be set using 
system variable OPENCV_IO_MAX_IMAGE_PIXELS
@param filename Name of file to be loaded.
@param flags Flag that can take values of cv::ImreadModes
*/

调用cv中的库函数(或称API接口)读入图像。根据函数的定义可以看到函数imread()的返回值是Mat类,所以定义了对应变量src来存储函数的返回值。im是image的缩写形式,这个函数有两个形参,翻译完定义后对其剖析一下。
接下来翻译一下看定义说了些什么(水平有限,不到之处欢迎交流):

简介:从一个文件中载入一幅图像

@anchor imread

  • 函数从指定的文件中载入一幅图像并将其返回,如果图像不能被读取(由于文件缺失,未得到许可,没有支持或有效的格式),该函数返回一个空的矩阵(Mat::data==NULL Mat的对象被赋值为空)。
    当前,支持以下文件类型:
    -Windows 位图:*.bmp, *.dib(无条件支持)
    -JPEG 文件:*.jpeg, *.jpg, *.jpe (看后面的注释部分)
    -JPEG 2000 文件:*.jp2(看后面的注释部分)
    -便携网络图片:*.png (看后面的注释部分)
    -WebP :*.webp(看后面的注释部分)
    -便携图像格式:*.pbm, *.pgm, *.ppm *.pxm, *.pnm (无条件支持)
    -PEM 文件:*.pfm(看后面的注释部分)
    -Sun rasters :*.sr, *.ras(无条件支持)
    -TIFF 文件:*.tiff, *.tif(看后面的注释部分)
    -OpenEXR 图像文件:*.exr (看后面的注释部分)
    -Radiance HDR :*.hdr, *.pic(无条件支持)
    -GDAL支持的格栅和矢量地理空间数据

注释:

  • -函数是通过内容判定一幅图像的类型,而不是通过文件扩展名。
  • -对于彩色图像,解过码的图像将按照B G R的通道顺序存储。
  • -使用IMREAD_GRAYSCALE(灰度模式读入)时,如果编解码器内部的灰度转换可以使用,则使用该模块将图像转化为灰度图像。该结果可能和使用cvtColor()函数输出的结果存在差异。
  • -在 Microsoft Windows* OS 和 MacOSX*这两个平台上,默认使用由OpenCV映像传送来的编解码器(libjpeg, libpng, libtiff, and libjasper)。因此,OpenCV总是可以读取JPEGs,PNGs,和 TIFFs格式的图像。在 MacOSX 平台上,也可以选择使用MacOSX自身的图像读取器。但是,需要注意的是:当前由于颜色管理器嵌入在MacOSX中,所以MacOSX中的图像加载器会给图像们赋上不同的值。
  • -在Linux*, BSD flavors以及其他像Unix一样的开源操作系统上,OpenCV使用由操作系统映像提供的编解码器。安装相关的安装包(在Debian* 和 Ubuntu*不要忘了开发文件,例如,“libjpeg-dev”)以得到编解码器的支持,或者在CMake中打开OPENCV_BUILD_3RDPARTY_LIBS flag。
  • -如果你在CMake中把 * WITH_GDAL * 标识设置成真,并且用@ref IMREAD_LOAD_GDAL去加载图像,那么GDAL(http://www.gdal.org)
    驱动器将被用来解码图像,支持下列格式:Raster,Vector.
  • -如果EXIF信息被嵌入到图像文件,EXIF方向信息将被考虑在内。因此,除非标识 @ref IMREAD_IGNORE_ORIENTATION已经通过,否则图像将被相应的旋转。
  • -使用IMREAD_UNCHANGED标识以保持PFM图像中的浮点值不发生改变。
  • -像素的默认值必须低于2^30,使用系统变量OPENCV_IO_MAX_IMAGE_PIXELS可以设置这一限制。

参数filename是被载入文件的名字。

  • (说的是函数的第一个形参,param是parameter(参数、参量)的缩写,para-前缀,旁边、辅助的意思,meter 词根测量,辅助性测量的东西——参数,没有参数怎么知道测量什么呢。)

参数flags是种标识,该标识取读取模式的值。

  • (falgs是枚举类型,枚举可以理解为给一个东西起一个暗号(专业名称叫:标识符),比如说有一伙人打算去盗窃,事先跟负责放哨的人约定,看见警察过来了喊001,看见保安过来了喊002,看见有路人过来了喊003。这样子一来一是传递信息变得简单了,二是传递信息也更加安全了,试想一下,如果看到警察来了就喊:警察来了、警察来了,那还有逃跑的机会吗?上去不就按住了。还有“警察来了”这四个字也没有001这个数字发声快吧,如果这一伙贼机智的选了一个结巴的人去放哨,喊警察来了,估计第二声还没喊出来手铐就戴上了吧。(ps:c语言中枚举本质就是整型,意思是编译器会自动给枚举标识符按排列顺序从0开始编码,枚举变量可以用任意整型赋值。而c++中枚举变量,只能用被枚举出来的元素初始化。也就是说C语言中的枚举变量可以用整型数字初始化,但是C++中的枚举变量只能用标识符初始化。))
  • Imread flags
    enum ImreadModes {
    IMREAD_UNCHANGED = -1,如果设置成-1,则按加载的图像格式返回图像(使用alpha通道,否则会被剪裁)
    IMREAD_GRAYSCALE = 0, 如果设置成0,则无条件将加载的图像转换为单通道的灰度图像(编解码器内部转换)。
    IMREAD_COLOR = 1, 如果设置成1,则无条件将图像转化为3通道BGR的彩色图像。
    IMREAD_ANYDEPTH = 2, 如果设置成2,则返回16位或32位图像(当输入图像有响相应的深度时),否则转换成8位图像。
    IMREAD_ANYCOLOR = 4, 如果设置成4(哈哈,为什么没有3呢?),图像以任意可能的颜色格式读入。
    IMREAD_LOAD_GDAL = 8, 如果设置成8,则使用GDAL驱动器加载图像。
    IMREAD_REDUCED_GRAYSCALE_2 = 16, 如果设置成16,则无条件把图像转换为单通道的灰度图像并且将图像的尺寸缩减一半。
    IMREAD_REDUCED_COLOR_2 = 17, 如果设置成17,则无条件将图像转换为三通道BGR的彩色图像,并将图像尺寸缩减一半。
    IMREAD_REDUCED_GRAYSCALE_4 = 32,如果设置成32,则无条件将图像转换为单通道灰度图像,并将图像尺寸减小1/4。
    IMREAD_REDUCED_COLOR_4 = 33, 如果设置成33,则无条件将图像转换为三通道BGR彩色图像,并将图像尺寸减小1/4。
    IMREAD_REDUCED_GRAYSCALE_8 = 64, 如果设置成64,则无条件将图像转换为单通道灰度图像,并将图像尺寸减小1/8。
    IMREAD_REDUCED_COLOR_8 = 65, 如果设置成65,则无条件将图像转换为3通道BGR彩色图像,并将图像尺寸减小1/8。
    IMREAD_IGNORE_ORIENTATION = 128 ,如果设置成128,则不能根据EXIF的方向标识旋转图像。 };

第一个形参:const string& filename

  • 在C和C++中都没有字符串变量,在C中存放字符串就像上面一样定义一个字符数组,在C++中可以用string类来声明一个字符串变量存储字符串,因此char filename[] = {0} 也可以写成 string filename = {0},
  • const是常量修饰符,const修饰谁表示对谁进行保护,其他操作不能对修饰的对象进行修改(C语言中仍可通过地址操作修改,所以说C语言中的const是冒牌的const.)。
  • string& filename在C++是引用,引用可以理解为是给一个变量取了一个别名。例如:int a = 10;int& test_a = a;则test_a就是整型变量a的别名,对别名进行操作就可以改变a的值,test_a = 50;则运行完这句后a的值就由原来的10变成50了。为什么操作别名就能改变a的值呢?在使用方面完全可以理解为别名就是一个名字,其实质还是变量本身。比如说:班里面的张三长得非常好看,大家给她取了个别名叫班花,你见到张三后夸她说:吆,班花你今天穿的正漂亮啊。你虽然没有叫张三的名字,但是张三知道是在夸她。如果要深究其原理,引用实际上可以理解是一个常指针,在执行语句int& test_a = a; 和 test_a = 50;时编译器会讲隐藏的运算符展开为:int * const test_a = &a; * test_a = 50;这样就一目了然。C++中引入引用的最大目的可能是将引用作为形参,以简化主函数的实参向被调用函数形参的传递。在没有引用的时候要通过被调用函数修改主调函数中的实参值需要使用指针进行地址传递,有了引用可以直接将实参传递给被调用函数,被调用函数对引用的修改就是对实参的修改。
  • 由以上分析const string& filename 可以展开为 const string * const filename,既保护指针的指向不可改变,也保护指针所指向的内容也不可改变。这也符合程序的要求,不能说将磁盘中的图像传进来后程序随意对源图像进行修改,若是这样,程序运行完后源图像就被破坏了。程序修改的是源图像的副本src。
  • 那么只看定义怎么知道这个形参的位置应该输入或传递过来什么形式的值呢?这就回到了引用的使用,在使用的时候就不要考虑这个指针那个指针了的,很容易搞蒙圈,直接来,引用就是别名,const string&不用看,这是给编译器看的,直接看filename,意思是让输入或传递过来一个读入的文件名,string还是要看一看,要不然不知道是什么类型(打脸了),只有文件名就让程序来读入目标显然是太为难程序了,在上百G的硬盘里找到一张图片,臣妾不是杀毒软件,臣妾做不到啊,所以是保存文件的路径名。

第二个形参:int flags = IMREAD_COLOR

  • 枚举类型,不再赘述,可以空着不填表示默认。
24. //判断是否读取成功
25. while (!src.data)
26. 	{
27. 		cout << "The path of image is error,and could not load image..." << endl;
28. 		cout << "Please enter the path:";
29. 		cin >> filename;
30. 		src = imread(filename);
31. 	}

使用while()循环来判断图像是否读入成功,如果未读入成功则输出提示信息继续读入。

  • while循环的执行是条件为真则继续执行循环,若条件为假则结束该循环,往下执行程序的其他语句。
  • src.data,注意“.”运算符, src是一个Mat类的变量,data是该类里面的一个对象。data存储的是图像左上角第一个(第0行,第0列)元素(像素值)的地址。
  • !是非运算符。
    如果src.data不为空,则说明读取到了图像,!src.data为非真——假,条件为假不执行while循环。如果src.data为空,则说明未读取到图像,!src.data为非假——真,条件为真执行while循环。
32. namedWindow("input image", 1);//创建窗口以显示读取的图像
定义:CV_EXPORTS_W void namedWindow(const String& winname, int flags = WINDOW_AUTOSIZE);
/** @brief Creates a window.
The function namedWindow creates a window that can be used as a placeholder for 
images and trackbars. Created windows are referred to by their names.
If a window with the same name already exists, the function does nothing.
You can call cv::destroyWindow or cv::destroyAllWindows to close the window and 
de-allocate any associated memory usage. For a simple program, you do not really have to call these functions because all the resources and windows of the application are closed automatically by the operating system upon exit.
@note
Qt backend supports additional flags:
-   **WINDOW_NORMAL or WINDOW_AUTOSIZE:** WINDOW_NORMAL enables you to resize the window, whereas WINDOW_AUTOSIZE adjusts automatically the window size to fit the displayed image (see imshow ), and you cannot change the window size manually.
-   **WINDOW_FREERATIO or WINDOW_KEEPRATIO:** WINDOW_FREERATIO adjusts the image with no respect to its ratio, whereas WINDOW_KEEPRATIO keeps the image ratio.
-   **WINDOW_GUI_NORMAL or WINDOW_GUI_EXPANDED:** WINDOW_GUI_NORMAL is the old way to draw the window without statusbar and toolbar, whereas WINDOW_GUI_EXPANDED is a new enhanced GUI.By default, flags == WINDOW_AUTOSIZE | WINDOW_KEEPRATIO | WINDOW_GUI_EXPANDED
@param winname Name of the window in the window caption that may be used as a window identifier.
@param flags Flags of the window. The supported flags are: (cv::WindowFlags)
//! Flags for cv::namedWindow
enum WindowFlags {
       WINDOW_NORMAL     = 0x00000000, //!< the user can resize the window (no 
constraint) / also use to switch a fullscreen window to a normal size.
       WINDOW_AUTOSIZE   = 0x00000001, //!< the user cannot resize the window, the size is constrainted by the image displayed.
       WINDOW_OPENGL     = 0x00001000, //!< window with opengl support.
       WINDOW_FULLSCREEN = 1,          //!< change the window to fullscreen.
       WINDOW_FREERATIO  = 0x00000100, //!< the image expends as much as it can (no ratio constraint).
       WINDOW_KEEPRATIO  = 0x00000000, //!< the ratio of the image is respected.
       WINDOW_GUI_EXPANDED=0x00000000, //!< status bar and tool bar
       WINDOW_GUI_NORMAL = 0x00000010, //!< old fashious way };
 */

调用库函数创建图像显示窗口。这个函数有两个形参,翻译完定义后对其剖析一下。
接下来翻译一下看定义说了些什么(水平有限,不到之处欢迎交流):
简介:创建一个窗口。

  • 函数创建一个能够用来作为图像和进度条的占位符窗口。创建的窗口与它们的名字关联,如果已经存在相同名字的窗口,那么这个函数将不执行任何操作。可以调用cv::destroyWindow 或者 cv::destroyAllWindows 来关闭这个窗口并且释放与之相关联的所有内存空间。对一个简单的程序来说,确实没有必要调用这些函数,因为应用程序的所有资源和窗口会由操作系统在退出时自动地关闭。

注释:

  • Qt后端支持的额外标识:
    -WINDOW_NORMAL 使用户可以重新设定窗口的大小,然而,WINDOW_AUTOSIZE
    自动调整窗口的大小以适合显示出来的图像(see imshow ),并且用户不能人为地改变窗口的大小。
    -WINDOW_FREERATIO 不考虑图像的比率调整图像,然而,WINDOW_KEEPRATIO 保持图像的比率不变。
    -WINDOW_GUI_NORMAL 是一种没有状态栏和工具栏的老旧的创建窗口的方法,然而,WINDOW_GUI_EXPANDED 是一种新增强的GUI。

默认状态下,flags == WINDOW_AUTOSIZE | WINDOW_KEEPRATIO | WINDOW_GUI_EXPANDED

参数1:窗口名 窗口标题中窗口的名字,可以用作窗口的标识符。

  • 仍旧是引用作形参,传递或输入一个字符串就ok。

参数2:flags 窗口的标识。支持的标识有:(枚举)

  • WINDOW_NORMAL = 0x00000000, 用户可以重新调整窗口的大小(不受限制),也可以用来将一个全屏幕窗口转换成正常大小。
    WINDOW_AUTOSIZE = 0x00000001, 用户不能重新调整窗口的大小,窗口尺寸有显示的图像限制。
    WINDOW_OPENGL = 0x00001000, //!< window with opengl support.
    WINDOW_FULLSCREEN = 1, 将窗口调整为全屏。
    WINDOW_FREERATIO = 0x00000100, 图像尽可能多的开销图像(没有比率限制)
    WINDOW_KEEPRATIO = 0x00000000,图像的比率受到保护。
    WINDOW_GUI_EXPANDED=0x00000000, 状态栏和工具栏。
    WINDOW_GUI_NORMAL = 0x00000010, 一种老方法。
33. imshow("input image", src);//显示图像
定义:CV_EXPORTS_W void imshow(const String& winname, InputArray mat);
/** @brief Displays an image in the specified window.
The function imshow displays an image in the specified window. If the window was 
created with the
cv::WINDOW_AUTOSIZE flag, the image is shown with its original size, however it is 
still limited by the screen resolution.
Otherwise, the image is scaled to fit the window. The function may scale the 
image, depending on its depth:
-   If the image is 8-bit unsigned, it is displayed as is.
-   If the image is 16-bit unsigned or 32-bit integer, the pixels are divided by 
256. That is, the value range [0,255\*256] is mapped to [0,255].
-   If the image is 32-bit or 64-bit floating-point, the pixel values are 
multiplied by 255. That is, the value range [0,1] is mapped to [0,255].
If window was created with OpenGL support, cv::imshow also support ogl::Buffer , 
ogl::Texture2D and cuda::GpuMat as input.
If the window was not created before this function, it is assumed creating a 
window with cv::WINDOW_AUTOSIZE.
If you need to show an image that is bigger than the screen resolution, you will 
need to call namedWindow("", WINDOW_NORMAL) before the imshow.
@note 
This function should be followed by cv::waitKey function which displays the 
image for specified milliseconds. Otherwise, it won't display the image. For example, **waitKey(0)** will display the window infinitely until any keypress (it is suitable for image display). **waitKey(25)** will display a frame for 25 ms, after which display will be automatically closed. (If you put it in a loop to read videos, it will display the video frame-by-frame)
@note
[__Windows Backend Only__] Pressing Ctrl+C will copy the image to the clipboard.
[__Windows Backend Only__] Pressing Ctrl+S will show a dialog to save the image.
@param winname Name of the window.
@param mat Image to be shown.
*/

调用API,将图像显示在指定窗口。
首先先翻译一下定义,看定义说了些什么(水平有限,不到之处请指正)。
简介:在特定的窗口中展示一幅图像。

  • imshow函数在特定的窗口中展示一幅图像。如果窗口是用cv::WINDOW_AUTOSIZE标识创建的,那么图像显示出其原始尺寸,但是图像显示仍然受到屏幕分辨率的限制。否则,图像调整到适合窗口大小。该函数可以根据图像自身的深度调整图像的规格。
    -如果图像是无符号8位图像,则按原样显示。
    -如果图像是无符号16位或者整型32位,则像素值除以256。也就是说,将像素值范围[0,255*256]映射到[0,255].
    -如果图像是浮点型32位或者64位,则像素值乘以255.也就是说,将像素值范围由[0,1]映射到[0,255].
    如果由OpenGL支持创建,则imshow函数也支持ogl::Buffer , ogl::Texture2D 和cuda::GpuMat作为输入.
    如果在该函数前没有创建窗口,则函数默认创建一个cv::WINDOW_AUTOSIZE标识的窗口。
    如果显示的图像的分辨率比屏幕的分辨率大,则将需要在imread函数前调用namedWindow("", WINDOW_NORMAL)。

注释:

  • 该函数后应该跟随一个cv::waitKey函数,它显示了图像显示的时间,以毫秒为单位。否则,imread函数将不会显示图像。例如:waitKey(0)表示显示窗口将一直存在,直到有键盘任何按键输入。waitKey(25)表示显示时间位25毫秒,之后,显示窗口将被自动地关闭。(如果把它放在循环中读取视频,它将按照时间逐帧显示视频)

注意:

  • 仅Windows后台:按Ctrl+C后会将图像复制到剪切板。
    仅Windows后台:按Ctrl+S后将会显示一个保存该图像的对话框。

参数winname 窗口的名称。

  • 参数1仍然是用引用作为形参,传入或写入字符串常量即可(需要是窗口名称)

参数mat 将被显示的图像。

  • 参数2是Mat类变量(在此处即为读入的图像),传入对应实参即可。

由定义可以看出该函数返回值为空,即函数只是执行相关操作,不向主函数返回值。

36. if (3 == src.channels())
	{
		cvtColor(src, gray, CV_BGR2GRAY);
	}
	else
	{
		gray = src;
43.    }
  1. 判断读入图像像素的通道数,如果是3通道图像,则调用API cvtColor将图像转换为单通道灰度图像。否则将源图像复制给变量gray(表示灰度图像)。

  2. 3 == src.channels(),首先常规写法为src.channels()==3 ,即判断左值知否等于右值。这样写的缺点是万一中间的相等符号少写一个等号就变成了赋值语句了,例如:int a = 9; if (a = 3){},在这种情况下编译器并不会报错,若是在一个庞大的程序中因为这样程序出现问题时将非常难排除。但是采用3 == src.channels()这样的写法将可以有效避免此种情况的发生,因为这样写即使少些了一个等号编译器也会报错,一个常量不能够作为左值被赋值。在此处使用两种方法都可以,通过测试可以知道src.channels()也是一个常量,采用常规的写法即使少写了一个等号也会报错提示。

  3. src.channels(),src是Mat类型的变量,channels()是类中的对象,channels()也称内联函数。内联函数是由宏函数进化而来。
    inline int Mat::channels() const {return CV_MAT_CN(flags);}
    为什么要有宏函数呢?比如在下面这种情况下:

void my_printf(int a, int b)
{
	printf("a= %d,b= %d",&a,&b);
}
int main ()
{
	int a = 0;
	int b = 1000;
	
	for(i=0;i<1000;i++)
	{
		my_printf(a,b);
		a++;
		b--;
	}
	return 0;
}

在这个程序中执行for循环时需要调用1000次my_printf()这个函数,参数也需要传递1000次,这就带来了很大的计算开销。面对此种情况,宏函数应运而生,下面是将被调用函数定义为宏函数形式。

#define MY_PRINTF(int a, int b)\
{\
	printf("a= %d,b= %d",&a,&b);\
}

程序里面所有的宏都是在预处理阶段进行展开,定义成宏函数后,在预处理阶段编译器将被调用函数在调用处展开,这样就不需要重复调用函数了。有了宏函数为什么还要有内联函数呢?原因是:宏是在预处理阶段展开,并不对展开的内容进行语法检查,这给程序的安全埋下了伏笔,而内联函数是在编译阶段展开,编译器对展开内容进行语法检查。宏函数和内联函数都是以牺牲代码空间换取代码的运行效率,因此被封装的函数体不能过大和复杂。编译器会自动检测内联函数,若内联函数内有复杂的嵌套或者循环,则inline声明失效。

暂时还不明白const {return CV_MAT_CN(flags)的含义,但是可以肯定的是channels()的返回值是常量。

38. cvtColor (src, gray, CV_BGR2GRAY);
定义:CV_EXPORTS_W void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 );
/** @brief Converts an image from one color space to another.
The function converts an input image from one color space to another. In case of a 
transformation to-from RGB color space, the order of the channels should be specified explicitly (RGB or BGR). Note that the default color format in OpenCV is often referred to as RGB but it is actually BGR (the bytes are reversed). So the first byte in a standard (24-bit) color image will be an 8-bit Blue component, the second byte will be Green, and the third byte will be Red. The fourth, fifth, and sixth bytes would then be the second pixel (Blue, then Green, then Red), and so on.
The conventional ranges for R, G, and B channel values are:
-   0 to 255 for CV_8U images
-   0 to 65535 for CV_16U images
-   0 to 1 for CV_32F images
In case of linear transformations, the range does not matter. But in case of a non-linear transformation, an input RGB image should be normalized to the proper value range to get the correct results, for example, for RGB \f$\rightarrow\f$ L\*u\*v\* transformation. For example, if you have a 32-bit floating-point image directly converted from an 8-bit image without any scaling, then it will have the 0..255 value range instead of 0..1 assumed by the function. So, before calling #cvtColor ,you need first to scale the image down:
@code
    img *= 1./255;
    cvtColor(img, img, COLOR_BGR2Luv);
@endcode
If you use #cvtColor with 8-bit images, the conversion will have some information 
lost. For many applications, this will not be noticeable but it is recommended to use 32-bit images in applications that need the full range of colors or that convert an image before an operation and then convert back.
If conversion adds the alpha channel, its value will set to the maximum of 
corresponding channel range: 255 for CV_8U, 65535 for CV_16U, 1 for CV_32F.
@param src input image: 8-bit unsigned, 16-bit unsigned ( CV_16UC... ), or 
single-precision floating-point.
@param dst output image of the same size and depth as src.
@param code color space conversion code (see #ColorConversionCodes).
@param dstCn number of channels in the destination image; if the parameter is 0, 
the number of the channels is derived automatically from src and code.
@see @ref imgproc_color_conversions
 */

简介:将图像的颜色空间从一个转换到另一个。

  • 该函数将输入图像的颜色空间转换到另一个颜色空间。对于RGB颜色空间的转换,通道顺序应该进行精确的划分(RGB or BGR)。注意,在OpenCV中默认的颜色空间格式是经常提到的RGB,而不是真是的BGR(字节是相反的)。因此,在一个标准24位彩色图像中,第一个字节将是一个8位的蓝色成份,第二字节是绿色,第三字节是红色。第四、五六字节将是下一个像素(Blue, then Green, then Red),以此类推。
  • R, G, 和 B 通道值的转换范围是:
    -对于8位无符号整型图像是0-255
    -对于16位无符号整型图像是0-65535
    -对于32位浮点型图像是0-1
    在线性变换的情况下,像素值的范围无关紧要。但是对于非线性变换,一幅RGB图像的像素值应该标准化到恰当的值域以获得正确的结果,例如,对于RGB转换。例如,如果将8位整型图像不加任何缩放的转换为32位浮点图像,那么图像像素将具有[0,255]的值域而不是函数假定的[0,1]。因此,在调用cvtColor函数之前,首先需要缩小图像。代码:
    img * = 1./255;
    cvtColor(img, img, COLOR_BGR2Luv);
  • 如果使用cvtColor对一个8位整型图像进行转换则将会丢失一些信息。对于许多应用而言,这一丢失将不被注意到,但是建议在需要全部颜色或在操作之前转换图像然后转换回来的应用程序中使用32位图像。
  • 如果转换添加了alpha通道,则其值将设置为相应通道的最大值:对于CV_8U是255, 对于CV_16U是65535, 对于CV_32F是1.

参数src 输入图像:无符号8位,无符号16位( CV_16UC… ),或者单精度浮点
参数dst 输出图像,和输入图像有着同样的尺寸和深度。
参数code 颜色空间代码。
参数dstCn 目标图像的通道数,如果参数是0,则图像的通道数来自于源图像和代码

  • 参数1是输入图像
  • 参数2是输出图像
  • 参数3是转换模式,例如CV_BGR2GRAY,将BGR三通道图像转换为灰度图像。
  • 参数4是输出图像的通道数,通常不填默认为0,即通道数来源于源图像。
45. adaptiveThreshold(~gray, binary, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -1);
定义:CV_EXPORTS_W void adaptiveThreshold( InputArray src, OutputArray dst,
double maxValue, int adaptiveMethod,int thresholdType, int blockSize, double C );
/** @brief Applies an adaptive threshold to an array.
The function transforms a grayscale image to a binary image according to the 
formulae:
-   **THRESH_BINARY**
    \f[dst(x,y) =  \fork{\texttt{maxValue}}{if \(src(x,y) > 
T(x,y)\)}{0}{otherwise}\f]
-   **THRESH_BINARY_INV**
    \f[dst(x,y) =  \fork{0}{if \(src(x,y) > 
T(x,y)\)}{\texttt{maxValue}}{otherwise}\f]
where \f$T(x,y)\f$ is a threshold calculated individually for each pixel (see 
adaptiveMethod parameter).
The function can process the image in-place.
@param src Source 8-bit single-channel image.
@param dst Destination image of the same size and the same type as src.
@param maxValue Non-zero value assigned to the pixels for which the condition is 
satisfied
@param adaptiveMethod Adaptive thresholding algorithm to use, see 
#AdaptiveThresholdTypes.
The #BORDER_REPLICATE | #BORDER_ISOLATED is used to process boundaries.
@param thresholdType Thresholding type that must be either #THRESH_BINARY or 
#THRESH_BINARY_INV,
see #ThresholdTypes.
@param blockSize Size of a pixel neighborhood that is used to calculate a 
threshold value for the pixel: 3, 5, 7, and so on.
@param C Constant subtracted from the mean or weighted mean (see the details 
below). Normally, it is positive but may be zero or negative as well.
@sa  threshold, blur, GaussianBlur 阈值化,模糊,高斯模糊
 */

调用API 自适应阈值化,目的是将灰度图像二值化。因为接下来使用形态学开操作(即先腐蚀后膨胀),形态学操作是在二值化的图像上进行的。
简介:对一个数组使用一个自适应阈值。实际意思是对图像进行阈值化操作,图像的像素值以数组的方式进行存储。
该函数按照下列公式将一幅灰度图像转换成二值图像:

  • THRESH_BINARY
    \f[dst(x,y) = \fork{\texttt{maxValue}}{if (src(x,y) > T(x,y))}{0}{otherwise}\f]
    如果图像某点的像素值大于设定值则取为0,否则保留原值。(反阈值化为0)
    THRESH_BINARY_INV
    \f[dst(x,y) = \fork{0}{if (src(x,y) > T(x,y))}{\texttt{maxValue}}{otherwise}\f]
    如果图像某点的像素值大于设定值则取为设定值,否则保留原值。(截断阈值化操作)
    该函数可以就地处理图像。

参数1:src 8位单通道原始图像。
参数2:dst 与源图像拥有相同大小和类型的目的图像。
参数3:maxValue 分配给像素的非零值,以满足操作条件。
参数4:adaptiveMethod 使用的自适应阈值化算法,参见AdaptiveThresholdTypes。BORDER_REPLICATE,BORDER_ISOLATED用来处理边界。
参数5:thresholdType 阈值化类型,必须是THRESH_BINARY(二值化)或者THRESH_BINARY_INV(反二值化),参见ThresholdTypes.
参数6:blockSize 一个像素邻域的大小,用来计算像素的阈值,通常为奇数3,5,7…
参数7:C 从平均值或者加权平均值中减去的常量(详情参见如下)(未见详情)。通常情况下应该是正值,但是也可能
为0或负值。
细心的你可能在第一个参数处发现了“~”这个符号,这个符号是取反的意思,即把灰度图像取反(这也与形态学开操作有关,也与二值化不会自动取反有关)。为什么要取反呢?下面是一幅灰度图像:在这里插入图片描述
为了便于理解姑且用“+”表示白色的像素点,用“-”表示暗色的像素点,灰度图像简化示意图如下:
在这里插入图片描述
取反后的简化示意图为:
在这里插入图片描述
再对取反后的图像二值化,+变亮,-变黑,是不是就把原来灰度图像中杂乱的黑线变白,背景变黑了?下面就是二值化后的图像。
在这里插入图片描述
果不其然,背景变成了黑色,杂乱的干扰变成了白色(这对开操作非常有用)。

47. //定义一个掩模
48. 	Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
getStructuringElement()定义:CV_EXPORTS_W Mat getStructuringElement(int shape, Size ksize, Point anchor = Point(-1,-1));
/** @brief Returns a structuring element of the specified size and shape for 
morphological operations.
The function constructs and returns the structuring element that can be further 
passed to #erode,#dilate or #morphologyEx. But you can also construct an arbitrary binary mask yourself and use it as the structuring element.
@param shape Element shape that could be one of #MorphShapes
@param ksize Size of the structuring element.
@param anchor Anchor position within the element. The default value \f$(-1, -1)\f$ 
means that the anchor is at the center. Note that only the shape of a cross-shaped element depends on the anchor position. In other cases the anchor just regulates how much the result of the morphological operation is shifted.
 */

调用API getStructuringElement()创建一个掩模,根据其定义可以看出该函数的返回值是Mat类型,所以定义了一个Mat类变量kernel来接收。
简介:函数返回指定大小和形状的结构元素,用于形态操作。

  • 该函数创建并返回一个结构元素,它可以被用来进一步的传递给函数erode,dilate 和 morphologyEx.

参数1:shape 元素的形状,可以是MorphShapes中之一(有MORPH_RECT = 0 矩形,MORPH_CROSS = 1十字形,MORPH_ELLIPSE = 2 椭圆)。
参数2:ksize 结构元素的尺寸。
参数3:anchor 结构元素中的锚点。默认值是(-1, -1),表示锚点位于结构元素中央。需要注意的是,只有十字形结构元素的形状取决于锚点的位置。在其他情况下,锚只调节形态操作的结果移位了多少。

49. morphologyEx(binary, dest, CV_MOP_OPEN, kernel);//调用形态学操作函数
定义:CV_EXPORTS_W void morphologyEx( InputArray src, OutputArray dst, int op, InputArray kernel,Point anchor = Point(-1,-1), int iterations = 1,int borderType = BORDER_CONSTANT,const Scalar& borderValue =morphologyDefaultBorderValue() );
/** @brief Performs advanced morphological transformations.
The function cv::morphologyEx can perform advanced morphological transformations 
using an erosion and dilation as basic operations.
Any of the operations can be done in-place. In case of multi-channel images, each 
channel is processed independently.
@param src Source image. The number of channels can be arbitrary. The depth should be one of CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
@param dst Destination image of the same size and type as source image.
@param op Type of a morphological operation, see #MorphTypes
@param kernel Structuring element. It can be created using #getStructuringElement.
@param anchor Anchor position with the kernel. Negative values mean that the 
anchor is at the kernel center.
@param iterations Number of times erosion and dilation are applied.
@param borderType Pixel extrapolation method, see #BorderTypes
@param borderValue Border value in case of a constant border. The default value 
has a special meaning.
@sa  dilate, erode, getStructuringElement
@note The number of iterations is the number of times erosion or dilatation 
operation will be applied.
For instance, an opening operation (#MORPH_OPEN) with two iterations is equivalent to apply
successively: erode -> erode -> dilate -> dilate (and not erode -> dilate -> erode 
-> dilate).
 */

调用形态学操作函数生成目标函数(即去除图像的干扰信息)。
简介:执行高级形态转换。

  • 函数cv::morphologyEx使用腐蚀和膨胀作为基础操作以实现执行高级的形态转换。(这也是该函数的原理,在后面作以讲解。)任何操作都可以就地执行。对于多通道图像,每一个通道独立处理。

参数1:src 源图像。图像的通道数可以是任意的。图像深度应该是一下中的一个:CV_8U, CV_16U, CV_16S, CV_32F or CV_64F.
参数2:dst 和原始图像拥有相同大小和类型的目标图像。
参数3:op 形态学操作的类型,参见MorphTypes。(程序中选的是开操作)

  • enum MorphTypes{
    MORPH_ERODE = 0, 腐蚀操作
    MORPH_DILATE = 1, 膨胀操作
    MORPH_OPEN = 2, 开操作(先腐蚀后膨胀)(由此可知我们这个程序不调用morphologyEx(),使用腐蚀和膨胀
    函数也可以达到目的)
    MORPH_CLOSE = 3, 闭操作(先膨胀后腐蚀)
    MORPH_GRADIENT = 4, 形态学梯度梯度(膨胀减去腐蚀)
    MORPH_TOPHAT = 5, 顶帽(源图像减去开操作后的图像)
    MORPH_BLACKHAT = 6, 黑帽(闭操作后的图像减去源图像)
    MORPH_HITMISS = 7, (暂不知晓)
    };

参数4:kernel 结构元素。可以使用函数getStructuringElement()来创建。
参数5:anchor 结构元素的锚点。负值表示锚点在结构元素正中间。(因为在创建结构元素时已经指定了锚点,所以该参数可以空着不写,表示使用结构元素中的锚点。)
参数6:iterations(迭代) 负腐蚀和膨胀被使用的次数。
参数7:borderType(边界类型) 创建图像边界的方法,参见BorderTypes(这个宏定义没有多大作用,就是按照某个规则给图像四周创建上边界,运算的时候不要越界。像素值存储的时候是没有边界的,不创建边界函数不知道运算到什么位置停止。可以空着不写,使用默认边界创建方式)。
参数8:borderValue(边界上的像素值)固定边界的边界值。默认值有着特殊含义。(空着不写使用默认值)
@sa dilate, erode, getStructuringElement 膨胀 腐蚀 创建结构元素
注意:迭代的次数是腐蚀或膨胀操作被使用的次数。例如,具有两次迭代的形态学开操等价于erode -> erode -> dilate -> dilate (而不是 erode -> dilate -> erode -> dilate)。

腐蚀是操作可以理解为让黑色位置感染白色位置,从而扩张自己的领域(就像细菌繁殖),下图是一张腐蚀前的二值图像:
在这里插入图片描述
下面是腐蚀后的二值图像:
在这里插入图片描述
可以明显的看到通过黑色的“感染”,白色的细线和小点都被感染成黑色了,字母也被感染的比以前纤瘦了。
膨胀操作可以理解为让白色空间胀大,从而挤兑黑色空间。如果直接对我们的二值图像膨胀会怎么样?看下图:
在这里插入图片描述
是不是白色的区域更大了?之所以对腐蚀后的图像再进行膨胀就是在消除了细小的干扰后再把纤瘦的区域变回原来大小。如图:
在这里插入图片描述
图像中的abcde比上上张图中的丰满多了吧。
问题来了通过这样的操作可以消除所有类型的图像中的干扰吗?答案可能会让你感到有些失望,不能。仔细观察一下这个实例会发现:图像中主要信息的尺寸和干扰信息的尺寸相差的比较大,这样一来在执行腐蚀操作时只会腐蚀掉小尺寸的干扰,而大尺寸的信息并不会被完全腐蚀掉,然后再用膨胀将主要信息还原成原来大小。不过我们在今后的学习中可以寻找其他的方法消除其他类型的干扰。

50. bitwise_not(dest, dest);//反转背景颜色
定义:CV_EXPORTS_W void bitwise_not(InputArray src, OutputArray dst, InputArray mask = noArray());
/** @brief  Inverts every bit of an array.
The function cv::bitwise_not calculates per-element bit-wise inversion of the 
input array:
\f[\texttt{dst} (I) =  \neg \texttt{src} (I)\f]
In case of a floating-point input array, its machine-specific bit representation (usually IEEE754-compliant) is used for the operation. In case of multi-channel arrays, each channel is processed independently.
@param src input array.
@param dst output array that has the same size and type as the input array.
@param mask optional operation mask, 8-bit single channel array, that specifies elements of the output array to be changed.
*/

调用bitwise_not(),将图像的背景颜色与字母颜色互相调换,以保持与源图像相同的背景。
简介:反转数组的每一位。

  • 函数cv::bitwise_not()计算输入数组的每个元素位的反转。对于输入的浮点数组,其使用机器特定位表示(通常是IEEE754-compliant)进行操作。对于输入的对通道数组,每一个通道被独立的处理。

参数1:src 输入数组。
参数2:dst 和输入数组具有相同尺寸和类型的输出数组。
参数3:mask(掩码)可选操作掩码,8位单通道数组,指定要更改的输出数组的元素。
bitwise_not(dest, dest),dest是变量,第一个作为输入,第二个作为输出,此处不够严谨,可再定义一个Mat类变量来作为输出。

51. namedWindow("dest image", 1);//创建目标图像显示窗口
52 	imshow("dest image", dest);//显示图像

创建目标图像显示窗口,显示图像

53. waitKey(3000);//显示窗口停留时间30s
定义:CV_EXPORTS_W int waitKey(int delay = 0);
/** @brief Waits for a pressed key.
The function waitKey waits for a key event infinitely (when \f$\texttt{delay}\leq 
0\f$ ) or for delay milliseconds, when it is positive. Since the OS has a minimum time between switching threads, the function will not wait exactly delay ms, it will wait at least delay ms, depending on what else is running on your computer at that time. It returns the code of the pressed key or 1 if no key was pressed before the specified time had elapsed.
@note
This function is the only method in HighGUI that can fetch and handle events, so 
it needs to be called periodically for normal event processing unless HighGUI is used within an environment that takes care of event processing.
@note
The function only works if there is at least one HighGUI window created and the 
window is active. If there are several HighGUI windows, any of them can be active.
@param delay Delay in milliseconds. 0 is the special value that means "forever".
 */

显示窗口停留时间30s已让用户预览效果然后选择是否保存。
简介:等待键盘输入。

  • 函数waitKey无限时间等待键盘输入或者当输入正值时按毫秒延迟。由于OS在切换线程之间时间最短,因此不会真正的延迟那么多毫秒,函数将至少延迟这么多秒,具体取决于此时电脑中还运行着哪些额外的程序。

注意:

  • 该函数是HighGUI中唯一能够获取和处理事件的方法,因此,为了正常处理事件它需要被提前调用,除非在负责事件处理的环境中使用HighGUI。该功能仅在至少创建一个HighGUI窗口且窗口处于活动状态时才有效。如果有多个HighGUI窗口,则其中任何一个都可以处于活动状态。

参数:delay 以毫秒延迟。0是一个意味着无限延迟的特殊值。
这里存在着不足,功能缺陷是:用户不能自己选择何时关闭图像。程序缺陷是:1.不能捕获鼠标操作事件,如何判断鼠标点击了显示窗口上的X按钮?2.如果是由前边一路读过来就会发现,在图像显示的30毫秒内按下Ctrl+S就可以保存图像了不需要执行后边的程序了。

54. cvDestroyWindow("dest image");//释放显示窗口
55. 	cvDestroyWindow("input image");
定义:CVAPI(void) cvDestroyWindow( const char* name );
/* destroy window and all the trackers associated with it */
销毁窗口和与之关联的进度条

调用cvDestroyWindow()释放创建的显示窗口。这也是一个缺陷点,只有显示窗口被释放后才能回到程序运行窗口继续往下执行程序。

>//输出提示信息。
57. cout << "--------------------------------------------------------" << endl << endl;
	cout << "Tips:Do you want to save the processed image?" << endl << endl;
	cout << "Save press:y    Don't save press:n" << endl << endl;
	cout << "--------------------------------------------------------" << endl;
61.	cout << "Please enter:";
//定义字符型变量存储用户输入信息
63.	 char c;
64. 	cin >> c;
//使用while循环判断用户输入是否正确,不正确则重新输入
65. while (c != 'y' && c != 'n')
	{
		cout << endl;
		cout << "Sorry,input is error,please re-enter:" << endl;
		cin >> c;
		cout << endl;
71. 	}
//如果输入的是y,则提示用户输入保存路径。
72. if (c== 'y')
	{
		cout << "Please enter the saved path:";
		cin >> filename;
		imwrite(filename, dest);
		cout << endl;
		cout << "Image saved successfully!" << endl;
79. 	}
定义:CV_EXPORTS_W bool imwrite( const String& filename, InputArray img, const std::vector<int>& params = std::vector<int>());
/** @brief Saves an image to a specified file.
The function imwrite saves the image to the specified file. The image format is 
chosen based on the ilename extension (see cv::imread for the list of extensions). In general, only 8-bit single-channel or 3-channel (with 'BGR' channel order) images can be saved using this function, with these exceptions:
- 16-bit unsigned (CV_16U) images can be saved in the case of PNG, JPEG 2000, and 
TIFF formats
- 32-bit float (CV_32F) images can be saved in PFM, TIFF, OpenEXR, and Radiance 
HDR formats;
 3-channel (CV_32FC3) TIFF images will be saved using the LogLuv high dynamic 
range encoding (4 bytes per pixel)
- PNG images with an alpha channel can be saved using this function. To do this, 
create 8-bit (or 16-bit) 4-channel image BGRA, where the alpha channel goes last. Fully transparent pixels should have alpha set to 0, fully opaque pixels should have alpha set to 255/65535 (see the code sample below).
If the format, depth or channel order is different, use Mat::convertTo and cv::cvtColor to convert it before saving. Or, use the universal FileStorage I/O functions to save the image to XML or YAML format.
The sample below shows how to create a BGRA image and save it to a PNG file. It 
also demonstrates how to set custom compression parameters:
@include snippets/imgcodecs_imwrite.cpp
@param filename Name of the file
@param img Image to be saved.
@param params Format-specific parameters encoded as pairs (paramId_1,paramValue_1, paramId_2, paramValue_2, ... .) see cv::ImwriteFlags
*/

这段程序存在bug,当用户输入的路径不存在,或者路径格式不正确,或者保存图像的格式不正确时,程序会意外结束。原因是因为程序没有实现对输入路径的正确性进行判断。
简介:保存一个图像指定的文件夹。

  • 函数imwrite保存图像到指定的文件夹。图像的格式根据拓展名选择(参见cv::imread 拓展名清单 )
    总的来说,只有8位单通道或者3通道(BGR通道顺序)图像能够使用该函数保存。下面这些例外:
    -16位无符号图像在PNG, JPEG 2000, 和 TIFF 格式下可以被保存。
    -32位浮点型图像在PFM, TIFF, OpenEXR, 和 Radiance HDR格式下可以被保存。
    -3通道TIFF图像将使用LogLuv高动态范围编码(每像素4个字节)保存。
    -有alpha通道的PNG图像可以使用该函数保存。为此,创建8位(或16位)4通道图像BGRA,其中alpha通道最后。 完全透明的像素应该将alpha设置为0,完全不透明的像素应该将alpha设置为255/65535(请参阅下面的代码示例)。

如果格式,深度或通道顺序不同,请在保存之前使用Mat :: convertTo和cv :: cvtColor进行转换。 或者,使用通用FileStorage I / O函数将图像保存为XML或YAML格式。

下面的示例显示了如何创建BGRA图像并将其保存为PNG文件。 它还演示了如何设置自定义压缩参数

  • @include snippets/imgcodecs_imwrite.cpp
    @param filename Name of the file.
    @param img Image to be saved.
    @param params Format-specific parameters encoded as pairs (paramId_1, paramValue_1, paramId_2, paramValue_2, … .) see cv::ImwriteFlags
    编码为对的格式特定参数(paramId_1,paramValue_1,paramId_2,paramValue_2,…)请参阅cv :: ImwriteFlags
//如果用户输入的是n则结束程序
80.else if (c == 'n')
	{
		cout << endl;
		cout << "Unsaved!" << endl;
		return 0;
85. 	}
86.	return 0;
87. }//程序结束。
2009-01-15 11:11:00 gisfarmer 阅读数 1336

C#个性化图像处理(附代码)

开源从出现的那天起就注定一直要被关注下去. 一些非常优秀的软件我们使用起来游刃有余,但是我们对它的运行机制或者源码了解很少; 这样的结果是当我们使用的越好, 对其中的运行机制了解的欲望就越少;其实一个资深的编程人员, 不应该过于迷恋于别人开发好的软件或组件, 这会使我们对更深层的原理知道的机会减少. 这里不是否定大家去使用现成的优秀的软件或组件, 而是至少应该明白其中的一些原理或运行机制, 然后再去使用效果会更好一些;因为我们可以明白的使用, 更可以明白的修改, 让优秀的软件或组件成为我们开发的工具真正为我们服务而不是让它们奴役我们的有限精力与激情,不要让它们成为让我们失去上进心的罪魁祸首。
其实我非常不赞成选用哪种语言开发哪个系统比较占优势, 哪个语言对于哪些问题的解决更得心应手, 这会让我们一方面花大量的时间与精力学好几门我们不一定喜欢(也许是非常抵触)的语言, 一方面养成了我们不去解决困难问题习惯, 我们更多的是找借口: "这种技术需要某某语言才能实现,我无法完成这个任务...!". 我想要说的是, 如果认为哪个技术非得要哪种特定的语言来实现, 只能说明我们的 "处世不深".
乔峰没有段誉的六脉神剑, 没有全真,玉女合壁的玉女素心剑, 没有梅超风的毒龙鞭法, 那些玄, 奇,  怪, 异武功他一样不会, 但他能成为武林第一除了有包容世 界的胸怀外, 更重要的是他把降龙十八掌运用的如火纯青。其实任何武功如果能掌握它的命门要术, 得其精髓, 都会天下无敌!
开源当然好, 如果不给开源呢? 没有关系, 任何语言, 只要功夫到家, 我们一样可以懂得任何软件或组件的运行机制, 只要时机成熟, 我们同样可以制作符合自己个性的软件或组件;因此别再抱怨你不会 java , 不会 C++, 不会 C#, 我要说的是, 你哪怕只会一个简单的 vb, 简单的 c, 照样可以制作您需要的东西.
语言没有最好的, 适合自己的就是最好的, 运用到如火纯青便是最好的.
PhotoShop 是一款最流行的图像处理软件, 已经相当成熟, 其中对图像显示效果的处理能力更牛B;用起来美得嘴里直流油, 遗憾的人家就是不开源, 没办法, 自已动手做一个? 呵呵, "一切皆有可能!"
这里我用 C# 制作了一款个性的图像处理软件, 列出主要功能的关键代码部分, 以示对开源支持.

一. 以底片效果显示图像

原理: 底片中的每个像素点颜色值都是正常图像对应像素点颜色值的取反( 满色为255).

( 关于颜色的相关技术请参考其他资料, 此处不补课 )

1.通过 GetPixel 方法获得图像中每一点像素的值,

2.再使用 SetPixel 方法将取反后的颜色值设置到对应的点...


try
{
int Height = this.pictureBox1.Image.Height;
int Width = this.pictureBox1.Image.Width;
Bitmap newbitmap
= new Bitmap(Width, Height);
Bitmap oldbitmap
= (Bitmap)this.pictureBox1.Image;
Color pixel;
for (int x = 1; x < Width; x++)
{
for (int y = 1; y < Height; y++)
{
int r, g, b;
pixel
= oldbitmap.GetPixel(x, y);
r
= 255 - pixel.R;
g
= 255 - pixel.G;
b
= 255 - pixel.B;
newbitmap.SetPixel(x, y, Color.FromArgb(r, g, b));
}
}
this.pictureBox1.Image = newbitmap;
}

二. 以浮雕效果显示图像

原理: 通过对图像像素点的像素值与相邻像素点的像素值相减后加上128, 然后作为新的像素点的值...


try
{
int Height = this.pictureBox1.Image.Height;
int Width = this.pictureBox1.Image.Width;
Bitmap newBitmap
= new Bitmap(Width, Height);
Bitmap oldBitmap
= (Bitmap)this.pictureBox1.Image;
Color pixel1, pixel2;
for (int x = 0; x < Width - 1; x++)
{
for (int y = 0; y < Height - 1; y++)
{
int r = 0, g = 0, b = 0;
pixel1
= oldBitmap.GetPixel(x, y);
pixel2
= oldBitmap.GetPixel(x + 1, y + 1);
r
= Math.Abs(pixel1.R - pixel2.R + 128);
g
= Math.Abs(pixel1.G - pixel2.G + 128);
b
= Math.Abs(pixel1.B - pixel2.B + 128);
if (r > 255)
r
= 255;
if (r < 0)
r
= 0;
if (g > 255)
g
= 255;
if (g < 0)
g
= 0;
if (b > 255)
b
= 255;
if (b < 0)
b
= 0;
newBitmap.SetPixel(x, y, Color.FromArgb(r, g, b));
}
}
this.pictureBox1.Image = newBitmap;
}

三. 以黑白效果显示图像

原理:  彩色图像处理成黑白效果时常用的三种方法. 1.最大值法, 2.平均值法, 3.加权平均值法..

         平均值法为例: 是取每一个像素点颜色值的 红,绿, 蓝. 相加除以 3 做为该像素点新的颜色值

         即:  ((R + G + B) / 3) .

注: 每种方法产生黑白效果的最终效果不一样,  加权平均值法产生的黑白效果最 "真实" . 


try
{
int Height = this.pictureBox1.Image.Height;
int Width = this.pictureBox1.Image.Width;
Bitmap newBitmap
= new Bitmap(Width, Height);
Bitmap oldBitmap
= (Bitmap)this.pictureBox1.Image;
Color pixel;
for (int x = 0; x < Width; x++)
for (int y = 0; y < Height; y++)
{
pixel
= oldBitmap.GetPixel(x, y);
int r, g, b, Result = 0;
r
= pixel.R;
g
= pixel.G;
b
= pixel.B;
//实例程序以加权平均值法产生黑白图像
int iType = 2 ;
switch (iType)
{
case 0://平均值法
Result = ((r + g + b) / 3);
break;
case 1://最大值法
Result = r > g ? r : g;
Result
= Result > b ? Result : b;
break;
case 2://加权平均值法
Result = ((int)(0.7 * r) + (int)(0.2 * g) + (int)(0.1 * b));
break;
}
newBitmap.SetPixel(x, y, Color.FromArgb(Result, Result, Result));
}
this.pictureBox1.Image = newBitmap;
}

四. 以柔化效果显示图像

原理: 当前像素点与周围像素点的颜色差距较大时取其平均值 ...算法:  高斯模板


try
{
int Height = this.pictureBox1.Image.Height;
int Width = this.pictureBox1.Image.Width;
Bitmap bitmap
= new Bitmap(Width, Height);
Bitmap MyBitmap
= (Bitmap)this.pictureBox1.Image;
Color pixel;
//高斯模板
int[] Gauss ={ 1, 2, 1, 2, 4, 2, 1, 2, 1 };
for (int x = 1; x < Width - 1; x++)
for (int y = 1; y < Height - 1; y++)
{
int r = 0, g = 0, b = 0;
int Index = 0;
for (int col = -1; col <= 1; col++)
for (int row = -1; row <= 1; row++)
{
pixel
= MyBitmap.GetPixel(x + row, y + col);
r
+= pixel.R * Gauss[Index];
g
+= pixel.G * Gauss[Index];
b
+= pixel.B * Gauss[Index];
Index
++;
}
r
/= 16;
g
/= 16;
b
/= 16;
//处理颜色值溢出
r = r > 255 ? 255 : r;
r
= r < 0 ? 0 : r;
g
= g > 255 ? 255 : g;
g
= g < 0 ? 0 : g;
b
= b > 255 ? 255 : b;
b
= b < 0 ? 0 : b;
bitmap.SetPixel(x
- 1, y - 1, Color.FromArgb(r, g, b));
}
this.pictureBox1.Image = bitmap;
}

五. 以锐化效果显示图像

原理:  获取图像有关形体的边缘, 并突出显示.


try
{
int Height = this.pictureBox1.Image.Height;
int Width = this.pictureBox1.Image.Width;
newBitmap
= new Bitmap(Width, Height);
// Bitmap newBitmap = new Bitmap(Width, Height);
Bitmap oldBitmap = (Bitmap)this.pictureBox1.Image;
Color pixel;
//拉普拉斯模板
int[] Laplacian ={ -1, -1, -1, -1, 9, -1, -1, -1, -1 };
for (int x = 1; x < Width - 1; x++)
for (int y = 1; y < Height - 1; y++)
{
int r = 0, g = 0, b = 0;
int Index = 0;
for (int col = -1; col <= 1; col++)
for (int row = -1; row <= 1; row++)
{
pixel
= oldBitmap.GetPixel(x + row, y + col); r += pixel.R * Laplacian[Index];
g
+= pixel.G * Laplacian[Index];
b
+= pixel.B * Laplacian[Index];
Index
++;
}
//处理颜色值溢出
r = r > 255 ? 255 : r;
r
= r < 0 ? 0 : r;
g
= g > 255 ? 255 : g;
g
= g < 0 ? 0 : g;
b
= b > 255 ? 255 : b;
b
= b < 0 ? 0 : b;
newBitmap.SetPixel(x
- 1, y - 1, Color.FromArgb(r, g, b));
}
this.pictureBox1.Image = newBitmap;
}

六. 以雾化效果显示图像

理: 图像的雾化处理不是基于图像中像素点之间的计算,而是给图像像素的颜色值引入一定的随机值, 使图像具有毛玻璃带水雾般的效果..


try
{
int Height = this.pictureBox1.Image.Height;
int Width = this.pictureBox1.Image.Width;
Bitmap newBitmap
= new Bitmap(Width, Height);
Bitmap oldBitmap
= (Bitmap)this.pictureBox1.Image;
Color pixel;
for (int x = 1; x < Width - 1; x++)
for (int y = 1; y < Height - 1; y++)
{
System.Random MyRandom
= new Random();
int k = MyRandom.Next(123456);
//像素块大小
int dx = x + k % 19;
int dy = y + k % 19;
if (dx >= Width)
dx
= Width - 1;
if (dy >= Height)
dy
= Height - 1;
pixel
= oldBitmap.GetPixel(dx, dy);
newBitmap.SetPixel(x, y, pixel);
}
this.pictureBox1.Image = newBitmap;
}

七. 以光照效果显示图像

原理: 按照一定的规则对图像中某范围内像素的亮度进行处理后, 能够产生类似光照的效果...


Graphics MyGraphics = this.pictureBox1.CreateGraphics();
MyGraphics.Clear(Color.White);
Bitmap MyBmp
= new Bitmap(this.pictureBox1.Image, this.pictureBox1.Width, this.pictureBox1.Height);
int MyWidth = MyBmp.Width;
int MyHeight = MyBmp.Height;
Bitmap MyImage
= MyBmp.Clone(new RectangleF(0, 0, MyWidth, MyHeight), System.Drawing.Imaging.PixelFormat.DontCare);
int A = Width / 2;
int B = Height / 2;
//MyCenter图片中心点,发亮此值会让强光中心发生偏移
Point MyCenter = new Point(MyWidth / 2, MyHeight / 2);
//R强光照射面的半径,即”光晕”
int R = Math.Min(MyWidth / 2, MyHeight / 2);
for (int i = MyWidth - 1; i >= 1; i--)
{
for (int j = MyHeight - 1; j >= 1; j--)
{
float MyLength = (float)Math.Sqrt(Math.Pow((i - MyCenter.X), 2) + Math.Pow((j - MyCenter.Y), 2));
//如果像素位于”光晕”之内
if (MyLength < R)
{
Color MyColor
= MyImage.GetPixel(i, j);
int r, g, b;
//220亮度增加常量,该值越大,光亮度越强
float MyPixel = 220.0f * (1.0f - MyLength / R);
r
= MyColor.R + (int)MyPixel;
r
= Math.Max(0, Math.Min(r, 255));
g
= MyColor.G + (int)MyPixel;
g
= Math.Max(0, Math.Min(g, 255));
b
= MyColor.B + (int)MyPixel;
b
= Math.Max(0, Math.Min(b, 255));
//将增亮后的像素值回写到位图
Color MyNewColor = Color.FromArgb(255, r, g, b);
MyImage.SetPixel(i, j, MyNewColor);
}
}
//重新绘制图片
MyGraphics.DrawImage(MyImage, new Rectangle(0, 0, MyWidth, MyHeight));
}

八. 以百叶效果显示图像

原理: 根据图像的高度或宽度和定制的百叶窗显示条宽度计算百叶窗显示的条目数量



try
{
MyBitmap
= (Bitmap)this.pictureBox1.Image.Clone();
int dw = MyBitmap.Width / 30;
int dh = MyBitmap.Height;
Graphics g
= this.pictureBox1.CreateGraphics();
g.Clear(Color.Gray);
Point[] MyPoint
= new Point[30];
for (int x = 0; x < 30; x++)
{
MyPoint[x].Y
= 0;
MyPoint[x].X
= x * dw;
}
Bitmap bitmap
= new Bitmap(MyBitmap.Width, MyBitmap.Height);
for (int i = 0; i < dw; i++)
{
for (int j = 0; j < 30; j++)
{
for (int k = 0; k < dh; k++)
{
bitmap.SetPixel(MyPoint[j].X
+ i, MyPoint[j].Y + k,
MyBitmap.GetPixel(MyPoint[j].X
+ i, MyPoint[j].Y + k));
}
}
this.pictureBox1.Refresh();
this.pictureBox1.Image = bitmap;
System.Threading.Thread.Sleep(
100);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message,
"信息提示");
}
(2)水平百叶窗显示图像关键代码

九. 马赛克效果显示图像

原理: 确定随机位置点和确定图像块的大小, 然后随机改变确定每一个图像块中像素的值...



try
{
int dw = MyBitmap.Width / 50;
int dh = MyBitmap.Height / 50;
Graphics g
= this.pictureBox1.CreateGraphics();
g.Clear(Color.Gray);
Point[] MyPoint
= new Point[2500];
for (int x = 0; x < 50; x++)
for (int y = 0; y < 50; y++)
{
MyPoint[x
* 50 + y].X = x * dw;
MyPoint[x
* 50 + y].Y = y * dh;
}
Bitmap bitmap
= new Bitmap(MyBitmap.Width, MyBitmap.Height);
for (int i = 0; i < 10000; i++)
{
System.Random MyRandom
= new Random();
int iPos = MyRandom.Next(2500);
for (int m = 0; m < dw; m++)
for (int n = 0; n < dh; n++)
{
bitmap.SetPixel(MyPoint[iPos].X
+ m, MyPoint[iPos].Y + n, MyBitmap.GetPixel(MyPoint[iPos].X + m, MyPoint[iPos].Y + n));
}
this.pictureBox1.Refresh();
this.pictureBox1.Image = bitmap;
}
for (int i = 0; i < 2500; i++)
for (int m = 0; m < dw; m++)
for (int n = 0; n < dh; n++)
{
bitmap.SetPixel(MyPoint[i].X
+ m, MyPoint[i].Y + n, MyBitmap.GetPixel(MyPoint[i].X + m, MyPoint[i].Y + n));
}
this.pictureBox1.Refresh();
this.pictureBox1.Image = bitmap;
}

十. 以油画效果显示图像

原理: 图像每个像素点所在的坐标值整除一个随机数, 记为iModel,然后将该点的RGB值设置成附近iModel点之内的任一点



Graphics g
= this.panel1.CreateGraphics();
//Bitmap bitmap = this.MyBitmap;
//取得图片尺寸
int width = MyBitmap.Width;
int height = MyBitmap.Height;
RectangleF rect
= new RectangleF(0, 0, width, height);
Bitmap img
= MyBitmap.Clone(rect, System.Drawing.Imaging.PixelFormat.DontCare);
//产生随机数序列
Random rnd = new Random();
//取不同的值决定油画效果的不同程度
int iModel = 2;
int i = width - iModel;
while (i > 1)
{
int j = height - iModel;
while (j > 1)
{
int iPos = rnd.Next(100000) % iModel;
//将该点的RGB值设置成附近iModel点之内的任一点
Color color = img.GetPixel(i + iPos, j + iPos);
img.SetPixel(i, j, color);
j
= j - 1;
}
i
= i - 1;
}
//重新绘制图像
g.Clear(Color.White);
g.DrawImage(img,
new Rectangle(0, 0, width, height));

十一. 以扭曲效果显示图像

原理: 将每一个像素的 RGB 值加上它自身的坐标值即可



Size offset
=new Size (w++,h++);//设置偏移量
Graphics g = panel1.CreateGraphics();
Rectangle rect
= this.panel1.ClientRectangle;
Point[] points
= new Point[3];
points[
0] = new Point(rect.Left+offset.Width ,rect.Top +offset .Height);
points[
1] = new Point(rect.Right, rect.Top + offset.Height);
points[
2] = new Point(rect.Left, rect.Bottom - offset.Height);
g.Clear(Color.White);
g.DrawImage(MyBitmap, points);

十二. 以积木效果显示图像
原理:  Color 类的 FromArgb 方法定义一组颜色, 然后使用 Bitmap 对象的 SetPixel 方法为图像中的各像素点重新着色..



try
{
Graphics myGraphics
= this.panel1.CreateGraphics ();
//Bitmap myBitmap = new Bitmap(this.BackgroundImage);
int myWidth, myHeight, i, j, iAvg, iPixel;
Color myColor, myNewColor;
RectangleF myRect;
myWidth
= MyBitmap.Width;
myHeight
= MyBitmap.Height;
myRect
= new RectangleF(0, 0, myWidth, myHeight);
Bitmap bitmap
= MyBitmap.Clone(myRect, System.Drawing.Imaging.PixelFormat.DontCare);
i
= 0;
while (i < myWidth - 1)
{
j
= 0;
while (j < myHeight - 1)
{
myColor
= bitmap.GetPixel(i, j);
iAvg
= (myColor.R + myColor.G + myColor.B) / 3;
iPixel
= 0;
if (iAvg >= 128)
iPixel
= 255;
else
iPixel
= 0;
myNewColor
= Color.FromArgb(255, iPixel, iPixel, iPixel);
bitmap.SetPixel(i, j, myNewColor);
j
= j + 1;
}
i
= i + 1;
}
myGraphics.Clear(Color.WhiteSmoke);
myGraphics.DrawImage(bitmap,
new Rectangle(0, 0, myWidth, myHeight));
}

说明: 其实图像显示效果无法一一枚举.这里只列出最常用的12 种效果关键代码部分.

       全部的代码演示会有后续补充.

       这个图像处理软件是我和几个朋友在工作之余所做. 这里只贴出部分功能,

       技术有限, 错误难免, 出错之处还望指出.

       制作这个软件没有什么特别的目的, 只是心血来潮, 只是觉得好玩.

转自:http://www.cnblogs.com/ziyiFly/archive/2008/09/12/1290037.html

2018-06-30 12:11:41 weixin_42578941 阅读数 3229


图像处理——去除拍摄电子屏幕时产生的彩色波纹

1.中值滤波

1.将所求像素点周围的8个像素点以及本身存于一个数组中,再分别建立三个数组,分别是numR[9]numG[9],numB[9]。用于分别存储前九个像素点的R,G,B值,再依次对numR[9]numG[9],numB[9]进行快速排序,取三个数组的中值作为所求像素点的新的RGB值。如图1.1,图1.2。

                                         图1.1 

均值滤波运算过程如下


                      图1.2

中值滤波GDI+代码如下

for (int x = 0; x < w - 1; x++)
	{
		for (int y = 0; y < h - 1; y++)
		{
int n = 0;
			for (int col = -2; col <= 2; col++)
			{
				for (int row = -2; row <= 2; row++)
				{
					src->GetPixel(x + col, y + row, &color[n]);
					numR[n] = color[n].GetR();
					numG[n] = color[n].GetG();
					numB[n] = color[n].GetB();
					n++;
				}
			}
			qsort(numR, 25, sizeof(numR[0]), cmp);
			qsort(numG, 25, sizeof(numG[0]), cmp);
			qsort(numB, 25, sizeof(numB[0]), cmp);
dest->SetPixel(x,y,Color(BYTE(numR[12]),BYTE(numG[12]),BYTE(numB[1])));
		}
	}
	graph->DrawImage(dest, w+2, 0, w, h);
int cmp(const void*a, const void*b)
{
	return*(int*)a - *(int*)b;
}

2.混合均值滤波

混合均值滤波是在均值滤波的基础上改进得来的一种算法,类似于混合中值滤波

 图2.1

 混合均值滤波GDI+算法如下

for (int x = 0; x < w - 1; x++)
	{
		for (int y = 0; y < h - 1; y++)
		{
			int n = 0;
			for (int col = -1; col <= 1; col++)
			{
				for (int row = -1; row <= 1; row++)
				{
					src->GetPixel(x + col, y + row, &color[n]);
					n++;
				}
			}
			total_numR[0] = (color[1].GetR() + color[3].GetR() + color[4].GetR() + color[5].GetR() + color[7].GetR()) / 5;
			total_numR[1] = (color[0].GetR() + color[2].GetR() + color[4].GetR() + color[6].GetR() + color[8].GetR()) / 5;
			total_numR[2] = (total_numR[0] + total_numR[1] + color[4].GetR()) / 3;
			total_numG[0] = (color[1].GetG() + color[3].GetG() + color[4].GetG() + color[5].GetG() + color[7].GetG()) / 5;
			total_numG[1] = (color[0].GetG() + color[2].GetG() + color[4].GetG() + color[6].GetG() + color[8].GetG()) / 5;
			total_numG[2] = (total_numG[0] + total_numG[1] + color[4].GetG()) / 3;
			total_numB[0] = (color[1].GetB() + color[3].GetB() + color[4].GetB() + color[5].GetB() + color[7].GetB()) / 5;
			total_numB[1] = (color[0].GetB() + color[2].GetB() + color[4].GetB() + color[6].GetB() + color[8].GetB()) / 5;
			total_numB[2] = (total_numB[0] + total_numB[1] + color[4].GetB()) / 3;
			dest->SetPixel(x, y, Color(BYTE(total_numR[2]+20), BYTE(total_numG[2]+20), BYTE(total_numB[2]+20)));
		}
}

混合均值滤波可以将图片的彩色波纹减弱,并对原图产生较小的模糊效果,而中值滤波可以去掉弱彩色波纹,所以两个算法一起使用会达到对彩色波纹取出的效果,但由于中值滤波特性,在将波纹去掉的同时,图片也可能有较大程度的模糊。



2019-05-31 17:45:29 WHU_StudentZhong 阅读数 716

     最近在做一个遥感图像处理的任务,觉得比较有意思,就拿出来跟大家分享一下。

     这次的任务是遥感图像的阴影提取,看上去好像有一点高大上的样子,让人有些摸不到头脑。我先到网上查找了一下,主要的方法都是用二值化,配合Canny算子或者Sobel算子之类的来提取阴影的面积,但是我觉得这样做比较复杂,而且效果也不一定很好。于是我就变了一个思路,来用阴影本身的特点来进行提取。

     首先要说明的是,我要处理的图像都是光照条件比较好的,所以阴影部分的亮度就会比其他区域的亮度明显低不少;另外,由于亮度的降低,导致其色调比较浅,所以R、G、B三个通道数值的方差肯定比较小。为了编写代码,我先做了一些尝试,最后确定了效果比较好的阈值,最后计算了阴影面积的比重,并显示在窗口的标题上。

         下面我们来看一下效果

                                               

                                                                                        原图

                                             

                                                                                    阴影图

           

                                              最终效果图                                                                    强力提取效果图

            从图中可以看到,最终的效果还是非常好的,目测估计准确率在90%以上(哈哈哈)

            下面是其他图片的处理效果

      无论是遥感影像还是生活照的效果都不错哦。

      下面就是代码部分了。(提醒:环境是VS2017+OPENCV4.1)

       大家有什么好的办法,记得和我讨论分享一下哦。(^_^)

#include "pch.h"
#include <iostream>
#include <opencv2/opencv.hpp>
#include <math.h>
using namespace std;
using namespace cv;

double varOf3(double x1, double x2, double x3) {
	double mean = (x1 + x2 + x3) / 3;
	return (pow((x1 - mean), 2) + pow((x2 - mean), 2) + pow((x3 - mean), 2));
}
Mat ShadowExtraction(Mat src,int type=1);

int main()
{
	//读取图片
	char filename[] = "Color4.bmp";
	Mat src = imread(filename);
	resize(src, src, Size(720, (720 * src.rows / src.cols)));//将图像的尺寸缩放到适合屏幕观看
	imshow("Previous", src);
	ShadowExtraction(src);
	return 0;
}

Mat ShadowExtraction(Mat src, int type ) {
	int rows = src.rows, cols = src.cols;
	Mat M(rows, cols, CV_8UC1);
	double* var = new double[rows*cols];
	int i = 0;
	for (int x = 0; x < cols; x++) {
		for (int y = 0; y < rows; y++) {
			if (((double)src.at<Vec3b>(y, x)[0] + (double)src.at<Vec3b>(y, x)[1] + (double)src.at<Vec3b>(y, x)[2]) < 250) {//限制亮度
				var[i] = sqrt(varOf3((double)src.at<Vec3b>(y, x)[0], (double)src.at<Vec3b>(y, x)[1], (double)src.at<Vec3b>(y, x)[2]));
			}
			else {
				var[i] = 255;
			}
			i++;
		}
	}
	int j = 0;
	for (int x = 0; x < cols; x++) {
		for (int y = 0; y < rows; y++) {
			M.at<uchar>(y, x) = (uchar)(var[j]);//把方差作为亮度进行赋值
			j++;
		}
	}
	//imshow("Before Binarization", M);
	switch (type) {
	case 1://轻度阴影提取
		equalizeHist(M, M);//可以过滤掉颜色比较浅的部分
		break;
	case 2://强力阴影提取
		break;
	}
	/*int block_size = 25;
	int const_value = 10;
	adaptiveThreshold(M, M, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, block_size, const_value);*/
	threshold(M, M, 70, 255, THRESH_BINARY_INV);//把图像二值化
	medianBlur(M, M, 7);//中值滤波,去除小斑点
	imshow("Shadow", M);
	Mat M3 = src.clone();//M3是最后阴影提取后的结果
	double count = 0.0;
	for (int x = 0; x < cols; x++) {//给识别出来的阴影上色
		for (int y = 0; y < rows; y++) {
			if (M.at<uchar>(y, x) == 255) {
				M3.at<Vec3b>(y, x)[0] = 255;
				M3.at<Vec3b>(y, x)[1] = 0;
				M3.at<Vec3b>(y, x)[2] = 0;
				count++;
			}
		}
	}
	char str1[64] = "Shadow in Previous";
	char str2[64];
	sprintf_s(str2, "  ||  Weight of Shadow:%.2lf%%", 100.0*count / rows / cols);//阴影量
	strcat_s(str1, str2);
	//erode(M3, M3, Mat());//腐蚀
	//dilate(M3, M3, Mat());//膨胀
	imshow(str1, M3);//显示最后处理结果的


	waitKey(0);

	delete[] var;
	return M3;
}

   

 

2017-10-24 20:07:49 piaoxuezhong 阅读数 6433

暗角的概念

暗角一词属于摄影术语,是指一幅图像的四周的亮度或饱和度相比于中间部分的降低,画面四角有变暗的现象。暗角对于任何相机设置或镜头都不可避免。当然有时会故意引入暗角这种效果。

产生暗角的原因

(1)边角的成像光线与镜头光轴有较大的夹角是主要原因。沿着视场边缘的光线的前进方向看光圈,由于光线与光圈所在的平面有夹角,看到的光圈是椭圆的,所以通光面积减小。镜头光心到胶片的边缘距离较大,同样的光圈直径到达底片的光线夹角较小,亮度必然减小。同理,同样的光线偏角,对于边角光线位移较大,等价于照在较大的面积上。而面积是与位移的平方成正比的,所以综合上述原因,边缘亮度与光线和光轴夹角的cos值的4次方成正比。换句话说,广角镜头的边缘亮度随着视角变大急剧下降。


(2)长焦镜头,尤其是变焦长焦镜头,镜片很多,偏离光圈比较远的镜片为了能让边角光线通过,这些镜片必须很大。为了降低成本,缩小了这些镜片直径,造成边角成像光线不能完全通过,降低了边角的亮度。
(3)边角的像差较大。为了提高像质,某些镜片的边缘或专门设置的光阑有意挡住部分影响成像质量的边缘光线,造成边角失光。

暗角的分类

(1)自然暗角:表现为照片四角平缓暗化的渐变效果,这主要是由不同位置的光照进相机传感器的角度不同造成的。这种类型的照片暗角在用广角镜头拍摄的时候最为明显。:
(2)光学暗角:渐变同样也很平缓,但是它形成的主要原因是由镜头的固有特性,或者由镜筒自身的阴影造成。光学暗角也最终决定了一个镜头的成像圈的尺寸。这种暗角多在使用大光圈的情况下出现,并且受特定的镜头设计的影响很大。
(3)机械暗角:通常非常突兀,且只存在于照片的四角,通常是由遮光斗、滤镜环或其他安置在镜头前的遮光器材造成的。这种暗角如果由大光圈镜头或者变焦镜头来拍的话渐变效果就会略微平缓一些,没有那么突兀,也可以使用长焦从而避免这种效果。

po几张带暗角效果的原始图像:



去暗角算法

去阴影的算法有多种,例如参考【7,8】里的方法,本文主要讨论基于熵的去暗角算法,在论文《Retrospective shading correction based on entropy minimization》【1】中介绍了基于熵最小化的阴影校正(暗角自然也是一种阴影形式)方法,另外论文【2】《Single-Image Vignetting Correction by Constrained Minimization of log-Intensity Entropy》进一步讨论了论文【1】中方法的局限,主要是指局部最优的问题,并提出了一种基于对数熵的方法,这里大概讲述一下论文【2】中方法的思路,另见参考[4]。其主要内容有三部分:一是关于对数熵的评价准则;二是阴影去除方法的建模;三是模型参数的优化,使得熵值最小。

(1)对数熵

关于熵的概念,有点抽象,在维基里有很详细的解释,请参见:https://en.wikipedia.org/wiki/Entropy,这里只po一下连续随机变量的信息熵公式:

,

其中,X为图像灰度的分布,f(x)为概率密度函数,当X乘以一个参数c时,熵变为:


当c>1时,ln|c|>0.

(2)暗角建模

首先将灰度进行对数映射:

即将[0,255]的像素值基于对数关系映射到[0, N-1]内,通常N取256,这样映射后的像素范围还是[0,255]。映射后的直方图为:

那两个数学符号了,是向上取整和向下取整的意思,作和是一种线性权重取值方式。此时的对数熵直方图由于巨大的色阶调整,会出现直方图信息缺失,需要进行高斯平滑得到新的直方图。经过高斯平滑后的离散熵为:

论文给出的阴影函数反函数,也称增益函数g 为:

, g的范围为:1<g<1

其中,(x,y)是图像点坐标,x,y均值表示图像的中心位置。可以看到,当r=0时,校正系数=1,即无需校正。当r=1时,校正系数为1+a+b+c。经过暗角校正后的图像就为:


图像从暗角的中心点到四周是逐渐变暗的,函数g是随着r单调递增的,因此函数g的一阶导数大于0,即:

,由r>0,可以得到:

,令q2=r,那么转换为:

,方程解为:

,根据g在所属范围内的单调递增特性,推出C1~C5,并保证了r=0和1时,g>0,并且在范围0~1内,g不等于0;


(3)参数最优化

在第二部分,建立暗角模型后,问题便转化为一个最优化问题,最优条件是调参使得对数熵最小。文中介绍的求最优方法是爬山法:即从一个随机的初始解开始,逐步找到一个最优解。 因为模型中有多个参数,通过爬山法逐步获得最优解的过程中可以依次分别将某个参数的值增加或者减少一个单位。

算法实现:(待完成)

参考:

  1. https://en.wikipedia.org/wiki/Vignetting
  2. http://blog.csdn.net/omade/article/details/17449471
  3. http://www.xinpianchang.com/e881
  4. http://www.cnblogs.com/Imageshop/p/6166394.html
  5. http://blog.csdn.net/grafx/article/details/68958815
  6. 《Single-Image Vignetting Correction by Constrained Minimization of log-Intensity Entropy》[J].IEEE
  7. 《Single-Image Vignetting Correction》[J].IEEE
  8. 《Single-Image Vignetting Correction Using Radial Gradient Symmetry》[J].IEEE
没有更多推荐了,返回首页