Opencv【2】基础

Opencv(2)基础


图像金字塔

  • 拉普拉斯金字塔——用来重建一张图象,根据它的上层降采样图片

  • 高斯金字塔——用来对图像进行降采样。降采样常用来简单加速算法,在小尺度上计算,再放大到原图上。

    从底向上,逐层降采样,降采样之后,行列对半,即删除偶数行与列,得到降采样之后的上一层图片。

    步骤

    • 对当前层进行高斯模糊
    • 删除当前层偶数的行与列

    得到降采样图象,只有原图的四分之一。

    高斯差分(DOG,difference of Gaussian):同一张图片经过不同参数的高斯变换之后结果相减,得到的图象称为高斯差分。常用于特征抽取。

    高斯差分是图象的内在特征,常用于灰度图像增强、角点检测。

    API:

    • 上采样:cv::pyrUp,zoom in ,放大,长宽各放大2倍。
    • 降采样:cv::pyrDown,Zoom out,缩小,长宽各取一半。
    /*11.图像金字塔*/
    void section11()
    {
    Mat src;
    src = imread("D:/workspace/image/lena.jpg", IMREAD_COLOR);
    if (!src.data) {
        printf("load error!");
        return;
    }
    namedWindow("input", CV_WINDOW_AUTOSIZE);
    imshow("input", src);
    
    // 上采样
    Mat upImg;
    // 注意,这里的Size参数是(宽,高),对应的就是(cols,rows),而不是一般矩阵计算的(rows,cols)
    pyrUp(src, upImg, Size(src.cols * 2, src.rows * 2));
    imshow("上采样", upImg);
    
    // 降采样
    Mat downImg;
    // 默认就是宽高+1/2,应对奇偶问题
    pyrDown(src, downImg, Size( (src.cols+1) / 2, (src.rows+1) / 2));
    imshow("降采样", downImg);
    
    // 高斯差分,彩色
    Mat d1, d2, dst;
    GaussianBlur(src, d1, Size(5, 5), 0, 0);
    GaussianBlur(d1, d2, Size(5, 5), 0, 0);
    // 两图相减
    subtract(d1, d2, dst, Mat());
    normalize(dst, dst, 255, 0, NORM_MINMAX);
    imshow("DOG-Color", dst);
    
    // 高斯差分,灰度
    Mat gray,d3,d4,dst2;
    cvtColor(src, gray, CV_BGR2GRAY);
    // if both sigmas are zeros, they are computed from ksize.width and ksize.height,根据模板大小自动计算sigma
    GaussianBlur(gray, d3, Size(5, 5), 0, 0);
    GaussianBlur(d3, d4, Size(5, 5), 0, 0);
    // 两图相减
    subtract(d3, d4, dst2, Mat());
    // 通常高斯差分(差值)很小,需要进行归一化,下列公式以最大最小值归一化公式,实际进行的是强度的线性拉伸操作
    normalize(dst2, dst2, 255, 0, NormTypes::NORM_MINMAX);
    imshow("DOG-Gray", dst2);
    
    waitKey(0);
    }
  • 结果图

    图象金字塔

    94K4hQ.png

    高斯差分(分别为:灰度图DOG,彩色图DOG,原图)

    94Khtg.png


基本阈值操作

阈值,threshold,实际就是一个中介值。图象阈值通常用于图象分割,而对灰度图进行阈值分割通常就是二值化。

  • 阈值二值化(threshold binary), THRESH_BINARY = 0
  • 阈值反二值化(threshold binary Inverted), THRESH_BINARY_INV = 1
  • 阈值截断(truncate), THRESH_TRUNC = 2
  • 阈值取零(threshold to zero), THRESH_TOZERO = 3
  • 阈值反取零(threshold to zero Inverted), THRESH_TOZERO_INV = 4

阈值计算方法:THRESH_OTSU、THRESH_TRIANGLE.两种自动计算阈值的算法,前者又叫最大类间方差发(OTSU法,大律法)。threshold(gray, dst, &thresh_value, 255, THRESH_OTSU | THRESH_BINARY);//此时指定采用otsu方法自动计算阈值,指定的阈值thresh_value无效

阈值操作,指定输入图象只能为8位单通道图!!!否则出错!!!

完整代码:

