高级图像处理扩展实验

实验一:对视频进行物体边缘检测(Laplacian/canny)

实验一内容

读取摄像头或视频,对每一帧的图片使用拉普拉斯算子进行或canny算子进行物体边缘检测。拉普拉斯算子我们在前一篇文章中已经介绍过,现在介绍一下canny算子。

canny算子原理

首先用高斯滤波器平滑图像(去噪);
用用一阶偏导有限差分计算梯度幅值和方向,如使用Sobel算子;
对梯度幅值进行非极大值抑制,即一个点的梯度幅值如果大于该点至少两个梯度方向的梯度幅值,那么这个点的梯度就是局部极大值点,令其为0;
用双阈值算法检测和连接边缘,如果边缘像素点梯度值大于高阈值,则被认为是强边缘点;如果边缘梯度值小于高阈值,大于低阈值,则标记为弱边缘点;小于低阈值的点则被抑制掉。强边缘点可以认为是真的边缘,弱边缘点则可能是真的边缘,也可能是噪声或颜色变化引起的,为得到精确的结果,后者引起的弱边缘点应该去掉。使用深度优先算法,这个算法搜索所有连通的弱边缘,如果一条连通的弱边缘的任何一个点和强边缘点连通,则保留这条弱边缘,否则抑制这条弱边缘。

实验二:Harris角点检测

实验二内容

对图片中的物体检测其角点。

Harris角点算子原理

使用一个固定窗口在图像上进行任意方向上的滑动,比较滑动前与滑动后两种情况,窗口中的像素灰度变化程度。如果存在任意方向上的滑动,都有着较大灰度变化,那么我们可以认为该窗口中存在角点。
步骤如下:

  • 当窗口发生[u,v]移动时,那么滑动前与滑动后对应的窗口中的像素点灰度变化描述如下(u,v是窗口在水平、竖直方向的偏移):
    $$
    E(u, v)=\sum_{x, y} w(x, y)[I(x+u, y+v)-I(x, y)]^{2}
    $$
    其中[u,v]是窗口的偏移量,(x,y)是窗口内所对应的像素点,w(x,y)是窗口函数,最简单情形就是窗口内的所有像素所对应的w权重系数均为1,有时我们会将w(x,y)函数设定为以窗口中心为原点的二元正态分布。
  • 对I(x+u,y+v)进行二维泰勒级数展开,我们取一阶近似
    $$
    \begin{aligned} & \sum[I(x+u, y+v)-I(x, y)]^{2} \\ \approx & \sum\left[I(x, y)+u I_{x}+v I_{y}-I(x, y)\right]^{2} \\=& \sum u^{2} I_{x}^{2}+2 u v I_{x} I_{y}+v^{2} I_{y}^{2} \end{aligned}
    $$
    $$
    =\sum \left[ \begin{array}{ll}{u} & {v}\end{array}\right] \left[ \begin{array}{cc}{I_{x}^{2}} & {I_{x} I_{y}} \\ {I_{x} I_{y}} & {I_{y}^{2}}\end{array}\right] \left[ \begin{array}{l}{u} \\ {v}\end{array}\right]
    $$
    Ix和Iy是图像在x和y方向的导数,可以使用函数Sobel算子计算得到。
    $$
    = \left[ \begin{array}{ll}{u} & {v}\end{array}\right] \sum \left(\left[ \begin{array}{cc}{I_{x}^{2}} & {I_{x} I_{y}} \\ {I_{x} I_{y}} & {I_{y}^{2}}\end{array}\right]\right) \left[ \begin{array}{l}{u} \\ {v}\end{array}\right]
    $$
    故有
    $$
    E(u, v) \cong[u, v] M \left[ \begin{array}{l}{u} \\ {v}\end{array}\right]
    $$
    其中M:
    $$
    M=\sum_{x, y} w(x, y) \left[ \begin{array}{cc}{I_{x}^{2}} & {I_{x} I_{y}} \\ {I_{x} I_{y}} & {I_{y}^{2}}\end{array}\right]
    $$
  • 使用R来度量是否是角点,R:
    $$
    R=\operatorname{det} M-k(\operatorname{trace} M)^{2}
    $$
    $$
    \begin{aligned} \operatorname{det} M &=\lambda_{1} \lambda_{2} \\ \operatorname{trace} M &=\lambda_{1}+\lambda_{2} \end{aligned}
    $$
    k是常量,一般取值为0.04~0.06。λ1和λ2是矩阵M的特征值。根据这些特征中我们可以判断一个区域是否是角点,边界或者是平面。
  • 设定一个R的阈值。当λ1和λ2都小时,|R|也小,这个区域就是一个平坦区域;当λ1>>λ2 或者λ1<<λ2,R小于0,这个区域是边缘;当λ1和λ2都很大是,R也很大(λ1和λ2中的最小值都大于阈值),说明这个区域是角点。

