图像处理 背景纹理去除_图像处理 纹理去除 - CSDN

• 可将图像转到频域内处理掉这种波纹。FFT后：滤波器：处理后的频域图像：逆变换：消除了垂直方向上的干扰。get_image_size (Image1, Width1, Height1) gen_rectangle1 (Rectangle1, 0, Width1/2-2, Height1/2-10, ...
上图在垂直方向上存在一定频率的干扰波纹。可将图像转到频域内处理掉这种波纹。FFT后：滤波器：处理后的频域图像：逆变换：消除了垂直方向上的干扰。get_image_size (Image1, Width1, Height1)
gen_rectangle1 (Rectangle1, 0, Width1/2-2, Height1/2-10, Width1/2+2)
gen_rectangle1 (Rectangle2, Height1/2+10, Width1/2-2, Height1, Width1/2+2)
union2 (Rectangle1, Rectangle2, RegionUnion)
fft_generic (Image1, ImageFFT2, 'to_freq', -1, 'sqrt', 'dc_center', 'complex')
paint_region (RegionUnion, ImageFFT2, ImageResult, 0, 'fill')
fft_generic (ImageResult, ImageFFT3, 'from_freq', 1, 'sqrt', 'dc_center', 'byte')
展开全文
• 源码 #include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui_c.h> #include <iostream> using namespace cv; using namespace std; //定义全局变量存储路径信息 ...int ma...
源码
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui_c.h>
#include <iostream>

using namespace cv;
using namespace std;

//定义全局变量存储路径信息
char filename[] = { 0 };

int main(int argc, char** argv)
{
Mat src, dest, gray, binary;
cout << "----------------------------------------------------------------" << endl << endl;
cout << "Please enter a image path as follows and press Enter." << endl << endl;
cout << "Eg: F:/cvpicture/flower.png" << endl << endl;
cout << "ps: Image preview will automatically turn off after 30 seconds！" << endl << endl;
cout << "----------------------------------------------------------------" << endl;
cout << "Please enter the path:";
cin >> filename;

//判断是否读取成功
while (!src.data)
{
cout << "The path of image is error,and could not load image..." << endl;
cout << "Please enter the path:";
cin >> filename;
}
namedWindow("input image", 1);//创建窗口以显示读取的图像
imshow("input image", src);//显示图像

//判断原图像的通道数，如果的是三通道图像则将图像转化为单通道灰度图像
if (3 == src.channels())
{
cvtColor(src, gray, CV_BGR2GRAY);
}
else
{
gray = src;
}
//将灰度图像二值化

//定义一个掩模
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(binary, dest, CV_MOP_OPEN, kernel);//调用形态学操作函数
bitwise_not(dest, dest);//反转背景颜色
namedWindow("dest image", 1);//创建目标图像显示窗口
imshow("dest image", dest);
waitKey(3000);//显示窗口停留时间30s
cvDestroyWindow("dest image");//释放显示窗口
cvDestroyWindow("input image");

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;
//定义字符型变量存储用户输入信息
char c;
cin >> c;
while (c != 'y' && c != 'n')
{
cout << endl;
cout << "Sorry,input is error,please re-enter:" << endl;
cin >> c;
cout << endl;
}
if (c== 'y')
{
cout << "Please enter the saved path:";
cin >> filename;
imwrite(filename, dest);
cout << endl;
cout << "Image saved successfully!" << endl;
}
else if (c == 'n')
{
cout << endl;
cout << "Unsaved！" << endl;
return 0;
}
return 0;
}




展开全文
• 图像处理——去除图像上杂乱的干扰(庖丁解牛) 源码地址：图像处理——去除图像上杂乱的干扰 庖丁解牛 1. #include <opencv2/opencv.hpp> 2. #include <opencv2/highgui/highgui_c.h> 3. #include <...
图像处理——去除图像上杂乱的干扰(庖丁解牛)
源码地址：图像处理——去除图像上杂乱的干扰
庖丁解牛
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){}


第二个参数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()函数读取图像
/** @brief Loads an image from a file.
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
-   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
*/


接下来翻译一下看定义说了些什么（水平有限，不到之处欢迎交流）：
简介：从一个文件中载入一幅图像