// 构建一个用于callback传递数据的结构体
struct UserData {
    Mat * gray;
    int * thresh_value;
    int *thresh_max;
    int *op_value;
    int *op_max;
    // C++ 结构体可以有函数,如初始化函数
    UserData(Mat *gray, int * thresh_value, int *thresh_max, int *op_value, int *op_max)
    {
        this->gray = gray;
        this->thresh_value = thresh_value;
        this->thresh_max = thresh_max;
        this->op_value = op_value;
        this->op_max = op_max;
    }
};
/*12.阈值化*/
void section12()
{
    Mat src,gray;
    src = imread("D:/workspace/image/woman.jpg", IMREAD_COLOR);
    if (!src.data) {
        printf("load error!");
        return;
    }
    namedWindow("input", CV_WINDOW_AUTOSIZE);
    imshow("input", src);

    cvtColor(src, gray, CV_BGR2GRAY);

    int thresh_value = 127; // 自定义阈值初始值
    int thresh_max = 255; // max threshold
    int op_value = 0; // 对应THRESH_BINARY,二值化
    int op_max = 4;
    char title[] = "threshold window"; // title基本就是当常量用,就不传参了
    UserData userdata(&gray, &thresh_value, &thresh_max, &op_value, &op_max);

    // 声明两个Trackbar的回调函数,分别控制阈值及操作类型
    void CallBack_Thresh(int, void*);
    void CallBack_Operate(int, void*);

    // 设置TrackBar
    namedWindow(title, CV_WINDOW_AUTOSIZE);
    createTrackbar("threshold_value:", title, &thresh_value, thresh_max, CallBack_Thresh, static_cast<void*>(&userdata));
    createTrackbar("threshold_op:", title, &op_value, op_max, CallBack_Operate, static_cast<void*>(&userdata));

    // 主动调用一下,让图显示出来,以触发后续的回调
    CallBack_Thresh(thresh_value, (void*)(&userdata));

    waitKey(0);
}
// section12,阈值化中Trackbar,用于threshold调节
void CallBack_Thresh(int threshold_value, void * data) {
    // 传进来来的数据类型还原
    UserData * userdata = (UserData*)(data);
    // 每次在trackbar中调整了value,回去调整主调处的value。
    // 这一步我猜不用做,因为createTrackbar中value参数是int*类型,估计内部做了这个操作了。
    *(userdata->thresh_value) = threshold_value; 

    Mat dst;
    threshold( *(userdata->gray), dst, *(userdata->thresh_value), *(userdata->thresh_max), *(userdata->op_value));
    imshow("threshold window", dst);
}
// threshold operate的trackbar回调函数
void CallBack_Operate(int op_value, void * data) {
    UserData * userdata = (UserData*)(data);
    Mat dst;
    threshold(*(userdata->gray), dst, *(userdata->thresh_value), *(userdata->thresh_max), *(userdata->op_value));
    imshow("threshold window", dst);
}

结果图(依次为:原图,二值化,截断,反取零):

94MjqP.png


自定义线性滤波

卷积:使用kernel(卷积核)覆盖到图像上的某一个图块,然后覆盖的区域做乘加操作(卷积在离散函数上的操作),结果放在卷积核对应的锚点(通常是kernel的中心点)上。使该操作覆盖到图像的每一个像素上。

作用:增强、模糊、锐化、边缘检测。

API:filter2D

常见算子

  • Robert
  • Sobel
  • Laplace

实验如下:

