数字视频技术及应用实验一:关键帧提取与分割
实验内容
阅读实验二指导书,提取视频关键帧(镜头的第一帧为关键帧),然后分别利用分水岭算法和阈值分割算法对关键帧进行图像分割,其中。至少任选两种不同的阈值分割算法进行实验。
实验步骤
在vs中新建工程。
- 配置OpenCV。
- 读取视频。
- 计算前后两帧灰度值之差,若大于某阈值,则将该帧作为关键帧。
- 对关键帧采用阈值分割算法或分水岭算法(OpenCV中有封装好的分割函数,但建议按照原理自己实现),生成分割图像。
- 显示原视频、关键帧和分割后结果。
实验方法
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、关键帧提取和阈值分割
- 2、分水岭算法
实验总结
1、这里视频关键帧提取采取的是检测镜头的方法,b把镜头的第一帧认为是关键帧,判断镜头的方法是帧差法,通过判断帧之间的差异,达到一定的阈值被认为是一个新的镜头。
2、阈值分割,没有自己重新写OTSU等算法,直接采用opencv库函数中的算法。
3、分水岭算法,由图像的分水岭算法推及到视频,每一帧进行处理,对于图像而言,使用了很多图像处理的算法,例如灰度化、sobel算子进行边缘检测;使用队列存储被标识的像素点。
Comments | NOTHING