函数从指定的文件中载入一幅图像并将其返回，如果图像不能被读取（由于文件缺失，未得到许可，没有支持或有效的格式），该函数返回一个空的矩阵（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 （看后面的注释部分）
-GDAL支持的格栅和矢量地理空间数据

注释：

-函数是通过内容判定一幅图像的类型，而不是通过文件扩展名。
-对于彩色图像，解过码的图像将按照B G R的通道顺序存储。
-在 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。
驱动器将被用来解码图像，支持下列格式：Raster,Vector.
-像素的默认值必须低于2^30，使用系统变量OPENCV_IO_MAX_IMAGE_PIXELS可以设置这一限制。

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

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

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

（falgs是枚举类型，枚举可以理解为给一个东西起一个暗号（专业名称叫：标识符），比如说有一伙人打算去盗窃，事先跟负责放哨的人约定，看见警察过来了喊001，看见保安过来了喊002，看见有路人过来了喊003。这样子一来一是传递信息变得简单了，二是传递信息也更加安全了，试想一下，如果看到警察来了就喊：警察来了、警察来了，那还有逃跑的机会吗？上去不就按住了。还有“警察来了”这四个字也没有001这个数字发声快吧，如果这一伙贼机智的选了一个结巴的人去放哨，喊警察来了，估计第二声还没喊出来手铐就戴上了吧。（ps:c语言中枚举本质就是整型,意思是编译器会自动给枚举标识符按排列顺序从0开始编码，枚举变量可以用任意整型赋值。而c++中枚举变量,只能用被枚举出来的元素初始化。也就是说C语言中的枚举变量可以用整型数字初始化，但是C++中的枚举变量只能用标识符初始化。））

第一个形参：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的硬盘里找到一张图片，臣妾不是杀毒软件，臣妾做不到啊，所以是保存文件的路径名。

枚举类型，不再赘述，可以空着不填表示默认。

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;
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
-   **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标识的窗口。

注释：

注意：

仅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.    }


判断读入图像像素的通道数，如果是3通道图像，则调用API cvtColor将图像转换为单通道灰度图像。否则将源图像复制给变量gray（表示灰度图像）。

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

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
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
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 分配给像素的非零值，以满足操作条件。
参数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_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 和输入数组具有相同尺寸和类型的输出数组。
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;

//定义字符型变量存储用户输入信息
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，当用户输入的路径不存在，或者路径格式不正确，或者保存图像的格式不正确时，程序会意外结束。原因是因为程序没有实现对输入路径的正确性进行判断。
简介：保存一个图像指定的文件夹。

总的来说，只有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. }//程序结束。



展开全文
• （1）基于阈值的分割方法：可在各种颜色空间或不同通道中完成阈值、自适应阈值、 ...个人认为图像分割的算法可以从分割目标入手：通常是要将图像分成目标区域和背景。需要从图像的特征入手，以灰度图像为例
（1）基于阈值的分割方法：可在各种颜色空间或不同通道中完成阈值、自适应阈值、（2）基于边缘的分割方法：各种边缘检测算子（3）基于区域的分割方法：分水岭、区域归并与分裂（4）图割分割：最大流（最小割）算法（5）基于深度信息的分割：（6）基于先验信息的分割：

个人认为图像分割的算法可以从分割目标入手：通常是要将图像分成目标区域和背景。需要从图像的特征入手，以灰度图像为例(其余类型的图像处理均类似)，图像图形很明显的特征有：图像灰度值特征、目标边界特征、纹理特征、形态学特征等等；还有一些基于这些特征所计算提取出的特征，比如信息熵、能量泛函等等。