/*自定义线性滤波*/
void section13()
{
    Mat src, gray;
    src = imread("D:/workspace/image/lena.jpg", IMREAD_COLOR);
    //pyrDown(src, src);
    cvtColor(src, src, CV_BGR2GRAY);
    if (!src.data) {
        printf("load error!");
        return;
    }
    namedWindow("input", CV_WINDOW_AUTOSIZE);
    imshow("input", src);

/*
    // Robert算子
    Mat robx, roby;
    // robert x和y 方向算子
    Mat k_rob_x = (Mat_<int>(2, 2) << 1, 0, 0, -1);
    Mat k_rob_y = (Mat_<int>(2, 2) << 0, 1, -1, 0);
    filter2D(src, robx, -1, k_rob_x);
    filter2D(src, roby, -1, k_rob_y);
    imshow("robert x", robx);
    imshow("robert y", roby);
    // 这里存在问题:数据溢出处理
    imshow("robert x+y", robx + roby);
*/

/*
    // Sobel算子
    Mat sobx, soby;
    Mat k_sob_x = (Mat_<int>(3, 3) << -1, 0, 1, -2, 0, 2, -1, 0, 1);
    Mat k_sob_y = (Mat_<int>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1);
    filter2D(src, sobx, -1, k_sob_x);
    filter2D(src, soby, -1, k_sob_y);
    imshow("sobel x", sobx);
    imshow("sobel y", soby);
    imshow("sobel x+y", sobx + soby);
*/

    // Laplace算子
    Mat lap;
    Mat k_lap = (Mat_<int>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
    filter2D(src, lap, -1, k_lap);
    imshow("laplace", lap);

    waitKey(0);
}

结果图:

  • robert算子,观察头发部分。

    95iqyD.png

  • sobel算子

    95i4oR.png

  • Laplace算子

    95ioJx.png


卷积边缘问题

卷积的时候发现,图像的边界上存在一些行列,无法进行卷积处理(kernel覆盖的时候,若锚点覆盖到该区域像素上,会发生kernel超出原图的情况)。此时Opencv提供几种处理方案,它默认是BORDER_DEFAULT。

  • BORDER_DEFAULT。
  • BORDER_CONSTANT-填充边缘用指定的像素值,值由函数最后一个参数指定。
  • BORDER_REPLICATE-用边缘的像素扩充复制
  • BORDER_WRAP-用另外一边的像素来补偿填充

当然也可以自己给图像添加边缘:copyMakeBoder

代码如下:

/*14.卷积边缘处理*/
void section14() {
    Mat src,dst;
    src = imread("D:/workspace/image/lena.jpg", IMREAD_COLOR);
    if (!src.data) {
        printf("load error!");
        return;
    }
    namedWindow("input", CV_WINDOW_AUTOSIZE);
    imshow("input", src);

    char c;
    int bordertype = BORDER_DEFAULT;
    int bordersize = src.cols*0.1;
    RNG rng(12345);
    while (1) {
        c = waitKey(500);
        if (c == 27){ // ESC pressed to quit
            break;
        }
        if (c == 'c') {
            bordertype = BORDER_CONSTANT;
        }
        else if (c == 'r') {
            bordertype = BORDER_REFLECT;
        }
        else if (c == 'd') {
            bordertype = BORDER_DEFAULT;
        }
        else if (c == 'a') {
            bordertype = BORDER_WRAP;
        }
        // 为图像添加边缘
        Scalar color(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255)); // color 仅在 BORDER_CONSTANT下作用
        copyMakeBorder(src, dst,bordersize, bordersize, bordersize, bordersize,bordertype,color);
        imshow("border demo", dst);
    }
}

效果图

95FFOg.png
95Fi6S.png
95FPl8.png


Sobel算子

图像边缘提取:边缘的特征——亮度突变,相对(邻域像素)而言,该点值突变,体现到函数上,就是导数值很大。同理,物体内部(表面)通常导数值较小,体现到图像上就是值相近,平滑。

Sobel:离散的微分算子,用来计算图像灰度的近似梯度。集合了高斯平滑和微分求导。(求导在离散上的运算)。

梯度: G=G2x+G2y G = G x 2 + G y 2 ,但是在图像求导中,我们关注的是值得变化率,而不是变化方向(变大还是变小),即导数得量值是重点,符号无所谓。且计算机计算得时候开放、平方操作开销太大,故用以下公式近似梯度。

G=|Gx|+|Gy| G = | G x | + | G y |

附:Sobel在kernel=3时不是很准确,Opencv采用了改进得Scharr函数:kernel=(Mat_<int>(3,3)<<-3,0,3,-10,0,10,-3,0,3);

方法:cv::sobel,cv::scharr。

注:一切梯度算子对噪声都很敏感,故都需要先做去噪处理。

代码如下

/*15.Sobel算子*/
void section15() {
    Mat src, dst;
    src = imread("D:/workspace/image/woman.jpg", IMREAD_COLOR);
    pyrDown(src, src);
    if (!src.data) {
        printf("load error!");
        return;
    }
    namedWindow("input", CV_WINDOW_AUTOSIZE);
    imshow("input", src);

    // blur
    GaussianBlur(src, src, Size(3, 3), 0, 0);
    // gray
    cvtColor(src, src, CV_BGR2GRAY);
    imshow("blured_gray", src);

    // 1.若不convertScaleAbs,显示图像处问题,因为sobel操作之后会有负值。
    // 2.Sobel函数得第三个类型为输出数据类型,一定要范围大于等于输入图像。但若等于输入图像(-1),可能发生数据截断
    // 3.Sobel函数得第4、5个参数为卷积模板选择,x还是y方向,不能同时选中(1),结果很差,不知道内部源码如何处理得.
    Mat sobelx, sobely,sobelxy;
    Sobel(src, sobelx, CV_16S, 1, 0);
    Sobel(src, sobely, CV_16S, 0, 1);
    convertScaleAbs(sobelx, sobelx);
    convertScaleAbs(sobely, sobely);
    imshow("x", sobelx);
    imshow("y", sobely);

    // 存在溢出
    sobelxy = sobelx + sobely;
    imshow("xy+", sobelxy);

    addWeighted(sobelx, 0.5, sobely, 0.5, 0, sobelxy);
    imshow("xy_add", sobelxy);

    waitKey(0);
}

