本文最后更新于2020年11月21日,已超过 6 个月没更新!

数字视频技术及应用实验一:关键帧提取与分割

实验内容

阅读实验二指导书,提取视频关键帧(镜头的第一帧为关键帧),然后分别利用分水岭算法和阈值分割算法对关键帧进行图像分割,其中。至少任选两种不同的阈值分割算法进行实验。

实验步骤

在vs中新建工程。

  1. 配置OpenCV。
  2. 读取视频。
  3. 计算前后两帧灰度值之差,若大于某阈值,则将该帧作为关键帧。
  4. 对关键帧采用阈值分割算法或分水岭算法(OpenCV中有封装好的分割函数,但建议按照原理自己实现),生成分割图像。
  5. 显示原视频、关键帧和分割后结果。

实验方法

1. 关键帧提取算法

  • 提取视频关键帧(镜头的第一帧为关键帧)

实现代码

Mat frame_key;
        cap >> frame_key;
        if (frame_key.empty())
            cout << "frame_key is empty!" << endl;
        waitKey(10);
        Mat frame;
        Mat previousImage, currentImage, resultImage;
        while (1)
        {

            currentFrame++;
            Mat frame;
            cap >> frame;

            if (frame.empty())
            {
                cout << "frame is empty!" << endl;
                break;
            }
            imshow("当前视频", frame);
            waitKey(10);

            Mat srcImage_base;
            Mat srcImage_test1;
            srcImage_base = frame_key;
            srcImage_test1 = frame;
            //将图像从BGR色彩空间转换到 HSV色彩空间
            cvtColor(srcImage_base, previousImage, CV_BGR2GRAY);
            cvtColor(srcImage_test1, currentImage, CV_BGR2GRAY);

            absdiff(currentImage, previousImage, resultImage);  //帧差法,相减

            threshold(resultImage, resultImage, 10, 255.0, CV_THRESH_BINARY); //二值化,像素值相差大于20则置为255,其余为0
            float counter = 0;
            float num = 0;
            // 统计两帧相减后图像素
            for (int i = 0; i < resultImage.rows; i++)
            {
                uchar* data = resultImage.ptr<uchar>(i); //获取每一行的指针
                for (int j = 0; j < resultImage.cols; j++)
                {
                    num = num + 1;
                    if (data[j] == 255) //访问到像素值
                    {
                        counter = counter + 1;
                    }
                }
            }
            p = counter / num;

            if (p > 0.6) //输出关键帧
            {
                frame_key = frame;
                imshow("关键帧视频", frame_key);
                waitKey(10);
            }
        }

2. 阈值分割算法

OTUS大津法阈值分割
自适应阈值分割
实现方法:
调用opencv内部函数:

threshold(previousImage, previousImage, 80, 250, THRESH_BINARY);
imshow("OTUS大津法阈值分割", previousImage);
adaptiveThreshold(currentImage, resultImage, 255, 0, 0, 7, 9);
imshow("自适应阈值分割", previousImage);

3. 分水岭算法

算法步骤

1) 图像灰度化。
2) 根据Sobel算子计算图像梯度(梯度范围为0-255),边缘像素的梯度记为其邻域像素的梯度。
3) 对各像素点的梯度值从小到大排序,梯度值相同的像素为同一层。
4) 处理第一层所有的像素点,如果其邻域已经被标识属于某一个区域,则将这个像素加入队列。
5) 队列非空时,弹出第一个元素。扫描该像素的邻域像素,如果其邻域像素的梯度值相等,则根据邻域像素的标识来更新该像素的标识。一直循环到队列为空。
6) 再次扫描当前梯度值层级的像素,如果还有像素未被标识,说明它是一个新的极小区域,则当前区域的值(当前区域的值从0开始计数)加1后赋值给该标识的像素。然后从该像素出发继续执行步骤5)遍历该梯度值层级的所有像素,直至没有新的极小区域。
7) 返回步骤4),处理下一个梯度值层级的像素,直至所有层级的像素都被处理。

实现代码(主要)

Vec3b RandomColor(int value)
{
    value = value % 255;  //生成0~255的随机数  
    RNG rng;
    int aa = rng.uniform(0, value);
    int bb = rng.uniform(0, value);
    int cc = rng.uniform(0, value);
    return Vec3b(aa, bb, cc);
}

