目录
简单的变换
不那么简单的变换
cv::pyrDown()
cv::Canny() & cv::cvtColor()
从摄像头中读取
写入AVI文件
练习
许多基础的计算机视觉工作都包括对视频流使用滤波器。
一个最简单的操作就是对图像的平滑处理,这将通过高斯核或者其他核卷积效减小图像的信息量。
#include
using namespace std;
int main()
{
cv::namedWindow("Example2-5-in", cv::WINDOW_AUTOSIZE);
cv::namedWindow("Example2-5-out", cv::WINDOW_AUTOSIZE);
cv::Mat image = cv::imread("./images/girl.jpg");
cv::imshow("Example2-5-in", image);
cv::Mat out;
cv::GaussianBlur(image, out, cv::Size(5, 5), 3, 3);
cv::GaussianBlur(out, out, cv::Size(5, 5), 3, 3);
cv::imshow("Example2-5-out", out);
cv::waitKey();
cv::destroyAllWindows();
return EXIT_SUCCESS;
}
实例化一个输出矩阵out,这将由程序自行管理并在合适的时候自动分配、释放内存。
将原始图像输入到两个cv::GaussianBlur()函数中,输入图像被5*5大小的高斯核模糊并且写入out中。
注意:高斯核的大小必须时奇数,因为高斯卷积会在其覆盖区域的中心输出结果。
使用高斯模糊对一张图像实现基2的降采样。如果要进行多次的降采样,就要建立一个尺度空间(图像金字塔),这一方法是计算机视觉用于处理传感器和目标尺度变化的常用手段之一。
对信号的降采样等效于和一系列脉冲函数进行卷积(将这些函数视为“峰值”)。
在OpenCV中,高斯模糊以及降采样通过 cv::pyrDown()函数来实现。
void cv::pyrDown(
InputArray src,
OutputArray dst,
const Size & dstsize = Size(),
int borderType = BORDER_DEFAULT
)
size of the output image is computed as Size((src.cols+1)/2, (src.rows+1)/2)
, but in any case, the following conditions should be satisfied:
输出图像的大小以公式 Size((src.cols+1)/2, (src.rows+1)/2) 计算所得,但是在任何例子中,下面两个条件需要被满足。
//示例2-6 使用cv::pyrDown()来创建一个新的图像,其宽高均为原始图像的一半
#include
using namespace std;
int main()
{
cv::Mat img1, img2;
cv::namedWindow("Example1", cv::WINDOW_AUTOSIZE);
cv::namedWindow("Example2", cv::WINDOW_AUTOSIZE);
img1 = cv::imread("./images/girl.jpg");
cv::imshow("Example1", img1);
cv::pyrDown(img1, img2);
cv::imshow("Example2", img2);
cv::waitKey();
return EXIT_SUCCESS;
}
//示例2-7:Canny边缘检测器输出一个单通道的(灰度)图像
#include
using namespace std;
int main()
{
cv::Mat img_rgb, img_gry, img_cny;
cv::namedWindow("Example_Gray", cv::WINDOW_AUTOSIZE);
cv::namedWindow("Example_Canny", cv::WINDOW_AUTOSIZE);
img_rgb = cv::imread("./images/girl.jpg");
cv::cvtColor(img_rgb, img_gry, cv::COLOR_BGR2GRAY);
cv::imshow("Example_Gray", img_gry);
cv::Canny(img_gry, img_cny, 10, 100, 3, true);
cv::imshow("Example_Canny", img_cny);
cv::waitKey();
return EXIT_SUCCESS;
}
cv::Canny()
void cv::Canny(
inputArray, //image:输入图像(8-bit)
outputArray, //edges:输出图像(单通道,8-bit,size与输入图像一致)
double, //threshold1:阈值1
double, //threshold2:阈值2
int, //apertureSize = 3: Sober算子大小
bool //L2gradient = false:是否采用更精确的方式计算图像梯度
);
void cv::Canny(
inputArray, //dx:输入图像在x方向的导数:16-bit(CV_16SC1 & CV_16SC3)
inputArray, //dy:输入图像在y方向的导数:16-bit(CV_16SC1 & CV_16SC3)
outputArray, //edges:输出边缘图像(单通道,8-bit,size与输入图像一致)
double, //threshold1:阈值1
double, //threshold2:阈值2
bool //L2gradient = false:是否采用更精确的方式计算图像梯度
);
关于阈值:小于 阈值1 不是边缘,介于阈值1与阈值2之间的是弱边缘,大于阈值2的是强边缘
关于L2gradient参数:
如果为true,计算图像梯度的时候会使用:(更加准确)
如果是false,计算图像梯度的时候使用:
//示例2-8:在一个简单图像处理流程中结合图像金字塔操作(两次)和Canny边缘检测器
//示例2-9:读写示例2-8的像素值
#include
#include
using namespace std;
int main()
{
cv::Mat img_rgb, img_gry, img_cny, img_pyr, img_pyr2;
cv::namedWindow("Example_Gray", cv::WINDOW_AUTOSIZE);
cv::namedWindow("Example_Canny", cv::WINDOW_AUTOSIZE);
img_rgb = cv::imread("./images/girl.jpg");
cv::cvtColor(img_rgb, img_gry, cv::COLOR_BGR2GRAY);
cv::pyrDown(img_gry, img_pyr);
cv::pyrDown(img_pyr, img_pyr2);
cv::Canny(img_pyr2, img_cny,10, 100, 3, true);
int x = 16, y = 32;
cv::Vec3b intensity = img_rgb.at(y, x);
uchar blue = intensity[0];
uchar green = intensity[1];
uchar red = intensity[2];
cout << "At(x, y) = (" << x << "," << y << "):(blue, green, red) = (" << (unsigned int)blue << ","
<< (unsigned int)green << "," << (unsigned int)red << ")" << endl;
cout << "Gray pixel there is : " << (unsigned int)img_gry.at(y, x) << endl;
x /= 4;
y /= 4;
cout << "Gray pixel there is : " << (unsigned int)img_gry.at(y, x) << endl;
img_cny.at(x, y) = 128;
cv::waitKey();
return EXIT_SUCCESS;
}
OpenCV的HighGui模块提供了摄像头接口。
使用cv::VideoCapture()从硬盘读取视频和摄像头有一致的接口。
对于前者:只需要提供读取文件的路径
对于后者:需要给它一个相机ID号(如果只有一个摄像头连接,这个ID号通常是0),ID的默认是-1,意味着 随意选择一个摄像头。
#include
#include
#include
#include
using namespace std;
int main()
{
cv::namedWindow("Example2_10", cv::WINDOW_AUTOSIZE);
cv::VideoCapture cap;
cap.open(0);
cv::Mat frame;
if (!cap.isOpened())
{
cout << "Couldn't open capture!" << endl;
return -1;
}
while (1)
{
cap >> frame;
if (frame.empty())
{
cout << "图片写入失败" << endl;
return -1;
}
cv::imshow("Example2_10", frame);
if (cv::waitKey(33) >= 0) //手动退出,任意键退出
{
break;
}
}
cv::destroyAllWindows();
return EXIT_SUCCESS;
}
创建一个写入对象以便将帧依次输入到一个视频文件中,允许我们进行这个工作的对象是cv::VideoWriter.
一旦将其实例化,可以将每一帧图像输入到cv::VideoWriter对象中,在完成写入之后调用cv::VideoWriter.release()方法。
//示例2-11:一个完整的读取彩色视频并转换为对数极坐标视频的程序
#include
#include
using namespace std;
int main()
{
cv::namedWindow("Example2_11", cv::WINDOW_AUTOSIZE);
cv::namedWindow("Log_Polar", cv::WINDOW_AUTOSIZE);
cv::VideoCapture cap;
cap.open("./images/bike.avi");
double fps = cap.get(cv::CAP_PROP_FPS);
cv::Size size(
(int)cap.get(cv::CAP_PROP_FRAME_WIDTH),
(int)cap.get(cv::CAP_PROP_FRAME_HEIGHT)
);
cv::VideoWriter writer;
writer.open("log_bike.avi", CV_FOURCC('M', 'J', 'P', 'G'), fps, size, true);
//注意writer写入文件的名称,需要加上后缀名
cv::Mat logpolar_frame, bgr_frame;
while (1)
{
cap >> bgr_frame;
if (bgr_frame.empty())
{
break;
}
cv::imshow("Example2_11", bgr_frame);
cv::logPolar(
bgr_frame,
logpolar_frame,
cv::Point2f(
bgr_frame.cols/2,
bgr_frame.rows/2
),
40,
cv::WARP_FILL_OUTLIERS
);
cv::imshow("Log_Polar", logpolar_frame);
writer << logpolar_frame;
char c = cv::waitKey(33);
if (c == 27)
{
break;
}
}
cap.release();
return EXIT_SUCCESS;
}
cv::VideoWriter的调用参数:
第一个参数是新建视频文件的文件名;
第二个参数是视频编码方式,指明视频将以何种方式进行压缩;
第三个参数是视频的帧率;
第四个参数是视频的Size。
cv::logPolar的调用参数:Log-polor转换表示从笛卡尔坐标到极坐标的变换
void cvLogPolar(
const CvArr* src, //输入图像
CvArr* dst, //输出图像
CvPoint2D32f center, //变换的中心,输出图像在这里最清晰
double M, //幅度的尺度参数
int flags=CV_INTER_LINEAR+CV_WARP_FILL_OUTLIERS );
flags:插值方法和以下选择标志的结合
CV_WARP_FILL_OUTLIERS -填充输出图像所有像素,如果这些点有和外点对应的,则置零。
CV_WARP_INVERSE_MAP - 表示矩阵由输出图像到输入图像的逆变换,并且因此可以直接用于像素插值。否则,函数从map_matrix中寻找逆变换。
3、将示例2-11和2-6中的代码连接起来,建立一个读取视频并存储降采样后的彩色图像的程序。
4、修改练习3中的代码,并结合示例2-2中窗口显示的代码来显示处理的图像
#include
#include
#include
#include
using namespace std;
int main()
{
cv::namedWindow("Example2_11", cv::WINDOW_AUTOSIZE);
cv::namedWindow("pyr_image", cv::WINDOW_AUTOSIZE);
cv::VideoCapture cap;
cap.open("./images/bike.avi");
double fps = cap.get(cv::CAP_PROP_FPS);
cv::Size size(
(int)cap.get(cv::CAP_PROP_FRAME_WIDTH),
(int)cap.get(cv::CAP_PROP_FRAME_HEIGHT)
);
cv::VideoWriter writer;
writer.open("log_bike_pyr.avi", CV_FOURCC('M', 'J', 'P', 'G'), fps, size, true);
cv::Mat pyr_frame, bgr_frame;
while (1)
{
cap >> bgr_frame;
if (bgr_frame.empty())
{
break;
}
cv::imshow("Example2_11", bgr_frame);
cv::pyrDown(bgr_frame, pyr_frame);
cv::imshow("pyr_image", pyr_frame);
writer << pyr_frame;
char c = cv::waitKey(33);
if (c == 27)
{
break;
}
}
cap.release();
return EXIT_SUCCESS;
}
5、修改4,添加一个示例2-4中的滑动条,用户可以动态控制金字塔的降采样等级(从2-8)。可以跳过存储的步骤,但要将处理结果显示出来。
//问题:进度条的数字不能随进度条的改变而改变,初始程序无法自动播放视频,需要手动点击进度条,由于能力有限,仅完成以下部分。
#include
#include
#include
#include
using namespace std;
int g_slider_position = 2;
int g_old_position = 2;
bool g_dontset = false;
cv::VideoCapture g_cap;
cv::Mat pyr_frame, bgr_frame;
int g_frames;
int n = 0;
void onTrackbarSlide(int pos, void*)
{
while (1)
{
if (g_old_position != g_slider_position)
{
g_old_position = g_slider_position;
cv::destroyWindow("pyr_image");
cv::namedWindow("pyr_image", cv::WINDOW_AUTOSIZE);
}
if (n == g_frames - 1)
{
g_cap.set(CV_CAP_PROP_POS_FRAMES, 1);
n = 0;
}
n++;
g_cap >> bgr_frame;
if (bgr_frame.empty())
{
break;
}
cv::imshow("Example2_11", bgr_frame);
for (int i = 1; i < pos; i++)
{
cv::pyrDown(bgr_frame, bgr_frame);
}
cv::imshow("pyr_image", bgr_frame);
char c = cv::waitKey(33);
if (c == 27)
{
break;
}
}
}
int main()
{
cv::namedWindow("Example2_11", cv::WINDOW_AUTOSIZE);
cv::namedWindow("pyr_image", cv::WINDOW_AUTOSIZE);
g_cap.open("./images/bike.avi");
double fps = g_cap.get(cv::CAP_PROP_FPS);
cv::Size size(
(int)g_cap.get(cv::CAP_PROP_FRAME_WIDTH),
(int)g_cap.get(cv::CAP_PROP_FRAME_HEIGHT)
);
int frames = (int)g_cap.get(cv::CAP_PROP_FRAME_COUNT);
int tmpw = (int)g_cap.get(cv::CAP_PROP_FRAME_WIDTH);
int tmph = (int)g_cap.get(cv::CAP_PROP_FRAME_HEIGHT);
g_frames = frames;
cv::createTrackbar("Position", "Example2_11", &g_slider_position, 8, onTrackbarSlide);
cv::waitKey();
return EXIT_SUCCESS;
}