实验三:基于帧间差分法的运动目标检测

实验三内容

使用帧间差分法检测运动目标。该方法只对固定摄像头(所摄的是一片固定区域)的视频有比较好的效果。

帧间差分法原理

使用后一帧灰度图像的灰度值减去前一帧灰度图像同样位置的灰度值,得到一个差值矩阵。将该矩阵做二值化处理,得到二值化图像。使用中值滤波消除小的噪声。然后使用腐蚀,将白色区域相互之间断开,再使用膨胀,填充白色区域内的小空洞。最后使用查找轮廓函数查找各个白色区域的外切矩形,获取其坐标,并再原图上根据坐标绘制框即可。对于视频的第一帧,我们使用首帧灰度图像减首帧灰度图像,后面的帧都是使用后一帧灰度图像减去前一帧灰度图像。

实验代码(三个实验)

#include "pch.h"
#include <iostream>
#include <string>
#include <math.h>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp> 
#include <opencv2/highgui/highgui.hpp> //包含imread, imshow等
#include <opencv2/imgproc/imgproc.hpp> //包含cvtColor等

using namespace std;
using namespace cv;

// 拉普拉斯算子
Mat laplacian(Mat &pScr) {
    Mat res, graypScr;
    cvtColor(pScr, graypScr, CV_BGR2GRAY);
    int row = graypScr.rows;
    int col = graypScr.cols;
    // 锐化后的图像像素值g(x,y)=f(x,y)+或-二阶差分值(即filter2D)计算后的结果,这里是-
    Mat kernel = (Mat_<float>(3, 3) << -1, -1, -1, -1, 8, -1, -1, -1, -1);
    // 使用上面的核中的权重对图像中每个像素点的像素值进行卷积计算时与下面拉普拉斯函数效果相同
    filter2D(graypScr, res, graypScr.depth(), kernel);
    // 使用拉普拉斯函数进行锐化,ksize=1时拉普拉斯算子是0,1,0,1,-4,1,0,1,0
    return res;
}

// 使用拉普拉斯算子对视频或摄像头做边缘检测
void video_edge_detect_laplacian(string load_path) {
    VideoCapture capture;
    Mat frame;
    string window_name = "laplacian_detect_video";
    if (load_path == "")
        frame = capture.open(0);
    else
        frame = capture.open(load_path);
    if (capture.isOpened()) {
        while (1) {
            capture >> frame;
            if (frame.empty())
                break;
            if (waitKey(10) >= 0)
                break;
            namedWindow(window_name, WINDOW_AUTOSIZE);
            frame = laplacian(frame);
            imshow(window_name, frame);

        }
    }
}