最为简单的就是灰度值特征了，一幅图中有时候目标区域与背景区域有很明显的亮度区别，基于这个认识，只要试图找到某个亮度的值，我们假设低于该值的认为是背景，高于该值的认为是目标。关于找这个值的算法就是阈值分割算法了，像OTSU、迭代法、最大熵法等等都是属于这一范畴。
同时也可以注意到，在空域内，目标的边界是区分目标与背景的重要依据，因此区分边界也是一个重要的手段，通常边界点周围灰度值变化率很高，因此可以基于图像灰度梯度来识别。这就有一些sobel算子、canny算子等等方式，都是通过找到边界来确定目标区域与背景的。
在有些图像中，目标区域具有一定的连续性，基于区域连续性的一些方法像区域生长法、分水岭算法等（本人对这一块不是很熟悉）。
另外，基于图像原始的特征进行提取获得“精炼”的二级特征，并据此分割也是一种好的方法。像SNAKE算法，该算法认为目标区域的边界是“外力”，内力共同作用的结果，因此当外力内力平衡时找到边界，基于这种平衡，提出了判断能量泛函最小的判断原则。此外，还有基于几何活动轮廓模型的水平集方法，该方法是借助于目标区域的几何度量参数，可以比较好的处理一些拓扑变化。

这里主要简单介绍几类经典的方法:

基于边缘检测的方法
基于边缘检测的方法主要是通过检测出区域的边缘来进行分割，利用区域之间特征的不一致性，首先检测图像中的边缘点，然后按一定策略连接成闭合的曲线，从而构成分割区域。图像中的边缘通常是灰度、颜色或纹理等性质不连续的地方。对于边缘的检测，经常需要借助边缘检测算子来进行，其中常用的边缘检测算子包括[3]：Roberts 算子、Laplace 算子、Prewitt 算子、Sobel
算子、Rosonfeld算子、Kirsch 算子以及Canny 算子等。
边缘检测算法比较适合边缘灰度值过渡比较显著且噪声较小的简单图像的分割。对于边缘比较复杂以及存在较强噪声的图像，则面临抗噪性和检测精度的矛盾。若提高检测精度，则噪声产生的伪边缘会导致不合理的轮廓：若提高抗噪性，则会产生轮廓漏检和位置偏差[4]。

阈值分割方法
阈值分割是最古老的分割技术，也是最简单实用的。许多情况下，图像中目标区域与背景区域或者说不同区域之间其灰度值存在差异，此时可以将灰度的均一性作为依据进行分割[3]。阈值分割即通过一个或几个阈值将图像分割成不同的区域。阈值分割方法的核心在于如何寻找适当的阈值。最常用的阈值方法是基于灰度直方图的方法，如最大类间方差法(OTSU)[5]、最小误差法、最大熵法等。此类方法通常对整幅图像使用固定的全局阈值，如果图像中有阴影或亮度分布不均等现象，分割效果会受到影响。基于局部阈值的分割方法对图像中的不同区域采用不同的阈值，相对于全局阈值方法具有更好的分割效果[6]，该方法又称为自适应阈值方法。其中阈值的选取一般是基于图像的局部统计信息，如局部方差[7]、局部对比度[8]以及曲面拟合阈值[9]等。无论是基于全局阈值还是局部阈值，阈值方法通常受噪声影响较大。为了得到较好的分割结果，通常还需要与其他图像处理技术，如图像去噪等相结合。

基于聚类的分割方法
聚类分析是多元统计分析的方法之一，也是模式识别中非监督模式识别的一个重要分支。根据数据集合的内部结构将其分成不同的类别，使得同一类内样本的特征尽可能相似，而属于不同类别的样本点的差异尽可能大。聚类分析技术大致上可分为硬聚类、模糊聚类与可能性聚类方法[10]。

