概述:OpenCV 介绍与环境#
OpenCV 介绍#
- opencv 计算机视觉开源库,算法涉及图像处理与机器学习。
- Intel 公司贡献,俄罗斯工程师贡献大部分 C/C++ 代码。
- BSD 许可,可免费商用。
- SDK 支持 Java,Python,IOS,Android。
OpenCV 框架#
示例代码#
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main() {
Mat srcImage = imread("/Users/kanikig/Documents/CProjects/test/test.jpg");
if (!srcImage.data) {
std::cout << "Image not loaded";
return -1;
}
imshow("[img]", srcImage);
waitKey(0);
return 0;
}
加载、修改、保存图像#
色彩空间:
- 位图
- 灰度
- RGB 真彩色
- CMYK
- HSV
- YUV
加载图像#
Mat srcImage = imread(filename, int flags)
flags =
IMREAD_UNCHANGED (<0) 表示加载原图,不做任何改变
IMREAD_GRAYSCALE (0)表示把原图作为灰度图像加载进来
IMREAD_COLOR (>0) 表示把原图作为RGB图像加载进来(default)
显示图像#
namedWindow("窗口1", CV_WINDOW_NORMAL);
/*
参数1:窗口的名字
参数2:窗口类型,CV_WINDOW_AUTOSIZE 时表明窗口大小等于图片大小。不可以被拖动改变大小。
CV_WINDOW_NORMAL 时,表明窗口可以被随意拖动改变大小。
*/
imshow("窗口1", srcImage); //在“窗口1”这个窗口输出图片。
修改图像#
Mat gray_image;
cvtColor(image, gray_image, COLOR_RGB2GRAY);
保存图像#
imwrite(filename, Input_img, param);
/*
保存图像文件到指定目录路径
只有8位、16位的PNG、JPG、Tiff文件格式而且是单通道或者三通道的BGB的图像才可以通过这种方式保存
保存PNG格式的时候可以保存透明通道的图片
可以指定压缩参数
*/
矩阵的掩膜操作#
获取像素指针#
//确定图像深度,若为false报错
CV_Assert(myImage.depth() == CV_8U);
//获取当前行指针(row从0开始)
const uchar* current= Image.ptr<uchar>(row);
//获得当前像素点像素值
p(row,col) =current[col];
//像素范围处理,保证RGB在0-255
saturate_cast<ucahr>();
掩膜(mask)操作#
//定义掩膜
Mat kernel = (Mat_<char>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
//掩膜操作
filter2D(src, dst, src.depth(), kernel);
//程序计时
t = (double)getTickCount();
t = ((double)getTickCount() - t)/getTickFrequency();
Mat 对象#
Mat 对象使用#
//构造函数
Mat dst;
dst = Mat(int rows, int cols, int type);
dst = Mat(Size size, int type);
//等价于
dst.create(Size size, int type);
//其中前两个参数分别表示行(row)跟列(column)、第三个CV_8UC3中的8表示每个通道占8位、U表示无符号、C表示Char类型、3表示通道数目是3,第四个参数是向量表示初始化每个像素值是多少,向量长度对应通道数目一致
dst = Mat M(2,2,CV_8UC3, Scalar(0,0,255))
dst = Scalar(127, 0, 255); //将图像设置成单一灰度和颜色
//常用方法
Image.cols;
Image.rows;
Image.copyTo(mat);
Image.clone();
Image.convertTo(Mat dst, int type);
Image.channels(); //RGB=3, 灰度图=1
Image.depth(); //一般写-1
Image.empty();
uchar* ptr(row);
Mat dst = src.clone();
src.copyTo(dst);
Mat 定义数组#
Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
eye(int rows, int cols, int type); //生产单位矩阵
图像操作#
读写图像#
imread();
imwrite();
读写像素#
int px = Image.at<uchar>(row, col);
修改像素#
int height = image.rows;
int width = image.cols;
int channels = image.channels();
printf("height=%d width=%d channels=%d", height, width, channels);
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
if (channels == 3) {
image.at<Vec3b>(row, col)[0] = 0; // blue
image.at<Vec3b>(row, col)[1] = 0; // green
}
}
}
//Vec3b对应三通道的顺序是blue、green、red的uchar类型数据。Vec3f对应三通道的float类型数据
//把CV_8UC1转换到CV32F1实现如下:src.convertTo(dst, CV_32F);
//反色
bitwise_not(src, dst);
图像混合#
线性混合理论#
相关 API#
AddWeighted( const CvArr* src1, double alpha,const CvArr* src2, double beta,double gamma, CvArr* dst );
/*
参数1:src1,第一个原数组
参数2:alpha,第一个数组元素权重
参数3:src2第二个原数组
参数4:beta,第二个数组元素权重
参数5:gamma,图1与图2作和后添加的数值(默认0)。不要太大,不然图片一片白。总和等于255以上就是纯白色了。
参数6:dst,输出图片
*/
调整亮度与对比度#
理论#
示例#
Mat new_image = Mat::zeros( image.size(), image.type() ); //创建一张跟原图像大小和类型一致的空白图像、像素值初始化为0
saturate_cast<uchar>(value) //确保值大小范围为0~255之间
Mat.at<Vec3b>(y,x)[index]=value //给每个像素点每个通道赋值
int height = image.rows;
int width = image.cols;
double alpha = 1.2;
double beta = 50;
output = Mat::zeros(image.size(), image.type());
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
output.at<Vec3b>(row, col)[0] = saturate_cast<uchar>(alpha * image.at<Vec3b>(row, col)[0] + beta); //blue
output.at<Vec3b>(row, col)[1] = saturate_cast<uchar>(alpha * image.at<Vec3b>(row, col)[1] + beta); //green
output.at<Vec3b>(row, col)[2] = saturate_cast<uchar>(alpha * image.at<Vec3b>(row, col)[2] + beta); //red
}
}
}
绘制形状和文字#
Point 与 Scalar#
Point p;
p.x = 10;
p.y = 8;
//等价于
p = Point(10,8);
Scalar(B, G, R);
绘制形状#
(LINE_4, LINE_8, LINE_AA) //AA反锯齿
line(frame, beginPoint, endPoint, Scalar(0, 0, 255), 2); //起点为beginPoint,终点是endPoint,颜色是红色,线宽是2,shift为默认值
Rect rect = Rect(200, 100, 300, 300); // 起始xy,宽高
rectangle(Image, rect, color, 2, LINE_8);
eclipse(Image, Point(Image.cols/2, Image.rows/2), Size, 90, 0, 360, color, 2);
circle(central point, radius, color, 2);
//多边形
Point pts[1][5];
pts[0][0] = Point(100,100);
fillpoly(Image, in, out, 1, color, 8);
文字#
putText(
cv::Mat& img, // 待绘制的图像
const string& text, // 待绘制的文字
cv::Point origin, // 文本框的左下角
int fontFace, // 字体 (如cv::FONT_HERSHEY_PLAIN)
double fontScale, // 尺寸因子,值越大文字越大
cv::Scalar color, // 线条的颜色(RGB)
int thickness = 1, // 线条宽度
int lineType = 8, // 线型(4邻域或8邻域,默认8邻域)
bool bottomLeftOrigin = false // true='origin at lower left'
);
随机绘制#
RNG rng(12345);
Point pt1;
Point pt2;
for (int i =0; i < 1000; i++){
pt1.x = rng.uniform(0, Image.cols);
pt1.y = rng.uniform(0, Image.rows);
pt2.x = rng.uniform(0, Image.cols);
pt2.y = rng.uniform(0, Image.rows);
Scalar color = Scalar(rng.uniform(0,255), rng.uniform(0,255), rng.uniform(0,255));
if (wairKey(50) > 0){
break; //50秒循环一次,直到按键
}
line(Image, pt1, pt2, color, 1, 8);
}
模糊图像一#
原理#
示例#
blur(Mat src, Mat dst, Size(xradius, yradius), Point(-1,-1));
GaussianBlur(Mat src, Mat dst, Size(11, 11), sigmax, sigmay); //Size(x, y), x, y 必须是正数而且是奇数
图像模糊二#
中值滤波#
双边滤波#
示例#
medianBlur(Mat src, Mat dest, ksize);
bilateralFilter(src, dest, 15, 100, 3);
/* 15 –计算的半径d,半径之内的像数都会被纳入计算,如果提供-1 则根据sigma space参数取值
100 – sigma color 决定多少差值之内的像素会被计算
3 – sigma space 如果d的值大于0则声明无效,否则根据它来计算d值
中值模糊的ksize大小必须是大于1而且必须是奇数。
*/
膨胀与腐蚀#
腐蚀#
膨胀#
示例#
kernel = getStructuringElement(int shape, Size ksize, Point anchor);
/*
- 形状 (MORPH_RECT \MORPH_CROSS \MORPH_ELLIPSE)
- 大小 奇数 Size(1,1);
- 锚点 默认是Point(-1, -1)意思就是中心像素
*/
dilate(src, dst, kernel);
erode(src, dst, kernel);
//动态调整结构元素大小
createTrackbar(const String & trackbarname, const String winName, int* value, int count, Trackbarcallback func, void* userdata=0);
/*
形式参数一、trackbarname:滑动空间的名称;
形式参数二、winname:滑动空间用于依附的图像窗口的名称;
形式参数三、value:初始化阈值;
形式参数四、count:滑动控件的刻度范围;
形式参数五、TrackbarCallback是回调函数,其定义如下:
*/
void (CV_CDECL *TrackbarCallback)(int pos, void* userdata);
demo#
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
Mat src, dst;
char OUTPUT_WIN[] = "output image";
int element_size = 3;
int max_size = 21;
void CallBack_Demo(int, void*);
int main(int argc, char** argv) {
src = imread("D:/vcprojects/images/test1.png");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
namedWindow("input image", CV_WINDOW_AUTOSIZE);
imshow("input image", src);
namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
//上一与下二参数必须一致
createTrackbar("Element Size :", OUTPUT_WIN, &element_size, max_size, CallBack_Demo);
CallBack_Demo(0, 0);
waitKey(0);
return 0;
}
void CallBack_Demo(int, void*) {
int s = element_size * 2 + 1;
Mat structureElement = getStructuringElement(MORPH_RECT, Size(s, s), Point(-1, -1));
// dilate(src, dst, structureElement, Point(-1, -1), 1);
erode(src, dst, structureElement);
imshow(OUTPUT_WIN, dst);
return;
}
形态学操作#
开 open#
闭 close#
形态学梯度 Morphological Gradient#
顶帽 top hat#
黑帽 black hat#
示例#
Mat kernel = getStructuringElement(MORPH_RECT, Size(11,11), Point(-1, -1));
morphologyEx(src, dst, CV_MOP_BLACKHAT, kernel);
/*
- Mat src – 输入图像
- Mat dest – 输出结果
- int OPT – CV_MOP_OPEN/ CV_MOP_CLOSE/ CV_MOP_GRADIENT / CV_MOP_TOPHAT/ CV_MOP_BLACKHAT 形态学操作类型
Mat kernel 结构元素
int Iteration 迭代次数,默认是1
*/
形态学操作 提取水平垂直线#
原理#
实现思路#
示例#
//二值化
adaptiveThreshold(
Mat src, // 输入的灰度图像
Mat dest, // 二值图像
double maxValue, // 二值图像最大值
int adaptiveMethod // 自适应方法,只能其中之一 –
// ADAPTIVE_THRESH_MEAN_C , ADAPTIVE_THRESH_GAUSSIAN_C
int thresholdType,// 阈值类型
int blockSize, // 块大小
double C // 常量C 可以是正数,0,负数
);
adaptiveThreshold(~gray_src, binImg, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 15, -2);
// ~ 反色 bitwise_not(src, dst);
// 水平结构元素
Mat hline = getStructuringElement(MORPH_RECT, Size(src.cols / 16, 1), Point(-1, -1));
// 垂直结构元素
Mat vline = getStructuringElement(MORPH_RECT, Size(1, src.rows / 16), Point(-1, -1));
// 矩形结构,可以用于OCR
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(src, dst, CV_MOP_BLACKHAT, hline);
图像上采样和下采样#
图像金字塔#
示例#
pyrUp(Mat src, Mat dst, Size(src.cols*2, src.rows*2))
//生成的图像是原图在宽与高各放大两倍
pyrDown(Mat src, Mat dst, Size(src.cols/2, src.rows/2))
//生成的图像是原图在宽与高各缩小1/2
// DOG
Mat gray_src, g1, g2, dogImg;
cvtColor(src, gray_src, CV_BGR2GRAY);
GaussianBlur(gray_src, g1, Size(5, 5), 0, 0);
GaussianBlur(g1, g2, Size(5, 5), 0, 0);
subtract(g1, g2, dogImg, Mat());
// 归一化显示
normalize(dogImg, dogImg, 255, 0, NORM_MINMAX);
imshow("DOG Image", dogImg);
基本阈值操作#
阈值概念#
阈值类型#
示例#
cvtColor(src, gray_src, CV_BGR2GRAY);
threshold(src, dst, 0, 255, THRESH_TRIANGLE | type_value);
//OISU和TRIANGLE自动计算阈值,故dst后阈值写0被忽略
enum ThresholdTypes {
THRESH_BINARY = 0,
THRESH_BINARY_INV = 1,
THRESH_TRUNC = 2,
THRESH_TOZERO = 3,
THRESH_TOZERO_INV = 4,
THRESH_MASK = 7,
THRESH_OTSU = 8,
THRESH_TRIANGLE = 16
};
自定义线型滤波#
卷积概念#
常见算子#
自定义卷积模糊#
filter2D(
Mat src, //输入图像
Mat dst, // 模糊图像
int depth, // 图像深度32/8
Mat kernel, // 卷积核/模板
Point anchor, // 锚点位置
double delta // 计算出来的像素+delta
)
示例#
// Sobel X 方向
Mat kernel_x = (Mat_<int>(3, 3) << -1, 0, 1, -2,0,2,-1,0,1);
//Sobel Y 方向
Mat kernel_y = (Mat_<int>(3, 3) << -1, -2, -1, 0,0,0, 1,2,1);
// 拉普拉斯算子
Mat kernel = (Mat_<int>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
filter2D(src, dst, -1, kernel_x, Point(-1, -1), 0.0);
边缘处理#
卷积边缘问题#
处理边缘#
示例#
copyMakeBorder(
Mat src, // 输入图像
Mat dst, // 添加边缘图像
int top, // 边缘长度,一般上下左右都取相同值,
int bottom,
int left,
int right,
int borderType // 边缘类型
Scalar value
)
while(true){
c = waitKey(500);
//esc
if((char)c == 27)
{break;}
}
Sobel 算子#
卷积应用 - 边缘提取#
示例#
Sobel (
InputArray Src // 输入图像
OutputArray dst// 输出图像,大小与输入图像一致
int depth // 输出图像深度.
Int dx. // X方向,几阶导数
int dy // Y方向,几阶导数.
int ksize, SOBEL算子kernel大小,必须是1、3、5、7、
double scale = 1
double delta = 0
int borderType = BORDER_DEFAULT
)
//Sobel 的改进
Scharr (
InputArray Src // 输入图像
OutputArray dst// 输出图像,大小与输入图像一致
int depth // 输出图像深度.
Int dx. // X方向,几阶导数
int dy // Y方向,几阶导数.
double scale = 1
double delta = 0
int borderType = BORDER_DEFAULT
)
//步骤
GaussianBlur( src, dst, Size(3,3), 0, 0, BORDER_DEFAULT );
cvtColor( src, gray, COLOR_RGB2GRAY );
Scharr(gray_src, xgrad, CV_16S, 1, 0);
Scharr(gray_src, ygrad, CV_16S, 0, 1);
// Sobel(gray_src, xgrad, CV_16S, 1, 0, 3);
// Sobel(gray_src, ygrad, CV_16S, 0, 1, 3);
convertScaleAbs(xgrad, xgrad);// 计算图像A的像素绝对值,输出到图像B
convertScaleAbs(ygrad, ygrad);
addWeighted( xgrad, 0.5,ygrad, 0.5, 0, xygrad);
Laplance 算子#
理论#
示例#
Laplacian(
InputArray src,
OutputArray dst,
int depth, //深度CV_16S
int kisze, // 3
double scale = 1,
double delta =0.0,
int borderType = 4
)
Mat gray_src, edge_image;
GaussianBlur(src, dst, Size(3, 3), 0, 0);
cvtColor(dst, gray_src, CV_BGR2GRAY);
Laplacian(gray_src, edge_image, CV_16S, 3);
convertScaleAbs(edge_image, edge_image);
threshold(edge_image, edge_image, 0, 255, THRESH_OTSU | THRESH_BINARY);
Canny 边缘检测#
算法介绍#
示例#
Canny(
InputArray src, // 8-bit的输入图像
OutputArray edges,// 输出边缘图像, 一般都是二值图像,背景是黑色
double threshold1,// 低阈值,常取高阈值的1/2或者1/3
double threshold2,// 高阈值
int aptertureSize,// Soble算子的size,通常3x3,取值3
bool L2gradient // 选择 true表示是L2来归一化,否则用L1归一化
)
cvtColor(src, gray_src, CV_BGR2GRAY);
blur(gray_src, gray_src, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
Canny(gray_src, edge_output, t1_value, t1_value * 2, 3, false);
霍夫变换 - 直线检测#
理论#
示例#
HoughLines(
InputArray src, // 输入图像,必须8-bit的灰度图像
OutputArray lines, // 输出的极坐标来表示直线
double rho, // 生成极坐标时候的像素扫描步长
double theta, //生成极坐标时候的角度步长,一般取值CV_PI/180
int threshold, // 阈值,只有获得足够交点的极坐标点才被看成是直线
double srn=0;// 是否应用多尺度的霍夫变换,如果不是设置0表示经典霍夫变换
double stn=0;//是否应用多尺度的霍夫变换,如果不是设置0表示经典霍夫变换
double min_theta=0; // 表示角度扫描范围 0 ~180之间, 默认即可
double max_theta=CV_PI
) // 一般情况是有经验的开发者使用,需要自己反变换到平面空间
HoughLinesP(
InputArray src, // 输入图像,必须8-bit的灰度图像
OutputArray lines, // 输出的极坐标来表示直线
double rho, // 生成极坐标时候的像素扫描步长,一般为1
double theta, //生成极坐标时候的角度步长,一般取值CV_PI/180
int threshold, // 阈值,只有获得足够交点的极坐标点才被看成是直线
double minLineLength=0;// 最小直线长度
double maxLineGap=0;// 最大间隔
)
// extract edge
Canny(src, src_gray, 150, 200);
cvtColor(src_gray, dst, CV_GRAY2BGR);
imshow("edge image", src_gray);
vector<Vec4f> plines;
HoughLinesP(src_gray, plines, 1, CV_PI / 180.0, 10, 0, 10);
Scalar color = Scalar(0, 0, 255);
for (size_t i = 0; i < plines.size(); i++) {
Vec4f hline = plines[i];
line(dst, Point(hline[0], hline[1]), Point(hline[2], hline[3]), color, 3, LINE_AA);
}