// 使用canny算子对视频或摄像头做边缘检测
void video_edge_detect_canny(string load_path, int lowthreshold, int highthreshold) {
    VideoCapture capture;
    Mat frame;
    string window_name = "canny_detect_video";
    if (load_path == "")
        frame = capture.open(0);
    else
        frame = capture.open(load_path);
    if (capture.isOpened()) {
        while (1) {
            capture >> frame;
            if (frame.empty())
                break;
            if (waitKey(10) >= 0)
                break;
            namedWindow(window_name, WINDOW_AUTOSIZE);
            Mat frame_gray;
            cvtColor(frame, frame_gray, CV_BGR2GRAY);
            createTrackbar("lowthreshold value:", window_name, &lowthreshold, 255, 0);
            createTrackbar("highthreshold value:", window_name, &highthreshold, 255, 0);
            Canny(frame_gray, frame_gray, lowthreshold, highthreshold);
            imshow(window_name, frame_gray);
        }
    }
}

// 使用harris角点检测算法检测角点
void harris_corner_detect(string load_path, string save_path_harris_corner_detect, int thresh) {
    Mat pScr, graypScr, res, res_norm, res_norm_scaled;
    res = Mat::zeros(pScr.size(), CV_32FC1);
    // 1为加载图像的颜色类型,1为原图返回,0为灰度返回
    pScr = imread(load_path, 1);
    cvtColor(pScr, graypScr, CV_BGR2GRAY);
    cornerHarris(graypScr, res, 2, 3, 0.04, BORDER_DEFAULT);
    /// 归一化 
    normalize(res, res_norm, 0, 255, NORM_MINMAX);
    // 转成CV8U图像
    convertScaleAbs(res_norm, res_norm_scaled);
    // 画圈圈出角点
    for (int j = 0; j < res_norm.rows; j++) {
        for (int i = 0; i < res_norm.cols; i++) {
            if ((int)res_norm.at<float>(j, i) > thresh) {
                // 在归一化后的R分数矩阵上圈出角点,Point(i, j)为圆心,5为半径,2表示线条粗细,8为线条类型,0为圆心坐标点和半径值的小数点位数
                circle(res_norm_scaled, Point(i, j), 5, Scalar(0), 2, 8, 0);
                // 在原图上圈出角点,-1表示圆被填充
                circle(pScr, Point(i, j), 5, Scalar(255, 0, 0), -1, 8, 0);
            }
        }
    }
    string window_name_1 = "harris_corner_detect_R_score_picture";
    string window_name_2 = "harris_corner_detect_picture";
    // 角点矩阵图上圈出角点和原图上圈出角点
    namedWindow(window_name_1, WINDOW_AUTOSIZE);
    imshow(window_name_1, res_norm_scaled);
    waitKey(0);
    destroyAllWindows();
    namedWindow(window_name_2, WINDOW_AUTOSIZE);
    imshow(window_name_2, pScr);
    waitKey(0);
    destroyAllWindows();
    imwrite(save_path_harris_corner_detect, pScr);
}

