On this page

    C++部分

    图像的加载显示与保存

    加载图像(cv::imread)

    显示图像(cv::nameWindows与cv::imshow)

    保存图像(cv::imwrite)

    矩阵的掩膜操作

    获取图像指针

    像素范围处理

    掩膜操作实现图像对比度调整

    ps: 感觉和卷积很相似

    代码实现:

        int cols = (src.cols - 1) * src.channels();
    	int offsetx = src.channels();
    	int rows = src.rows;
    	dst = Mat::zeros(src.size(), src.type());
    	for (int row = 1; row < (rows - 1); row++)
    	{
    		const uchar* previous = src.ptr<uchar>(row - 1);
    		const uchar* current = src.ptr<uchar>(row);
    		const uchar* next = src.ptr<uchar>(row + 1);
    		uchar* output = dst.ptr<uchar>(row);
    		for (int col = offsetx; col < cols; col++)
    		{
    			output[col] = saturate_cast<uchar>(5 * current[col] - (current[col - offsetx] 
                                                   + current[col + offsetx] + previous[col] + next[col]));
    		}
    	}
    

    函数调用filer2D功能

    1. 定义掩膜:Mat kernel = (Mat_(3, 3)<<0, -1, 0, -1, 5, -1, 0, -1, 0);
    2. filter2D(src, dst, src.depth(), kernel);其中src与dst是Mat类型变量,src.depth表示位图深度, 有32, 24, 8等。 代码实现:可以实现和上面代码同样的功能
      	double t;
       t = (double)getTickCount();
       filter2D(src, dst, src.depth(), kernel);
       t = ((double)getTickCount() - t) / getTickFrequency();
       cout << "Built in Filter2D time passed in seconds" << t << endl;
      

      Mat对象

    Mat对象与IplImage对象

    Mat对象构造函数与常用方法 构造函数:

    常用方法:

    Mat对象使用

    部分复制:一般情况下只会复制Mat对象的头和指针部分,不会复制数据部分

    Mat A = imread(imgFilePath);
    Mat B(A) //只复制
    

    完全复制:如果想把Mat对象的头部和数据部分一起复制,可以通过以下两个API实现

    Mat F = A.clone();
    Mat G;
    A.copyTo(G)
    

    Mat对象使用的四个要点

    Mat对象创建

    定义小数组

    Mat C = (Mat_<double>(3, 3) <<  0, -1, 0, -1, 5, -1, 0, -1, 0);
    cout << "C = " << endl << C << endl << endl;
    

    图像操作

    	cvtColor(src, gray_src, CV_BGR2GRAY);
    	int height = gray_src.rows;
    	int width = gray_src.cols;
    	for(int row = 0; row < height; row++)
    	{
    		for (int col = 0; col < width; col++)
    		{
    			int gray = gray_src.at<uchar>(row, col);
    			gray_src.at<uchar>(row, col) = 255 - gray;
    		}
    	}
    

    其实以上代码和 bitwise_not(src. dst) 这个api效果是一样的

    Vec3b与Vec3F

    图像混合

    代码演示

    	double alpha = 0.5;
    	if (src1.rows == src2.rows && src1.cols == src2.cols && src1.type() == src2.type())
    	{
    		addWeighted(src1, alpha, src2, (1.0 - alpha), 0.0, dst);
    		//multiply(src1, src2, dst, 1.0);
    		namedWindow("blend demo ", CV_WINDOW_AUTOSIZE);
    		imshow("blend demo", dst);
    
    	}
    

    调整图像亮度和对比度

    理论 图像变换可以看作如下:

    代码演示

    for (int row = 0; row < height; row++)
    	{
    		for (int col = 0; col < width; col++)
    		{
    			if (nc == 1)
    			{
    				int v = src.at<uchar>(row, col);
    				dst.at<uchar>(row, col) = 255 - v;
    			}
    			else if (nc == 3)
    			{
    				int b = src.at<Vec3b>(row, col)[0];
    				int g = src.at<Vec3b>(row, col)[1];
    				int r = src.at<Vec3b>(row, col)[2];
    				dst.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(b*alpha + beta);
    				dst.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(g*alpha + beta);
    				dst.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(r*alpha + beta);
    			}
    		}
    

    其中alpha控制对比度,beta控制亮度。

    重要的API

    绘制形状和文字

    绘制线、矩形、圆、椭圆等基本几何形状

    void myLines()
    {
    	Point p1 = Point(20, 30);
    	Point p2;
    	p2.x = 400;
    	p2.y = 400;
    	Scalar color = Scalar(0, 0, 255);
    	line(src, p1, p2, color, 1, LINE_8);
    }
    
    void myEllipse()
    {
    	Scalar color = Scalar(0, 255, 0);
    	ellipse(src, Point(src.cols / 2, src.rows / 2), Size(src.cols / 4, src.rows / 8), 90, 0, 360, color, 2, LINE_8);
    }
    
    void myRectangle()
    {
    	Rect rect = Rect(200, 100, 300, 300);
    	Scalar color = Scalar(255, 0, 0);
    	rectangle(src, rect, color, 1, LINE_8);
    }
    
    void myCircle()
    {
    	Scalar color = Scalar(0, 255, 255);
    	Point center = Point(src.cols / 2, src.rows / 2);
    	circle(src, center, 150, color, 1, LINE_8);
    }
    

    随机生成(随机数生成cv::RNG)与绘制文本

    void RandomLineDemo()
    {
    	RNG rng(12345);
    	Point pt1;
    	Point pt2;
    	Mat bg = Mat::zeros(src.size(), src.type());
    	namedWindow("random line demo", CV_WINDOW_AUTOSIZE);
    	for (int i = 0; i < 65535; i++)
    	{
    		pt1.x = rng.uniform(0, src.cols);
    		pt2.x = rng.uniform(0, src.cols);
    		pt1.y = rng.uniform(0, src.rows);
    		pt2.y = rng.uniform(0, src.rows);
    		Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
    		if (waitKey(50) > 0)
    			break;
    		line(bg, pt1, pt2, color, 1, 8);
    		imshow("random line demo", bg);
    	}
    
    }
    

    模糊操作一

    模糊原理

    假设有6x6的图像像素点矩阵 卷积过程:6x6上面是个3x3的窗口,从左到右,从上向下移动,黄色的每个像素点值之和取平均值赋给中心红色像素作为它卷积处理之后新的像素值。每次移动一个像素格。(均值滤波)

    相关API

    模糊操作二

    中值滤波

    双边滤波(高斯双边滤波)

    相关API

    膨胀与腐蚀

    形态学操作(morphology operators)

    形态学操作-膨胀

    形态学操作-腐蚀

    相关API

    代码演示:

    void CallBack_Demo(int, void*)
    {
    	int s = element_size * 2 + 1;
    	Mat structureElement = getStructuringElement(MORPH_RECT, Size(s, s), Point(-1, -1));
    	//dilate(src, dst, structureElement, Point(-1, -1), 1);
    	erode(src, dst, structureElement, Point(-1, -1));
    	imshow(OUTPUT_WIN, dst);
    }
    

    动态调整结构元素大小

    代码演示:

    createTrackbar("Element Size:", OUTPUT_WIN, &element_size, max_size, CallBack_Demo);
    

    形态学操作

    总结:闭操作用于去掉小黑点,开操作用于去掉小白点

    相关API

    代码示例:

    	namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
    	Mat kernel = getStructuringElement(MORPH_RECT, Size(11, 11), Point(-1, -1));
    	morphologyEx(src, dst,CV_MOP_CLOSE, kernel, Point(-1, -1));
    	imshow(OUTPUT_WIN, dst);
    

    形态学操作应用-提取水平和垂直直线

    原理方法

    图像形态学操作时候,可以通过自定义的结构元素实现结构元素对输入图像一些对象敏感、另外一些对象不敏感,这样就会让敏感的对象改变而不敏感的对象保留输出。通过使用两个最基本的形态学操作-膨胀与腐蚀,使用不同的结构元素实现对输入图像的操作,得到想要的结果。

    结构元素

    提取步骤

    转换为二值图像-adaptiveThreshold

    adativeThreshold(
    Mat src, //输入的灰度图像
    Mat dst, //二值图像
    double maxValue, //二值图像最大值
    int adaptiveMethod, //自适应方法,只能其中之一
                       //ADAPTIVE_THRESH_MEAN_C, ADAPTIVE_THRESH_GAUSSIAN_C
    int thresholdType, //阈值类型
    int blocksize, //块大小
    double C //常量C 可以是正数, 0, 负数
    )
    

    注意:自适应阈值二值化的方式通过计算每个像素周围bxb大小像素块的加权均值并减去常量C得到,如果图片像素比较高,像素块又比较小,有可能会出现二值不好的情况,比如这样,黑色的部分被二值成了白色。

    代码实现:

    void Binarry_Line()
    {
    	Mat gray_src;
    	cvtColor(src, gray_src, CV_BGR2GRAY);
    	namedWindow("gray_image", CV_WINDOW_NORMAL);
    	cvResizeWindow("gray_image", 500, 500);
    	imshow("gray_image", gray_src);
    
    	Mat binImg;
    	threshold(gray_src, binImg, 80, 255, THRESH_BINARY_INV);
    	//adaptiveThreshold(gray_src, binImg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 101, -2);
    	namedWindow("binarry image", CV_WINDOW_NORMAL);
    	cvResizeWindow("binarry image", 500, 500);
    	imshow("binarry image", binImg);
    
    	Mat hline = getStructuringElement(MORPH_RECT, Size(src.cols / 16, 1), Point(-1, -1));
    	Mat vline = getStructuringElement(MORPH_RECT, Size(1, src.rows / 16), Point(-1, -1));
    
    	Mat temp;
    	erode(binImg, temp, hline);
    	dilate(temp, dst, hline);
    	namedWindow("final image", CV_WINDOW_NORMAL);
    	cvResizeWindow("final image", 500, 500);
    	imshow("final image", dst);
    	//createTrackbar("Element Size:", OUTPUT_WIN, &element_size, max_size, CallBack_Demo);
    	//CallBack_Demo(0, 0);
    }
    

    图像上采样和降采样

    代码演示

    void Threshold_Demo(int, void*)
    {
    
    	cvtColor(src, gray_src, CV_BGR2GRAY);
    	threshold(gray_src, dst, threshold_value, threshold_max, type_value);
    	imshow(output_title, dst);
    }
    

    自定义线性滤波

    卷积概念

    卷积如何工作

    卷积核又被称为算子 常见算子

    - Sobel算子:表现方向上的差异
    ![](https://upload-images.jianshu.io/upload_images/8332901-9e99aca151ed219b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    

    void Sobel() { //Sobel_X方向的差异 //Mat kernel_x = (Mat_(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1); //Sobel_Y方向的差异 Mat kernel_y= (Mat_(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1); filter2D(src, dst, -1, kernel_y, Point(-1, -1), 0.0); namedWindow("Sobel", WINDOW_AUTOSIZE); imshow("Sobel", dst); }

    - 拉普拉斯算子:提取边缘体征
    ![](https://upload-images.jianshu.io/upload_images/8332901-a4f306a773e873da.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    

    void Laplasian() { Mat kernel = (Mat_(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0); filter2D(src, dst, -1, kernel, Point(-1, -1), 0.0); namedWindow("Laplasian", WINDOW_AUTOSIZE); imshow("Laplasian", dst); }

    
    **自定义卷积模糊**
    API:
    

    filter2D(Mat src, //输入图像 Mat dst, //输出图像 Mat depth, //图像深度 32/8 Mat kernel, //卷积核/模板 Point anchor, //锚点位置 double delta //计算出来的像素+delta )

    代码演示:
    

    void Customer_Filter() { while (true) { char c = waitKey(500); if (c == 27) { break; } int ksize = 4 + (index % 5) * 2 + 1; Mat kernel = Mat::ones(Size(ksize, ksize), CV_32F) / (float)(ksize * ksize); filter2D(src, dst, -1, kernel, Point(-1, -1)); index++; namedWindow(“customer_filter”, WINDOW_AUTOSIZE); imshow(“customer_filter”, dst);

    } } ```
    

    处理卷积边缘

    卷积边界问题

    处理边缘

    API说明

    copyMakeBorder(
        Mat src, //输入图像
        Mat dst, //添加边缘图像
        int top,  //边缘长度, 一般上下左右都取相同值
        int bottom,
        int left,
        int right,
        int boderType,
        Scalar value
        )
    

    Sobel算子

    卷积应用-图像边缘提取

    Sobel算子

    API说明cv::Sobel

    cv::Sobel(
        InputArray Src, //输入图像
        OutputArray dst, //输出图像,大小与输入图像一致
        int depth, //输出图像深度
        int dx, //x方向, 几阶导数
        int dy, //y方向, 几阶导数
        int ksize, //Sobel算子kernel大小, 必须是1、 3、5、 7、(单数)
        double scale = 1,
        double delta = 0,
        int borderType = BORDER_DEFAULT
        )
    

    Laplacian算子

    理论

    在二阶微分的时候, 最大变化处的值为零即边缘是零值。通过二阶导数计算,依据此理论我们可以计算图像二阶导数,提取边缘。

    image.png

    Laplacian算子

    处理流程

    Canny边缘检测

    图像的边缘检测的原理是检测出图像中所有灰度值变化较大的点,而且这些点连接起来就构成了若干线条,这些线条就可以称为图像的边缘

    Canny算法介绍

    Canny算法过程-五步

    1. 高斯模糊-GaussianBlur
    2. 灰度转换-cvtColor
    3. 计算梯度-Sobel/Scharr
    4. 非最大信号抑制
    5. 高低阈值输出二值图像

    Canny算法介绍-非最大信号抑制 这里不是很明白

    Canny算法介绍-高低阈值输出二值图像

    相关API-cv::Canny

      Canny(
        inputArray src, //8-bit的输入图像
        OutputArray edges, //输出边缘图像, 一般都是二值图像
        double threshold1, //低阈值, 常取高阈值的1/2, 或者1/3
        double threshold2, //高阈值
        int aptertureSize, //Sobel算子的size, 通常3x3, 取值3
        boot L2gradient //选择true表示是L2来归一化, 否则用L1归一化
        )
    

    寻找轮廓

    相关API

    void cv::findContours   (   InputOutputArray    image,
                                OutputArrayOfArrays     contours,
                                OutputArray     hierarchy,
                                int     mode,
                                int     method,
                                Point   offset = Point() 
                            )   
    

    参数介绍: image:输入图像,图像必须为8-bit单通道图像,图像中的非零像素将被视为1,0像素保留其像素值,故加载图像后会自动转换为二值图像。我们同样可以使用cv::compare,cv::inRange,cv::threshold,cv::adaptiveThreshold,cv::Canny等函数来创建二值图像,,如果第四个参数为cv::RETR_CCOMP或cv::RETR_FLOODFILL,输入图像可以是32-bit整型图像(CV_32SC1)

    contours:检测到的轮廓,每个轮廓都是以点向量的形式进行存储即使用point类型的vector表示

    hierarchy:可选的输出向量(std::vector),包含了图像的拓扑信息,作为轮廓数量的表示hierarchy包含了很多元素,每个轮廓contours[i]对应hierarchy中hierarchy[i][0]~hierarchy[i][3],分别表示后一个轮廓,前一个轮廓,父轮廓,内嵌轮廓的索引,如果没有对应项,则相应的hierarchy[i]设置为负数。

    mode:轮廓检索模式,可以通过cv::RetrievalModes()查看详细信息,如下 RETR_EXTERNAL:表示只检测最外层轮廓,对所有轮廓设置hierarchy[i][2]=hierarchy[i][3]=-1 RETR_LIST:提取所有轮廓,并放置在list中,检测的轮廓不建立等级关系 RETR_CCOMP:提取所有轮廓,并将轮廓组织成双层结构(two-level hierarchy),顶层为连通域的外围边界,次层位内层边界 RETR_TREE:提取所有轮廓并重新建立网状轮廓结构 RETR_FLOODFILL:官网没有介绍,应该是洪水填充法

    method:轮廓近似方法可以通过cv::ContourApproximationModes()查看详细信息 CHAIN_APPROX_NONE:获取每个轮廓的每个像素,相邻的两个点的像素位置差不超过1 CHAIN_APPROX_SIMPLE:压缩水平方向,垂直方向,对角线方向的元素值,保留该方向的中点坐标,如果一个矩形轮廓只需4个点来保存轮廓信息 CHAIN_APPROX_TC89_L1和CHAIN_APPROX_TC89_KCOS使用Teh-Chinl链逼近算法中的一种

    Rect boundingRect(InputArray points)
    

    参数介绍: points:输入信息,可以为包含点的容器(vector)或是Mat。 返回包覆输入信息的最小正矩形。

    RotatedRect minAreaRect(InputArray points)
    

    参数介绍: points:输入信息,可以为包含点的容器(vector)或是Mat。 返回包覆输入信息的最小斜矩形。

    boundingRect和minAreaRect的区别如下图

    void minEnclosingCircle(InputArray points, Point2f& center, float& radius)
    

    参数介绍: points:输入信息,可以为包含点的容器(vector)或是Mat。 center:包覆圆形的圆心。 radius:包覆圆形的半径。

    霍夫变换-直线

    公式由下往上倒推

    霍夫直线变换介绍

    ps:不是很明白

    相关API学习

    霍夫圆检测

    霍夫圆检测原理

    相关API cv::HoughCircles

      HoughCircles(
        InputArray image, //输入图像,必须是8位的单通道灰度图像
        OutputArray circles, //输出结果,发现的圆信息
        int method, //方法 - HOUGH_GRADIENT
        double mindist, //10 最短距离 - 可以分辨是两个圆的,否则认为是同心圆
        double param1, //canny edge detection high threshold
        double param2, //中心点累加器阈值 - 候选圆心
        int minradius, //最小半径
        int maxradius //最大半径
        )
    

    像素重映射

    什么是像素重映射

    简单点说就是把输入图像中各个像素按照一定的规则映射到另外一张图像的对应位置上去,形成一张新的图像

    API介绍cv::remap

        Remap(
            InputArray src, //输入图像
            OutputArray dst, //输出图像
            InputArray map1, //x映射表 CV_32FC1 / CV_32FC2
            InputArray map2, //y映射表
            int interpolation, //选择的插值方法, 常见线性插值, 可选择立方等
            int boderMode, //BODER_CONSTANT
            const Scalar boderValue //color
            )
    

    python部分

    import cv2 as cv
    

    色彩空间转换

    cv.cvtColor()

    参数:

    imgae: 图片

        gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
        cv.imshow("gray", gray)
        hsv = cv.cvtColor(image, cv.COLOR_BGR2HSV)
        cv.imshow("hsv", hsv)
        yuv = cv.cvtColor(image, cv.COLOR_BGR2YUV)
        cv.imshow("yuv", yuv)
    

    cv.inRange()

    通道的分离与合并

    cv.split(): 通道的分离

    cv.merge(): 通道的合并

    像素运算

    : cv.add(m1, m2) : cv.subtract(m1, m2) : cv.multiply(m1, m2) : cv.divide(m1,m2) 均值&方差: cv.meanStdDev(m1)

    : cv.bitwise_and(m1,m2) : cv.bitwise_or(m1, m2) : cv.bitwise_not(m1)

    cv.addWeighted: 调整对比度与两度

    ROI(Range Of Interest)

    泛洪填充

    模糊操作

    边缘保留滤波(EPF)

    def image_hist(image):
        color = ('blue', 'green', 'red')
        for i, color in enumerate(color):
            hist = cv.calcHist([image], [i], None, [256], [0, 256])
            plt.plot(hist, color=color)
            plt.xlim([0, 256])
        plt.show()
    

    巴氏距离 相关性 卡方

    直方图的降维计算不明白是怎么算出来的 (这里不明白公式的推导)

    模板匹配

    简单来说就是,在源图像中寻找目标图像的位置

    图像二值化

    OpenCV中图像二值化的方法:

    全局阈值

    def treshold_demo(image):
        gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
        ret, binarry = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
        print("threshold value %s" % ret)
        cv.imshow("binarry", binarry)
    

    局部阈值 自适应阈值

    def local_threshold(image):
        gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
        binarry = cv.adaptiveThreshold(gray, 255, cv.ADAPTIVE_THRESH_GAUSSIAN_C, cv.THRESH_BINARY, 25, 10)
        cv.imshow("binarry", binarry)
    

    超大图像二值化: 如果图像过大,一些噪声或者光线就会影响二值化效果,所以通过分块后进行阈值化效果更好。

    图像金字塔原理

    reduce = 高斯模糊 + 降采样 expand = 扩大 + 卷积 降采样: PryDown 还原: PryUp 高斯金字塔与拉普拉斯金字塔 在使用拉普拉斯金字塔时注意,我们选用的图片大小必须是2^n大小,或者是一个宽高相等的图片

    如何计算图像边缘? 1、对图像求一阶导数,导数最大的时候,图像的变化率最大,也就是图像的边缘。 2、对图像求二阶导数,二阶导数为0的时候,一阶导数最大,源图像变化率最大,也就是图像的边缘。 一阶导数与sobel算子(scharr算子效果更明显,但同时也会引入更多噪声) 二阶导数与拉普拉斯算子

    Canny边缘提取

    1、高斯模糊 - GaussianBlur 2、灰度转换 - cvtColor 3、计算梯度 - Sobel/Scharr 4、非最大信号抑制 5、高低阈值输出二值图像

    直线检测

    霍夫直线变换(Hough Line Transform) cv.HoughLines 前提条件: 边缘检测已经完成 平面空间到极坐标空间转换

    霍夫圆检测

    霍夫圆变换原理

    现实考量:

    轮廓发现

    轮廓发现: 是基于图像边缘提取的基础寻找对象的轮廓的方法。 所以边缘提取的阈值选定会影响最终轮廓发现结果。

    API介绍:

    如何利用梯度来避免阈值烦恼

    对象检测

    图像形态学

    膨胀与腐蚀

    开闭操作

    其他形态学操作

    分水岭算法

    `