硬聚类方法中，样本点归属于不同类别的隶属度函数取值为0 或1，即每个样本只可能属于某一特定的类别。传统的硬聚类方法包括k 均值聚类[11]以及ISODATA[12等。模糊聚类方法是一种基于目标函数迭代优化的无监督聚类方法，样本点的隶属度函数取值为区间[0, 1]，同时每个样本点对各类的隶属度之和为1，即认为样本点对每个聚类均有一个隶属度关系，允许样本点以不同的模糊隶属度函数同时归属于所有聚类。模糊聚类方法的软性划分，真实地反映了图像的模糊性和不确定性，因此其性能优于传统的硬分割方法。目前模糊聚类方法已经广泛应用于图像处理特别是医学图像处理中，其中最常用的是模糊C
均值聚类方法（FCM）[13,14]。可能性聚类样本点的隶属度函数同样取值为区间[0, 1]，但其不要求隶属度之和为1。可能性聚类不仅顾及到样本与聚类中心的隶属度关系，同时考虑了样本的典型性对分类结果的影响。 传统的聚类算法没有考虑图像的空间信息，因此其对噪声与灰度分布不均非常敏感。为提高模糊聚类算法在图像分割中的效果，国内外学者提出了很多改进方法，其中结合空间信息是最常见的方法[15--24]。

活动轮廓方法

活动轮廓方法(又称为Snake 模型)是Kass等人[25]于1987 年提出的，活动轮廓即定义在图像域的曲线或者曲面，在与自身几何特性相关的内力以及图像数据相关的外力共同作用下，以最小化能量函数的形式向边界运动。经过二十多年的发展，活动轮廓模型已经在边缘检测、图像分割以及运动跟踪中得到了广泛的应用[26,27]。
按照曲线的表达方式的不同，活动轮廓模型大致可以分为两大类：参数活动轮廓模型和几何活动轮廓模型。参数活动轮廓模型采用参数化的形式直接描述活动轮廓曲线，轮廓曲线由一些规则排列的不连续的点(也称snaxels) 组成或通过一些基函数将其描述成一种连续的参数形式。由于参数活动轮廓方法都是显式的表示曲线，因此便于引入先验形状约束[28]，同时有利于人机交互[27]。其缺点在于不能自适应的改变曲线的拓扑结构，难以处理曲线的分裂与合并；同时作为一种局部性方法，其最终分割结果严重依赖于初始位置。此外，轮廓曲线在演化过程中很难精确模拟复杂的边界，如深度凹陷的边界。Xu等人[29]提出梯度矢量流(GVF)Snake
方法，利用梯度矢量流来代替传统外力场，由于GVF 的作用范围比传统的Snake 模型大，能够促使轮廓曲线进入目标物体的凹陷部分，因此其在一定程度上克服了参数活动轮廓方法易于陷入局部能量极小的缺陷。

几何活动轮廓模型以曲线演化理论以及水平集方法为基础，曲线的演化仅依赖于其内在几何特性，而独立于曲线的参数。由于采用水平集方法隐式的表示曲线，使得其能够灵活地处理曲线的拓扑变化。几何活动轮廓模型又可分为基于边界的活动轮廓模型、基于区域的活动轮廓模型。基于边界的活动轮廓模型主要依赖图像的边缘信息控制曲线的运动速度。在图像边缘强度较弱或是远离边缘的地方，轮廓曲线运动速度较大，而在图像边缘强度较强的地方，轮廓曲线运动速度较小甚至停止，使得最终的轮廓曲线运动到边缘位置。比较著名的模型包括Caselles[30]提出的基于平均曲率流的几何活动轮廓模型、Caselles
与Kimmel 等人[31]提出的测地线活动轮廓方法。基于边界的活动轮廓方法对于对比度较好的图像具有较好的分割效果，然而，由于轮廓曲线的演化依赖于图像的边缘信息，当图像中噪声较强时通常难以得到理想的分割效果。基于区域的活动轮廓方法最早是由Mumford 与Shah 提出的MS 模型[32]，其主要思想是用分片光滑的函数逼近原始图像，通过能量泛函的极小化寻找合适的轮廓曲线以及近似函数，使得近似函数在除去轮廓曲线以外的同质区域为光滑函数。尽管已经有二十多年的历史，MS
模型至今仍然具有强大的生命力，基于MS 模型的分割方法依然层出不穷。由于MS 模型数值求解存在较大困难，其近似求解算法也是研究的热点问题。目前关于MS 模型的近似方法主要包含两类方法：Ambrosio 提出的基于椭圆逼近的辅助变量模型[33--36]以及Chan 与Vese 提出的基于简化MS 模型与水平集方法相结合的方法[37]。Zhu 等人提出的区域竞争的活动轮廓模型，将区域增长、活动轮廓以及基于Bayes 公式和最小描述长度（MDL）的多相分割方法统一起来。区域竞争法拥有这三种方法的优点，互相弥补了他们的不足之处。此外，测地线活动区域模型[38]（Geodesic
Active Region）结合了基于边界与基于区域的活动轮廓方法，能够有效利用图像的边缘信息与区域信息。近年来，为处理灰度分布不均图像的分割问题，提出了许多局部化的活动轮廓模型，如Li Chunming 等人[39]提出的基于局部二进拟合能量的活动轮廓模型(LBF)。

基于图论的方法

基于图论的图像分割技术是近年来图像分割领域的一个新的研究热点。其基本思想是将图像映射为带权无向图，把像素视作节点，节点之间的边的权重对应于两个像素间的不相似性度量，割的容量对应能量函数。运用最大流／最小流算法对图进行切割，得到的最小割对应于待提取的目标边界。该方法具有快速、鲁棒、全局最优、抗噪性强、可扩展性好等优点。目前，基于图论的图像分割方法的研究主要集中在以下几个方面[40]：

(1) 最优剪切准则的设计；

(2) 谱方法用于分割；

(3) 快速算法的设计等。

基于图论的方法本质上将图像分割问题转化为最优化问题，是一种点对聚类方法，其最优分割基本原则就是使划分成的两个子图(区域) 内部相似度最大，而子图之间的相似度最小。图割算法中割集准则的好坏直接影响到最终分割结果，其中常见的割集准则包括：Minimum cut[41]、Average cut[42]、Normalizecut[43]、Min—max
Cut[44]以及Ratio cut[45]等。

区域生长与分水岭算法区域生长方法[46]也是一种常用的区域分割技术，其基本思路是首先定义一个生长准则，然后在每个分割区域内寻找一个种子像素，通过对图像进行扫描，依次在种子点周围邻域内寻找满足生长准则的像素并将其合并到种子所在的区域，然后再检查该区域的全部相邻点，并把满足生长准则的点合并到该区域，不断重复该过程直到找不到满足条件的像素为止。该方法的关键在于种子点的位置、生长准则和生长顺序。分水岭算法[47,48]是以数学形态学作为基础的一种区域分割方法。其基本思想是将梯度图像看成是假想的地形表面，每个像素的梯度值表示该点的海拔高度。原图中的平坦区域梯度较小，构成盆地，边界处梯度较大构成分割盆地的山脊。分水岭算法模拟水的渗入过程，假设水从最低洼的地方渗入，随着水位上升，较小的山脊被淹没，而在较高的山脊上筑起水坝，防止两区域合并。当水位达到最高山脊时，算法结束，每一个孤立的积水盆地构成一个分割区域。由于受到图像噪声和目标区域内部的细节信息等因素影响，使用分水岭算法通常会产生过分割现象，分水岭算法一般是作为一种预分割方法，与其它分割方法结合使用，以提高算法的效率或精度，如文献[49]使用分水岭算法结合基于图论的方法进行图像分
割，在计算精度与计算效率方面均取得了很好的效果。

图像分割概述

来源于http://blog.csdn.net/zouxy09

所谓图像分割指的是根据灰度、颜色、纹理和形状等特征把图像划分成若干互不交迭的区域，并使这些特征在同一区域内呈现出相似性，而在不同区域间呈现出明显的差异性。我们先对目前主要的图像分割方法做个概述，后面再对个别方法做详细的了解和学习。

1、基于阈值的分割方法

阈值法的基本思想是基于图像的灰度特征来计算一个或多个灰度阈值，并将图像中每个像素的灰度值与阈值相比较，最后将像素根据比较结果分到合适的类别中。因此，该类方法最为关键的一步就是按照某个准则函数来求解最佳灰度阈值。

2、基于边缘的分割方法

所谓边缘是指图像中两个不同区域的边界线上连续的像素点的集合，是图像局部特征不连续性的反映，体现了灰度、颜色、纹理等图像特性的突变。通常情况下，基于边缘的分割方法指的是基于灰度值的边缘检测，它是建立在边缘灰度值会呈现出阶跃型或屋顶型变化这一观测基础上的方法。

阶跃型边缘两边像素点的灰度值存在着明显的差异，而屋顶型边缘则位于灰度值上升或下降的转折处。正是基于这一特性，可以使用微分算子进行边缘检测，即使用一阶导数的极值与二阶导数的过零点来确定边缘，具体实现时可以使用图像与模板进行卷积来完成。

3、基于区域的分割方法

此类方法是将图像按照相似性准则分成不同的区域，主要包括种子区域生长法、区域分裂合并法和分水岭法等几种类型。

种子区域生长法是从一组代表不同生长区域的种子像素开始，接下来将种子像素邻域里符合条件的像素合并到种子像素所代表的生长区域中，并将新添加的像素作为新的种子像素继续合并过程，直到找不到符合条件的新像素为止。该方法的关键是选择合适的初始种子像素以及合理的生长准则。

区域分裂合并法（Gonzalez，2002）的基本思想是首先将图像任意分成若干互不相交的区域，然后再按照相关准则对这些区域进行分裂或者合并从而完成分割任务，该方法既适用于灰度图像分割也适用于纹理图像分割。

分水岭法（Meyer，1990）是一种基于拓扑理论的数学形态学的分割方法，其基本思想是把图像看作是测地学上的拓扑地貌，图像中每一点像素的灰度值表示该点的海拔高度，每一个局部极小值及其影响区域称为集水盆，而集水盆的边界则形成分水岭。该算法的实现可以模拟成洪水淹没的过程，图像的最低点首先被淹没，然后水逐渐淹没整个山谷。当水位到达一定高度的时候将会溢出，这时在水溢出的地方修建堤坝，重复这个过程直到整个图像上的点全部被淹没，这时所建立的一系列堤坝就成为分开各个盆地的分水岭。分水岭算法对微弱的边缘有着良好的响应，但图像中的噪声会使分水岭算法产生过分割的现象。

4、基于图论的分割方法

此类方法把图像分割问题与图的最小割（min cut）问题相关联。首先将图像映射为带权无向图G=<V，E>，图中每个节点N∈V对应于图像中的每个像素，每条边∈E连接着一对相邻的像素，边的权值表示了相邻像素之间在灰度、颜色或纹理方面的非负相似度。而对图像的一个分割s就是对图的一个剪切，被分割的每个区域C∈S对应着图中的一个子图。而分割的最优原则就是使划分后的子图在内部保持相似度最大，而子图之间的相似度保持最小。基于图论的分割方法的本质就是移除特定的边，将图划分为若干子图从而实现分割。目前所了解到的基于图论的方法有GraphCut，GrabCut和Random
Walk等。

5、基于能量泛函的分割方法

该类方法主要指的是活动轮廓模型（active contour model）以及在其基础上发展出来的算法，其基本思想是使用连续曲线来表达目标边缘，并定义一个能量泛函使得其自变量包括边缘曲线，因此分割过程就转变为求解能量泛函的最小值的过程，一般可通过求解函数对应的欧拉(Euler．Lagrange)方程来实现，能量达到最小时的曲线位置就是目标的轮廓所在。按照模型中曲线表达形式的不同，活动轮廓模型可以分为两大类：参数活动轮廓模型（parametric
active contour model）和几何活动轮廓模型（geometric active contour model）。

参数活动轮廓模型是基于Lagrange框架，直接以曲线的参数化形式来表达曲线，最具代表性的是由Kasset a1(1987)所提出的Snake模型。该类模型在早期的生物图像分割领域得到了成功的应用，但其存在着分割结果受初始轮廓的设置影响较大以及难以处理曲线拓扑结构变化等缺点，此外其能量泛函只依赖于曲线参数的选择，与物体的几何形状无关，这也限制了其进一步的应用。

几何活动轮廓模型的曲线运动过程是基于曲线的几何度量参数而非曲线的表达参数，因此可以较好地处理拓扑结构的变化，并可以解决参数活动轮廓模型难以解决的问题。而水平集（Level Set）方法（Osher，1988）的引入，则极大地推动了几何活动轮廓模型的发展，因此几何活动轮廓模型一般也可被称为水平集方法。

展开全文
• 图像处理(image processing)，用计算机对图像进行分析，以达到所需结果的技术。又称影像处理。图像处理一般指数字图像处理。数字图像是指用工业相机、摄像机、扫描仪等设备经过拍摄得到的一个大的二维数组，该数组的...
• ## 图像处理与识别学习小结

万次阅读 热门讨论 2009-08-31 23:14:00
图像处理与识别学习小结 数字图像处理是对图像进行分析、加工、和处理，使其满足视觉、心理以及其他要求的技术。图像处理是信号处理在图像域上的一个应用。目前大多数的图像是以数字形式存储，因而图像处理很多情况...
• 数字图像处理[M]. 电子工业出版社, 2014.第一章 绪论1 数字图像处理的主要内容（基本步骤）是什么？主要内容：图像获取、图像增强、图像复原、彩色图像处理、（小波变换）、形态学处理、分 割、识别、压缩编码。 ...
• 第一章 图像的概念: 1.什么是图像? 图 —— 物体投射或反射光的分布，是客观存在； 像 —— 人的视觉系统对图的接收在大脑中形成的印象或认识，是人的感觉；...图像处理：对图像信息进行性加工（...
• 数字图像处理与Python实现笔记之彩色图像处理初步摘要绪论2 彩色图像处理初步2.1 彩色图像的颜色空间2.1.1　RGB颜色空间2.1.2　HSI颜色空间2.1.3　RGB和HSI颜色空间的转换2.2 伪彩色图像处理2.2.1 强度分层2.2.2 ...
• 图像处理技术基础一.数字图像的基本概念1.图像2.图形和图像的区别3.数字图像的表示和图像数字化的精度4.数字图像处理系统5.数字图像处理的目的二.图像处理技术简介1.简介2.常用技术3.数字图像处理的特点4.数字图像...
• ## 图像处理与识别

千次阅读 多人点赞 2017-03-23 09:45:57
数字图像处理是对图像进行分析、加工、和处理，使其满足视觉、心理以及其他要求的技术。图像处理是信号处理在图像域上的一个应用。目前大多数的图像是以数字形式存储，因而图像处理很多情况下指数字图像处理。此外，...
• 数字图像处理要点总结图像及图像处理数字图像处理数字图像处理的目的数字图像处理的主要研究内容合理的创建标题，有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表...
• ## 指纹识别源代码（1）-图像处理

万次阅读 多人点赞 2019-11-26 19:13:09
源码已经上传到github,... 1.缩放处理 f=imread(image);%读取图像到内存 f=imresize(f,[363,312]);%该函数用于对图像做缩放处理。 figure;imshow(f); %用rgb2gray 将彩色图像转换为灰度图像。ma...
• ## 图像处理岗位面试题搜罗汇总

万次阅读 多人点赞 2018-06-21 15:11:46
原文...传统图像处理部分 图像处理基础知识 彩色图像、灰度图像、二...
• 1.1 图像与图像处理的概念 图像(Image)： 使用各种观测系统以不同形式和手段观测客观世界而获得的，可以直接或间接作用于人眼并进而产生视觉的实体。包括： ·各类图片，如普通照片、X光片、遥感图片； ·各类...
• 第九章 形态学图像处理 第九章 形态学图像处理 一腐蚀和膨胀 1 腐蚀 2 膨胀 二开操作与闭操作 三击中或击不中变换 四一些基本的形态学算法 1 边界提取 2 孔洞填充 3 连通分量的提取 4 凸壳 5 细化 6 粗化 7 ...
• 一般认为机器视觉“是通过光学装置和非接触传感器自动地接受和处理一个真实场景的图像，通过分析图像获得所需信息或用于控制机器运动的装置”，可以看出智能图像处理技术在机器视觉中占有举足轻重的位置。...
• ## Python中的图像处理

千次阅读 2018-03-03 22:27:15
第 1 章　基本的图像操作和处理本章讲解操作和处理图像的基础知识，将通过大量...1.1　PIL：Python图像处理类库PIL（Python Imaging Library Python，图像处理类库）提供了通用的图像处理功能，以及大量有用的基本图...
• ## 图像处理之滤波器

千次阅读 2016-09-01 11:01:18
图像处理之滤波器 ...由于我们组的项目和图像处理（运动物体检测与跟踪）有关，和我同组的iamfrankie同学将在FPGA方面对该项目发表一些好的博文，前段时间他的博文也受到各位的好评，其中不乏好的文章，
...