//  帧间差分法检测运动物体,前一帧图像和后一帧图像作差,然后二值化处理,再用中值滤波消除小噪声,再作腐蚀和膨胀
Mat move_detect(Mat background, Mat frame) {
    Mat result = frame.clone();
    // 将background和frame转为灰度图  
    Mat gray1, gray2;
    cvtColor(background, gray1, CV_BGR2GRAY);
    cvtColor(frame, gray2, CV_BGR2GRAY);
    // 将background和frame做差  
    Mat diff;
    //得到差分图像diff
    absdiff(gray1, gray2, diff);
    //imshow("diff", diff);
    // 差分图像做阈值化处理,得到处理后的二值化图像diff_thresh
    Mat diff_thresh;
    // 对单通道的图像做阈值处理,THRESH_BINARY表示当前点值大于阈值时,点置为第四个参数,否则设置为0
    threshold(diff, diff_thresh, 50, 255, CV_THRESH_BINARY);
    //imshow("diff_thresh", diff_thresh);
    // 作中值滤波,消除小的噪声
    //medianBlur(diff_thresh, diff_thresh, 3);
    //imshow("diff_thresh", diff_thresh);
    // 腐蚀,进行腐蚀操作时,将内核b划过图像,将内核b覆盖区域的最小相素值提取,并代替锚点位置的相素。
    Mat kernel_erode = getStructuringElement(MORPH_RECT, Size(3, 3));
    Mat kernel_dilate = getStructuringElement(MORPH_RECT, Size(18, 18));
    //  kernel_erode为用于腐蚀操作的结构元素,如果取值为mat(),那么默认使用一个3 x 3的方形结构元素,可以使用getstructuringelement来创建结构元素
    erode(diff_thresh, diff_thresh, kernel_erode);
    //imshow("erode", diff_thresh);
    // 膨胀  
    // dilate()函数可以对输入图像用特定结构元素进行膨胀操作,该结构元素确定膨胀操作过程中的邻域的形状,各点像素值将被替换为kernel_dilate大小邻域上的最大值
    dilate(diff_thresh, diff_thresh, kernel_dilate);
    //imshow("dilate", diff_thresh);
    // 查找轮廓并绘制轮廓  
    vector<vector<Point>> contours;
    findContours(diff_thresh, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
    // 在result上绘制轮廓
    drawContours(result, contours, -1, Scalar(0, 0, 255), 2);
    // 查找正外接矩形  
    vector<Rect> boundRect(contours.size());
    for (int i = 0; i < contours.size(); i++) {
        boundRect[i] = boundingRect(contours[i]);
        // 在result上绘制正外接矩形
        rectangle(result, boundRect[i], Scalar(0, 255, 0), 2);
    }
    // 返回result
    return result;
}

void video_move_detect(string load_path) {
    VideoCapture capture;
    // 存储当前帧和前一帧图像,以及结果图像
    Mat frame, frame_pre, result;
    string window_name = "video_move_detect";
    if (load_path == "")
        frame = capture.open(0);
    else
        frame = capture.open(load_path);
    if (capture.isOpened()) {
        while (1) {
            capture >> frame;
            if (frame.empty())
                break;
            if (waitKey(10) >= 0)
                break;
            int frame_index = capture.get(CV_CAP_PROP_POS_FRAMES);
            //如果为第一帧
            if (frame_index == 1)
                //调用MoveDetect()进行运动物体检测,返回值存入result  
                result = move_detect(frame, frame);
            //如果不是第一帧
            else
                //调用MoveDetect()进行运动物体检测,返回值存入result  
                result = move_detect(frame_pre, frame);
            frame_pre = frame.clone();
            namedWindow(window_name, WINDOW_AUTOSIZE);
            imshow(window_name, result);
        }
    }
}

int main() {
    string load_path = "C:/Users/zgcr6/Desktop/高图实验/kuozhan/save/1.avi";
    string load_path_2 = "C:/Users/zgcr6/Desktop/高图实验/kuozhan/save/2.avi";
    string load_path_image_corner_detect = "C:/Users/zgcr6/Desktop/高图实验/kuozhan/save/test_1.jpg";
    string save_path_image_corner_detect_result = "C:/Users/zgcr6/Desktop/高图实验/kuozhan/save/corner_detect_result.jpg";
    //video_edge_detect_laplacian(load_path);
    //video_edge_detect_canny(load_path,50,150);
    //harris_corner_detect(load_path_image_corner_detect, save_path_image_corner_detect_result, 100);
    video_move_detect(load_path_2);
    return 0;
}

 上一篇
视频流中的车牌区域检测 视频流中的车牌区域检测
检测原理使用形态学梯度检测或sobel边缘检测(y方向)来检测出图片中物体的垂直边缘;将检测结果转化成二值化图像;使用闭运算(先膨胀,再腐蚀)进行区域填充,填补空洞,同时切断各个白色区域之间的连通;使用轮廓检测函数findContours,
2019-05-04
下一篇 
高级图像处理基础实验与图像处理基础理论知识 高级图像处理基础实验与图像处理基础理论知识
实验1:图像灰度变换实验一内容1、利用OpenCV读取图像具体内容:用OpenCV打开图像,并在窗口中显示。2、灰度图像二值化处理具体内容:设置并调整阈值对图像进行二值化处理。3、灰度图像的对数变换具体内容:设置并调整r值对图像进行对数变换
2019-05-04
  目录