9IVRk4.png


Laplace算子

Sobel算子的意义是函数的一阶求导,通过导数的最大值可以认为是图像边缘。而Laplace是二阶求导。它采用的是L=X偏导+Y偏导。opencv提供api:cv::Laplace。

流程:gray—blur—laplace—convertScaleAbs—result

代码如下:

/*16.Laplace*/
void section16() {
    Mat src,gray,lap;
    src = imread("D:/workspace/image/lena.jpg", IMREAD_COLOR);
    if (!src.data) {
        printf("load error!");
        return;
    }
    // 显示原图
    namedWindow("input", CV_WINDOW_AUTOSIZE);
    imshow("input", src);

    // gray
    cvtColor(src, gray, CV_BGR2GRAY);
    GaussianBlur(gray, gray, Size(3, 3),0);

    Laplacian(gray, lap, CV_16S, 3);
    convertScaleAbs(lap, lap);
    //threshold(lap, lap, 0, 255, THRESH_OTSU | THRESH_BINARY);
    imshow("Laplace", lap);

    waitKey(0);
}

结果

9I6DoV.png


Canny边缘检测

步骤:Blur-Gray-计算梯度(sobel、scharr)-非最大信号抑制-高低阈值输出二值图像。

Canny算法提出了3个边缘检测的准则:信噪比准则(低错误率)、定位精度准则、单边缘响应准则。

非最大信号抑制:用来解决定位精度问题。问题产生是由于一般的求边缘的微分算子,结果中的边缘总是粗线条的,而非最大信号抑制是根据梯度方向,抑制该方向上的较小的值,只保留最大的梯度值。比如连续的三个像素梯度分别为3,4,5,且方向一致。那么保留5那个像素,3、4抑制为0,这样结果就是精细的边缘线条。至于方向,用arctan(x偏导/y偏导)即可求出,再就近取到0,45,90,135,180,225,270,315,360,上,表达了它指向8个邻域的方向。

高低阈值连接:设置高低阈值T1,T2,小于低阈值的舍弃,认为是可能的误检测,高于高阈值的,认为是强边缘,保留。

API:cv::Canny

参考:Canny算法

代码如下:

/*17.Canny算法*/
void canny()
{
    Mat src, gray, can;
    src = imread("D:/workspace/image/lena.jpg", IMREAD_COLOR);
    if (!src.data) {
        printf("load error!");
        return;
    }
    // 显示原图
    namedWindow("input", CV_WINDOW_AUTOSIZE);
    imshow("input", src);

    cvtColor(src, gray, CV_BGR2GRAY);
    GaussianBlur(gray, gray, Size(3, 3), 0);

    // Canny中的双阈值,这里设置低阈值为ThreshLow通过TrackBar调节。高阈值通过阈值比为2:1求得(通常为2:1或3:1)
    int threshLow = 30; // default = 30
    Mat * userdata[] = { &can, &gray }; // 用来传递callback中使用到的用户数据
    namedWindow("Canny", CV_WINDOW_AUTOSIZE);
    void CallBack_Canny(int, void*);    // 函数声明
    createTrackbar("ThreshLow", "Canny", &threshLow, 255, CallBack_Canny, userdata);
    // 普通调用函数显示图像
    CallBack_Canny(threshLow, userdata);

    waitKey(0);
}
// canny trackbar callback.(注:这是一个普通函数,可用户调用。同时又是一个回调函数,可通过trackbar自动触发)
void CallBack_Canny(int threshLow, void* userdataArr) {
    // 这里语法问题,调试了很久。关键:userdataArr是mat*的二维数组。简单说就是[]和*算是一对等价的记号。数组[]还原的时候不知道长度,所以用*,这样就变成了Mat**
    Mat ** userdata = (Mat**)userdataArr;
    Mat * can = *(userdata+0); // 二维数组第一行(还是一个数组),实际只有一个元素
    Mat * gray = *(userdata+1);// 同理,第二行,内部也只有一个元素,所以可以对它直接取值操作就是gray
    // 后两个参数分别是
    // Sobel的windowsize,取默认的3,窗口为3模板进行梯度计算
    // 最后一个参数,取默认的false,意为求梯度时用近似计算(范数为1的公式),而true为标准的2范数的公式
    Canny(*gray, *can, threshLow, threshLow * 2, 3, false);
    // 显然这里*gray可以用gray[0],*can可以用can[0]
    imshow("Canny", *can);
}