void watershed(Mat& the_image)
{
    Mat image = the_image;    //载入RGB彩色图像  
//  imshow("Source Image", image);

    //灰度化,滤波,Canny边缘检测  
    Mat imageGray;
    cvtColor(image, imageGray, CV_RGB2GRAY);//灰度转换  
    GaussianBlur(imageGray, imageGray, Size(5, 5), 2);   //高斯滤波  
//  imshow("Gray Image", imageGray);
    Canny(imageGray, imageGray, 80, 150);
//  imshow("Canny Image", imageGray);

    //查找轮廓  
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(imageGray, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
    Mat imageContours = Mat::zeros(image.size(), CV_8UC1);  //轮廓     
    Mat marks(image.size(), CV_32S);   //Opencv分水岭第二个矩阵参数  
    marks = Scalar::all(0);
    int index = 0;
    int compCount = 0;
    for (; index >= 0; index = hierarchy[index][0], compCount++)
    {
        //对marks进行标记,对不同区域的轮廓进行编号,相当于设置注水点,有多少轮廓,就有多少注水点  
        drawContours(marks, contours, index, Scalar::all(compCount + 1), 1, 8, hierarchy);
        drawContours(imageContours, contours, index, Scalar(255), 1, 8, hierarchy);
    }

    //传入的矩阵marks里
    Mat marksShows;
    convertScaleAbs(marks, marksShows);
//  imshow("marksShow", marksShows);
//  imshow("轮廓", imageContours);
    watershed(image, marks);

    //分水岭算法之后的矩阵marks
    Mat afterWatershed;
    convertScaleAbs(marks, afterWatershed);
    imshow("After Watershed", afterWatershed);

    //对每一个区域进行颜色填充  
    Mat PerspectiveImage = Mat::zeros(image.size(), CV_8UC3);
    for (int i = 0; i < marks.rows; i++)
    {
        for (int j = 0; j < marks.cols; j++)
        {
            int index = marks.at<int>(i, j);
            if (marks.at<int>(i, j) == -1)
            {
                PerspectiveImage.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
            }
            else
            {
                PerspectiveImage.at<Vec3b>(i, j) = RandomColor(index);
            }
        }
    }
    imshow("After ColorFill", PerspectiveImage);

    //分割并填充颜色的结果跟原始图像融合  
    Mat wshed;
    addWeighted(image, 0.4, PerspectiveImage, 0.6, 0, wshed);
    imshow("AddWeighted Image", wshed);

    waitKey(10);

}

全部代码

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/ml/ml.hpp>
#include <string.h>

using namespace std;
using namespace cv;
using namespace std;

Vec3b RandomColor(int value)
{
    value = value % 255;  //生成0~255的随机数  
    RNG rng;
    int aa = rng.uniform(0, value);
    int bb = rng.uniform(0, value);
    int cc = rng.uniform(0, value);
    return Vec3b(aa, bb, cc);
}

void watershed(Mat& the_image)
{
    Mat image = the_image;    //载入RGB彩色图像  
//  imshow("Source Image", image);

    //灰度化,滤波,Canny边缘检测  
    Mat imageGray;
    cvtColor(image, imageGray, CV_RGB2GRAY);//灰度转换  
    GaussianBlur(imageGray, imageGray, Size(5, 5), 2);   //高斯滤波  
//  imshow("Gray Image", imageGray);
    Canny(imageGray, imageGray, 80, 150);
//  imshow("Canny Image", imageGray);

    //查找轮廓  
    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(imageGray, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point());
    Mat imageContours = Mat::zeros(image.size(), CV_8UC1);  //轮廓     
    Mat marks(image.size(), CV_32S);   //Opencv分水岭第二个矩阵参数  
    marks = Scalar::all(0);
    int index = 0;
    int compCount = 0;
    for (; index >= 0; index = hierarchy[index][0], compCount++)
    {
        //对marks进行标记,对不同区域的轮廓进行编号,相当于设置注水点,有多少轮廓,就有多少注水点  
        drawContours(marks, contours, index, Scalar::all(compCount + 1), 1, 8, hierarchy);
        drawContours(imageContours, contours, index, Scalar(255), 1, 8, hierarchy);
    }

    //传入的矩阵marks里
    Mat marksShows;
    convertScaleAbs(marks, marksShows);
//  imshow("marksShow", marksShows);
//  imshow("轮廓", imageContours);
    watershed(image, marks);

    //分水岭算法之后的矩阵marks
    Mat afterWatershed;
    convertScaleAbs(marks, afterWatershed);
    imshow("After Watershed", afterWatershed);

    //对每一个区域进行颜色填充  
    Mat PerspectiveImage = Mat::zeros(image.size(), CV_8UC3);
    for (int i = 0; i < marks.rows; i++)
    {
        for (int j = 0; j < marks.cols; j++)
        {
            int index = marks.at<int>(i, j);
            if (marks.at<int>(i, j) == -1)
            {
                PerspectiveImage.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
            }
            else
            {
                PerspectiveImage.at<Vec3b>(i, j) = RandomColor(index);
            }
        }
    }
    imshow("After ColorFill", PerspectiveImage);

    //分割并填充颜色的结果跟原始图像融合  
    Mat wshed;
    addWeighted(image, 0.4, PerspectiveImage, 0.6, 0, wshed);
    imshow("AddWeighted Image", wshed);

    waitKey(10);

}

int main()
{
    int loop;
    cout << "请输入你的选择,0阈值分割,1分水岭算法:" << endl;
    cin >> loop;
    if (loop == 0)
    {
        long currentFrame = 1;
        float p;
        VideoCapture cap;
        //这里放置需要提取关键字的视频
        cap.open("D:\\C++\\OpenCV\\exp1.avi");
        if (!cap.isOpened())//如果视频不能正常打开则返回
        {
            cout << "cannot open video!" << endl;
            return 0;
        }
        Mat frame_key;
        cap >> frame_key;
        if (frame_key.empty())
            cout << "frame_key is empty!" << endl;
        //imshow("fram_1", frame_key);
        waitKey(10);

        Mat frame;
        Mat previousImage, currentImage, resultImage;
        while (1)
        {

            currentFrame++;
            Mat frame;
            cap >> frame;

            if (frame.empty())
            {
                cout << "frame is empty!" << endl;
                break;
            }
            imshow("当前视频", frame);
            waitKey(10);

            Mat srcImage_base;
            Mat srcImage_test1;
            srcImage_base = frame_key;
            srcImage_test1 = frame;
            //将图像从BGR色彩空间转换到 HSV色彩空间
            cvtColor(srcImage_base, previousImage, CV_BGR2GRAY);
            cvtColor(srcImage_test1, currentImage, CV_BGR2GRAY);

            absdiff(currentImage, previousImage, resultImage);  //帧差法,相减

            threshold(resultImage, resultImage, 10, 255.0, CV_THRESH_BINARY); //二值化,像素值相差大于20则置为255,其余为0
            float counter = 0;
            float num = 0;
            // 统计两帧相减后图像素
            for (int i = 0; i < resultImage.rows; i++)
            {
                uchar* data = resultImage.ptr<uchar>(i); //获取每一行的指针
                for (int j = 0; j < resultImage.cols; j++)
                {
                    num = num + 1;
                    if (data[j] == 255) //访问到像素值
                    {
                        counter = counter + 1;
                    }
                }
            }
            p = counter / num;

            if (p > 0.6) //输出关键帧
            {
                frame_key = frame;
                imshow("关键帧视频", frame_key);
                waitKey(10);

                cvtColor(frame_key, previousImage, CV_BGR2GRAY);
                threshold(previousImage, previousImage, 80, 250, THRESH_BINARY);
                imshow("OTUS大津法阈值分割", previousImage);
                adaptiveThreshold(currentImage, resultImage, 255, 0, 0, 7, 9);
                imshow("自适应阈值分割", previousImage);
                waitKey(10);
            }

        }
    }
    else if (loop == 1)
    {
        long currentFrame = 1;
        float p;
        VideoCapture cap;
        //这里放置需要提取关键字的视频
        cap.open("D:\\C++\\OpenCV\\exp1.avi");
        if (!cap.isOpened())//如果视频不能正常打开则返回
        {
            cout << "cannot open video!" << endl;
            return 0;
        }
        Mat frame_key;
        cap >> frame_key;
        if (frame_key.empty())
            cout << "frame_key is empty!" << endl;
        //imshow("fram_1", frame_key);
        waitKey(10);

        watershed(frame_key);

        Mat frame;
        Mat previousImage, currentImage, resultImage;
        while (1)
        {

            currentFrame++;
            Mat frame;
            cap >> frame;

            if (frame.empty())
            {
                cout << "frame is empty!" << endl;
                break;
            }
            imshow("当前视频", frame);
            waitKey(10);

            Mat srcImage_base;
            Mat srcImage_test1;
            srcImage_base = frame_key;
            srcImage_test1 = frame;
            //将图像从BGR色彩空间转换到 HSV色彩空间
            cvtColor(srcImage_base, previousImage, CV_BGR2GRAY);
            cvtColor(srcImage_test1, currentImage, CV_BGR2GRAY);

            absdiff(currentImage, previousImage, resultImage);  //帧差法,相减

            threshold(resultImage, resultImage, 10, 255.0, CV_THRESH_BINARY); //二值化,像素值相差大于20则置为255,其余为0

            float counter = 0;
            float num = 0;
            // 统计两帧相减后图像素
            for (int i = 0; i < resultImage.rows; i++)
            {
                uchar* data = resultImage.ptr<uchar>(i); //获取每一行的指针
                for (int j = 0; j < resultImage.cols; j++)
                {
                    num = num + 1;
                    if (data[j] == 255) //访问到像素值
                    {
                        counter = counter + 1;
                    }
                }
            }
            p = counter / num;

            if (p > 0.6) //输出关键帧
            {
                frame_key = frame;
//              imshow("关键帧视频", frame_key);
                waitKey(10);
                watershed(frame_key);
//              imshow("关键帧视频", frame_key);
                waitKey(10);
            }
        }
    }
}

实验结果

  • 1、关键帧提取和阈值分割

imgbed.cn图床

  • 2、分水岭算法

imgbed.cn图床

实验总结

1、这里视频关键帧提取采取的是检测镜头的方法,b把镜头的第一帧认为是关键帧,判断镜头的方法是帧差法,通过判断帧之间的差异,达到一定的阈值被认为是一个新的镜头。
2、阈值分割,没有自己重新写OTSU等算法,直接采用opencv库函数中的算法。
3、分水岭算法,由图像的分水岭算法推及到视频,每一帧进行处理,对于图像而言,使用了很多图像处理的算法,例如灰度化、sobel算子进行边缘检测;使用队列存储被标识的像素点。


疏影横斜水清浅,暗香浮动月黄昏