环境配置:Visual Studio 2015 + openCV3.1
opencv常用操作介绍
调用摄像头
#include <opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat frame; // Mat 即 Matrix
VideoCapture cap(0);// 摄像头,视频文件,0(default),1,2 摄像头, 文件路径都视频
while (true)
{
cap >> frame; // 读入流
// imshow函数用于显示图片或视频,并且该函数相当于一个线程
imshow("frame", frame); // 参数1--句柄
waitKey(10); // 参数单位为毫秒,为0时任意延迟, 必须加
}
system("pause");
return 0;
}
读取图片
#include <opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main() {
Mat imggray = imread("image.jpg", 1); // 参数1为图片路径,参数2:为0时是灰度图,为1时是彩色图
imshow("123", imggray);
waitKey(0);
return 0;
}
关于灰度图和彩色图:
- 灰度图是单通道存储,值为0~255
- 彩色图是三通道存储,即 R(red) G(green) B(blue)
- 需要注意的是在图像矩阵中,列数为行数的三倍,并且是以 B G R 的顺序存储的
_ 那么如何把彩色图变成灰度图呢?_
只需要一行代码就可以了
在上面代码的基础上,在imshow
函数之前加上如下一行代码
cvtColor(imggray, imggray, CV_RGB2GRAY);
cvtColor函数,其实就是 convert color,用来转换颜色的,有四个参数,前面两个参数是Mat类型,第三个参数是宏常量,这里看得出来我们传入的宏常量的作用是想让图片变成灰度图,第四个参数是一个默认值,默认为0,这里可以不用管。
我们知道图像其实都是由像素组成的,那么如果我们想获取图像的上的像素值怎么做呢?这里我们以读取图像矩阵中(1,1)上的像素为例
cout << (int) imggray.at<uchar>(1, 1) << endl; // 读取第一行第一列的像素值 ,并转换成int输出
openCV也有一些常用操作,这里总结一下:
创建一个 5 * 5 的浮点型矩阵
Mat image = Mat(5, 5 ,CV_64FC1);
Ps
CV_64FC1即单通道double型
F即float浮点型,是32位的,那么64位的double表示方法即64F_(注意在opencv中没有D的表示)_,类似的还有8位无符号字符 uchar – 8U
C 即 Channel,即通道
创建一个 5 * 5 的元素全为 0 矩阵
Mat image = Mat::zeros(5, 5, CV_64FC1);
创建一个 5 * 5 的 元素全为 1 的矩阵
Mat image = Mat::ones(5, 5, CV_64FC1);
创建一个 5 * 5 的单位矩阵
Mat image = Mat::eye(5, 5, CV_64FC1);
还有一些对于矩阵的操作
image.inv(); // 矩阵转置
image.copyTo(); // 矩阵拷贝
什么是卷积?
卷积是两个变量在某范围内相乘后求和的结果
摘自百度百科
听着挺玄乎,那么我现在以一个图来简单解释一下
在上面这个图中,我们可以看到有一个堆满数字的矩阵,这是一个二维的像素矩阵,那图中左上角的 3 * 3 的矩阵来说
__
/ \
| 0 0 0 |
| 0 1 1 |
| 0 1 2 |
\ /
矩阵中心的值(就是最中间的那个1),我们现在叫它 源像素(Source pixel)
现在又有一个卷积核(Convolution kernel),或者叫 滤波器矩阵
/ \
| 4 0 0 |
| 0 0 0 |
| 0 0 -4|
\ /
接下来我将像素矩阵和卷积核(滤波器矩阵)这两个矩阵进行逐个元素相乘求和,操作如下:
4*0 + 0*0 + 0*0 + 0*0 + 1*0 + 1*0 + 1*0 + 0*0 + 1*0 + 2*(-4) = -8
这时我们得到了一个值:-8,并且放在矩阵的中间,我们称这个值为新像素值(New pixel value)或者目标像素(Destination pixel)
那么上面的对两个矩阵逐个元素相乘求和的操作,就叫做 卷积
那么卷积能干什么呢?
我们来举个例子
下面有一幅图片
然后我想将图片变成一种浮雕的效果,如下
我们就可以利用卷积来实现该效果,但是我们先不用openCV的api操作,我们先用自己的算法实现
思路如下:
- 首先,就是对图像的矩阵进行遍历,因为是二维的,所以我们需要一个二重循环
- 其次,我们要让图像矩阵与卷积核逐个元素相乘求和,那么我们就需要又需要一个二重循环来控制
也就是说我们需要四重循环
注意
+ 整个图像的边缘是无法操作的,因为我们是对矩阵中每一个元素周围3*3的范围内进行操作,所以对图像矩阵循环遍历时,应该从1
开始,从 行或列 - 1
结束
+ 能否实现浮雕效果的关键在于这个卷积核,因为这个浮雕具有一种3d的效果,所以我们需要保证像素值一边为负,一边为正,并且形成一个对角,就会有一种侧面关照浮雕的立体效果,这里我们可以直接用上面图片中的那个3 * 3的卷积核
Mat image = imread("flower.jpg", 1); // 读取图片
cvtColor(image, image, CV_RGB2GRAY); // 将图片转换为灰度图
Mat dimage = Mat(image.rows, image.cols, CV_8UC1); // 建立一个跟image图片行,列相等,列少2的矩阵,类型为uchar
Mat model = Mat(3, 3, CV_64FC1); // 创立一个3 * 3 的矩阵,类型为double,卷积核 (滤波矩阵)
// 给model矩阵,即卷积核(滤波矩阵)每个元素赋值
model.at<double>(0, 0) = -4;
model.at<double>(0, 1) = 0;
model.at<double>(0, 2) = 0;
model.at<double>(1, 0) = 0;
model.at<double>(1, 1) = 0;
model.at<double>(1, 2) = 0;
model.at<double>(2, 0) = 0;
model.at<double>(2, 1) = 0;
model.at<double>(2, 2) = 4;
/*
利用自己的算法实现效果,思路如下:
首先,就是对图像的矩阵进行遍历,因为是二维的,所以我们需要一个二重循环
其次,我们要让图像矩阵与卷积核逐个元素相乘求和,那么我们就需要又需要一个二重循环来控制
这里需要注意的是,整个图像的边缘是无法操作的,因为我们是对矩阵中每一个元素周围3*3的范围内进行操作
*/
for (int i = 1; i < image.rows - 1; i++)
{
for (int j = 1; j < image.cols - 1; j++)
{
double sum = 0; // 加权求和
for (int m = 0; m < 3; m++)
{
for (int n = 0; n < 3; n++)
{
sum += ((double)image.at<uchar>(i + m - 1, j + n - 1) * model.at<double>(m, n));
}
}
dimage.at<uchar>(i, j) = sum; // 对dimage矩阵赋值
}
}
imshow("emboss", dimage);
waitKey(0);
那么,除了浮雕还可不可以实现其他效果呢?
比如下面这些效果
边缘检测
我们更改卷积核(滤波器),使其元素之和为0,如下
/ \
| -1 -1 -1 |
| -1 8 -1 |
| -1 -1 -1 |
\ /
代码更改:
// 给model矩阵,即卷积核(滤波矩阵)每个元素赋值
model.at<double>(0, 0) = -1;
model.at<double>(0, 1) = -1;
model.at<double>(0, 2) = -1;
model.at<double>(1, 0) = -1;
model.at<double>(1, 1) = 8;
model.at<double>(1, 2) = -1;
model.at<double>(2, 0) = -1;
model.at<double>(2, 1) = -1;
model.at<double>(2, 2) = -1;
效果图如下:
锐化
实现的关键还是在于卷积核(滤波器),
如下:
/ \
| -k -k -k |
| -k -2k+1 -k |
| -k -k -k |
\ /
即,中心比周围多一,也就是找到图像边缘的基础之上,保持图像的亮度
那么将代码中对卷积核(滤波器)的赋值更改一下:
/ \
| -1 -1 -1 |
| -1 9 -1 |
| -1 -1 -1 |
\ /
代码更改
// 给model矩阵,即卷积核(滤波矩阵)每个元素赋值
model.at<double>(0, 0) = -1;
model.at<double>(0, 1) = -1;
model.at<double>(0, 2) = -1;
model.at<double>(1, 0) = -1;
model.at<double>(1, 1) = 9;
model.at<double>(1, 2) = -1;
model.at<double>(2, 0) = -1;
model.at<double>(2, 1) = -1;
model.at<double>(2, 2) = -1;
效果图如下:
高斯模糊
高斯模糊可以有效的给图像降噪,使图像模糊的更加平滑
上图中的上方有两个公式,左边的那个是一维的计算公式,右边的那个是二维的计算公式,这里我需要用到的是二维的计算公式
代码如下:
#include<opencv.hpp>
#include<iostream>
#define PI 3.1415926535
using namespace std;
using namespace cv;
int main()
{
double sigma = 6; // 设定sigma值为6,即
Mat gauss(5, 5, CV_64FC1); // 创建一个5 * 5的卷积核
for (int i = 0; i<5; i++)
{
for (int j =0; j<5; j++)
{
gauss.at<double>(i, j) = exp(-(i*i + j*j) / (2 * sigma*sigma)) / (2 * PI * sigma * sigma); // 二维计算公式,卷积核元素赋值
}
}
// 归一化处理,目的是让权重之和为1,否则,如果权重之和大于1就太亮,小于1就太暗
double gssum = sum(gauss).val[0];
for (int i = 0; i<5; i++)
{
for (int j = 0; j<5; j++)
{
gauss.at<double>(i, j) /= gssum;
}
}
Mat frame = imread("flower.jpg", 1); // 读取图片
cvtColor(frame, frame, CV_RGB2GRAY); // 将图片转化为灰度图
//Mat dimg = Mat(frame.rows , frame.cols , CV_8UC1);
Mat dimg = Mat(frame.rows - 4, frame.cols - 4, CV_8UC1); // 因为原图像的边缘无法处理,这里就创建一个比原图像行列小四的矩阵,当然,用于原图大小相同的矩阵也是可以的
// 卷积处理,卷积核为5 * 5 的矩阵,而且图像的边缘无法处理,所以这里应该从2开始,行或列-2结束
for (int i = 2; i < frame.rows - 2; i++)
{
for (int j = 2; j < frame.cols - 2; j++)
{
double sum = 0;
for (int m = 0; m < gauss.rows; m++)
{
for (int n = 0; n < gauss.cols; n++)
{
sum += (double)(frame.at<uchar>(i + m - 2, j + n - 2)) * gauss.at<double>(m, n);
}
}
dimg.at<uchar>(i - 2, j - 2) = (uchar)sum;
}
}
imshow("gauss", dimg); // 显示图片
waitKey(10);
system("pause");
return 0;
}
原图(上图)与效果图(下图)对比情况:
相关API操作
对于刚才的算法实现,其实opencv是有相关api的操作的,下面介绍几个:
高斯模糊
GaussianBlur(image, image, cvSize(5, 5), 10, 10); // 高斯模糊
效果图如下:
跟我实现的差不多
Canny算子
Canny(image, image, 100, 100); // candy算子
效果图如下:
Sobel算子
Sobel(image, image, 0, 1, 1); // sobel 算子
效果图如下:
代码如下
Mat image = imread("flower.jpg", 1); // 读取图片
cvtColor(image, image, CV_RGB2GRAY); // 转换为灰度图
GaussianBlur(image, image, cvSize(5, 5), 10, 10); // 高斯模糊
// Canny(image, image, 100, 100); // candy算子
//Sobel(image, image, 0, 1, 1); // sobel 算子
imshow("api", image); // 显示图片
waitKey(0);