结果图:

9IbUln.png


霍夫变换(提取直线)

霍夫变换的本质是直角坐标系到极坐标中。

前提:边缘检测已经完成。

原理:对于一条直线。直线上的点做变换:X cos(theta) + y sin(theta) = r ,r表示该直线到原点的距离,theta为r与x轴夹角。

image

API:

  • HoughLines霍夫变换,将灰度图变换到霍夫空间中。需要自己反变换到平面坐标系。

  • HoughLinesP,直接返回霍夫变换找到的直线的两端点。

    void HoughLinesP(InputArray image, OutputArray lines, double rho, double theta, int threshold, doubleminLineLength=0, double maxLineGap=0 )

    • image – 8-bit, single-channel binary source image. The image may be modified by the function. 输入的图像应该是8为单通道二值图像
    • lines – Output vector of lines. Each line is represented by a 4-element vector (x_1, y_1, x_2, y_2) , where(x_1,y_1) and (x_2, y_2) are the ending points of each detected line segment. 输出的矢量。输出的两点是线段的两个端点 .
    • rho – Distance resolution of the accumulator in pixels. 极径参数的距离分辨率
    • theta – Angle resolution of the accumulator in radians.
      极角参数的角度分辨率
    • threshold – Accumulator threshold parameter. Only those lines are returned that get enough votes (>\texttt{threshold} ). 设定的阈值,大于此阈值的交点,才会被认为是一条直线
    • minLineLength – Minimum line length. Line segments shorter than that are rejected.
      线段的最小长度
    • maxLineGap – Maximum allowed gap between points on the same line to link them.
      点到直线被允许的最大距离

参考:

opencv中的标准霍夫线变换(HoughLines)和统计霍夫变换(HoughLinesP)

霍夫变换

霍夫变换(Hough)-Matlab

代码:

/*18.Hough变换-直线检测*/
void hough() {
    Mat src, gray, can, hou;
    src = imread("D:/workspace/image/hough_test.png", IMREAD_COLOR);
    if (!src.data) {
        printf("load error!");
        return;
    }

    cvtColor(src, gray, CV_BGR2GRAY);
    GaussianBlur(gray, gray, Size(3, 3), 0);
    int threshLow = 70;
    Canny(gray, can, threshLow, threshLow * 2, 3, false);

    Mat result = Mat::zeros(src.size(), src.type());
    vector<Vec4i> lines;
    HoughLinesP(can, lines, 1, CV_PI / 180, 80, 30, 8);
    for (size_t i = 0; i < lines.size(); i++)
    {
        line(result, Point(lines[i][0], lines[i][1]),Point(lines[i][2], lines[i][3]), Scalar(0, 0, 255), 1, 8);
    }

    imshow("gray", gray);
    imshow("can", can);
    imshow("result", result);

    waitKey(0);
}

效果图

9oU8QP.png


霍夫变换(曲线检测)

原理和直线检测一样,其实本质就是方程的坐标变换。对于直线也好,圆也好,都不只有一种表达方式,他们在不同的表达形式下,存在某些特性。

HoughCircles

9TuNz4.png

代码

/*19.Hough检测圆弧*/
void hough_circle() {
    Mat src, gray, hou;
    src = imread("D:/workspace/image/hough_circle_test.png", IMREAD_COLOR);
    if (!src.data) {
        printf("load error!");
        return;
    }

    cvtColor(src, gray, CV_BGR2GRAY);
    GaussianBlur(gray, gray, Size(3, 3), 0);

    Mat result = src.clone();
    // find circle
    vector<Vec3f> circles;
    HoughCircles(gray, circles, CV_HOUGH_GRADIENT, 1, 10, 100, 30, 5, 50);
    for (int i = 0; i < circles.size(); i++) {
        Vec3f cir =  circles[i];
        circle(result, Point(cir[0], cir[1]), cir[2], RED, 2, LINE_AA);
        circle(result, Point(cir[0], cir[1]), 2, RED, 1);
    }

    imshow("input", src);
    imshow("gray", gray);
    imshow("result", result);

    waitKey(0);
}

效果

9TutWF.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值