一文看懂计算机视觉-CV(基本原理+2大挑战+8大任务+4个应用) (easyai.tech)
计算机视觉的2大挑战
特征难以提取
需要计算的数据量巨大
计算机视觉的 8 大任务
图像分类
目标检测
语义分割
实例分割
视频分类
人体关键点检测
场景文字识别
目标跟踪
卷积神经网络
一文看懂卷积神经网络-CNN(基本原理+独特价值+实际应用)- 产品经理的人工智能学习库 (easyai.tech)
2大特点
有效的将大数据量的图片降维成小数据量
有效的保留图片特征,符合图片处理原则
解决了什么问题?
图片需要处理的数据量太大、导致成本很高、效率很低
数字化时很难保留原有的特征,导致准确率不高
人类视觉原理
摄入像素
初步处理:识别边缘、方向
进行抽象:大脑判断,是圆形还是什么图形
高层抽象:满足气球的特征,是气球
pixels->edges->object parts->object model
卷积神经网络-CNN 的基本原理
3个部分
卷积层:提取局部特征
池化层:降低参数数量
全连接层:类似传统神经网络(训练集和测试集,判断结果,输出结果)
卷积
使用一个过滤器(卷积核)来过滤图像的各个小区域,从而得到这些小区域的特征值。(有各种不同的卷积核)
池化层
下采样,他可以大大降低数据的维度。
池化层相比卷积层可以更有效的降低数据维度(毕竟卷积核很小),这么做不但可以大大减少运算量,还可以有效的避免过拟合。
全输出层
用处理好的数据进行神经网络运算,输出结果。
卷积神经网络一般不止3层
卷积层 – 池化层- 卷积层 – 池化层 – 卷积层 – 全连接层
CNN 的实际应用:
图片分类、检索
目标定位检测
目标分割
人脸识别
骨骼识别
cmake配置opencv
【精选】Vscode+Cmake配置并运行opencv环境(Windows和Ubuntu大同小异)_cmake配置opencv-CSDN博客
cmake_minimum_required(VERSION 3.0.0)
project(demo2 VERSION 0.1.0 LANGUAGES C CXX)
# Find OpenCV package
set(OpenCV_INCLUDE_DIRS C:\\SnowChar\\software\\opencv\\opencv\\build\\include\\opencv2)
set(OpenCV_DIR C:\\SnowChar\\software\\opencv\\opencv\\build\\x64\\vc16\\lib)
set(OpenCV_LIBRARIES C:\\SnowChar\\software\\opencv\\opencv\\build\\x64\\vc16\\lib\\opencv_world481.lib)
find_package(OpenCV REQUIRED) #添加lib
include_directories(${OpenCV_INCLUDE_DIRS}) #添加头文件include
message(${OpenCV_INCLUDE_DIRS})
message("1")
message(${OpenCV_DIR})
message("2")
message(${OpenCV_LIBRARIES})
message("3")
include(CTest)
enable_testing()
add_executable(demo2 main.cpp)
target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBRARIES})
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
lib 库
inlude 头文件
链接 主的库
后面加d的库是debug模式
Windows-MinGW-CMake-OpenCV 配置 - 知乎 (zhihu.com)
Mat类
Mat类用于存储数据,利用自动内存管理技术很好的解决了内存自动释放的问题,当变量不再需要时立即释放内存。
代码清单2-1 创建Mat类
cv::Mat a; //创建一个名为a的矩阵头
a = cv::imread(“test.jpg”); //向a中赋值图像数据,矩阵指针指向像素数据
cv::Mat b=a; //复制矩阵头,并命名为b
Mat里放的是矩阵的指针,所以改变b,a也会被改变
类型
表2-1 OpenCV中的数据类型与取值范围
数据类型 | 具体类型 | 取值范围 |
CV_8U | 8位无符号整数 | 0—255 |
CV_8S | 8位符号整数 | -128—127 |
CV_16U | 16位无符号整数 | 0-65535 |
CV_16S | 16位符号整数 | -32768—32767 |
CV_32S | 32位符号整数 | -2147483648—2147483647 |
CV_32F | 32位浮点整数 | -FLT_MAX—FLT_MAX, INF, NAN |
CV_64F | 64位浮点整数 | -DBL_MAX—DBL_MAX, INF, NAN |
通道
代码清单2-3 通过OpenCV数据类型创建Mat类
cv::Mat a(640,480,CV_8UC3) //创建一个640*480的3通道矩阵用于存放彩色图像
cv::Mat a(3,3,CV_8UC1) //创建一个3*3的8位无符号整数的单通道矩阵
cv::Mat a(3,3,CV_8U) //创建单通道矩阵C1标识可以省略
8U代表 8位 ,灰度0~225
C3代表3通道 ,R,G,B
Mat类构造与赋值
(2)根据输入矩阵尺寸和类型构造
代码清单2-5 利用矩阵尺寸和类型参数构造Mat类
cv::Mat::Mat( int rows, int cols, int type )
rows:构造矩阵的行数
cols:矩阵的列数
type:矩阵中存储的数据类型。此处除了CV_8UC1、CV_64FC4等从1到4通道以外,还提供了更多通道的参数,通过CV_8UC(n)中的n来构建多通道矩阵,其中n最大可以取到512.
代码清单2-7 用Size()结构构造Mat示例
cv::Mat a(Size(480, 640), CV_8UC1); //构造一个行为640,列为480的单通道矩阵
cv::Mat b(Size(480, 640), CV_32FC3); //构造一个行为640,列为480的3通道矩
(3)利用已有矩阵构造
代码清单2-8 利用已有矩阵构造Mat类
cv::Mat::Mat( const Mat & m);
m:已经构建完成的Mat类矩阵数据。
(1)构造时赋值
代码清单2-11 在构造时赋值的方法
cv::Mat::Mat(int rows,
int cols,
int type,
const Scalar & s
)
rows:矩阵的行数
cols:矩阵的列数
type:存储数据的类型
s:给矩阵中每个像素赋值的参数变量,例如Scalar(0, 0, 255)。
该种方式是在构造的同时进行赋值,将每个元素想要赋予的值放入Scalar结构中即可,这里需要注意的是,用此方法会将图像中的每个元素赋值相同的数值,例如Scalar(0, 0, 255)会将每个像素的三个通道值分别赋值0,0,255。我们可以使用如下的形式构造一个已赋值的Mat类
代码清单2-12 在构造时赋值示例
cv::Mat a(2, 2, CV_8UC3, cv::Scalar(0,0,255));//创建一个3通道矩阵,每个像素都是0,0,255
cv::Mat b(2, 2, CV_8UC2, cv::Scalar(0,255));//创建一个2通道矩阵,每个像素都是0,255
cv::Mat c(2, 2, CV_8UC1, cv::Scalar(255)); //创建一个单通道矩阵,每个像素都是255
(2)枚举赋值法
这种赋值方式是将矩阵中所有的元素都一一枚举出,并用数据流的形式赋值给Mat类。具体赋值形式如代码清单2-13所示。
代码清单2-13 利用枚举法赋值示例
cv::Mat a = (cv::Mat_<int>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
cv::Mat b = (cv::Mat_<double>(2, 3) << 1.0, 2.1, 3.2, 4.0, 5.1, 6.2);
(3)循环赋值
与通过枚举法赋值方法相类似,循环法赋值也是对矩阵中的每一位元素进行赋值,但是可以不在声明变量的时候进行赋值,而且可以对矩阵中的任意部分进行赋值。具体赋值形式如代码清单2-14所示。
代码清单2-14 利用枚举法赋值示例
cv::Mat c = cv::Mat_<int>(3, 3); //定义一个3*3的矩阵
for (int i = 0; i < c.rows; i++) //矩阵行数循环
{
for (int j = 0; j < c.cols; j++) //矩阵列数循环
{
c.at<int>(i, j) = i+j;
}
}
(4)类方法赋值
在Mat类里提供了可以快速赋值的方法,可以初始化指定的矩阵。例如生成单位矩阵、对角矩阵、所有元素都为0或者1的矩阵等。具体使用方法如代码清单2-15所示。
代码清单2-15 利用类方法赋值示例
cv::Mat a = cv::Mat::eye(3, 3, CV_8UC1);
cv::Mat b = (cv::Mat_<int>(1, 3) << 1, 2, 3);
cv::Mat c = cv::Mat::diag(b);
cv::Mat d = cv::Mat::ones(3, 3, CV_8UC1);
cv::Mat e = cv::Mat::zeros(4, 2, CV_8UC3);
上面代码中,每个函数作用及参数含义分别如下:
eye():构建一个单位矩阵,前两个参数为矩阵的行数和列数,第三个参数为矩阵存放的数据类型与通道数。如果行和列不相等,则在矩阵的 (1,1),(2,2),(3,3)等主对角位置处为1。
diag():构建对角矩阵,其参数必须是Mat类型的1维变量,用来存放对角元素的数值。
ones():构建一个全为1的矩阵,参数含义与eye()相同。
zeros():构建一个全为0的矩阵,参数含义与eye()相同。
对角矩阵 (diagonal matrix)是一个 主对角线 之外的元素皆为0的 矩阵 。 对角线上的元素可以为0或其他值。 对角线上元素相等的对角矩阵称为 数量矩阵 ;对角线上元素全为1的对角矩阵称为 单位矩阵 。
OTSU算法
缺陷,光照的阴影对OTSU算法影响很大
均衡化
CV_EXPORTS_W void equalizeHist( InputArray src, OutputArray dst );
Mat src = imread("F:/code/images/aero1.jpg");
if (src.empty()) {
printf("fail to read");
return -1;
}
namedWindow("input", WINDOW_AUTOSIZE);
imshow("input", src);
//直方图均衡化
Mat gray, dst;
cvtColor(src, gray, COLOR_BGR2GRAY);
imshow("gray", gray);
equalizeHist(gray, dst);//只能接受灰度图
imshow("eh-demo", dst);
//直方图计算
//设定像素取值范围
int histSize = 256;
float range[] = { 0,255 };
const float* histRanges = { range };
Mat gray_hist, dst_hist;
calcHist(&gray, 1, 0, Mat(), gray_hist, 1, &histSize, &histRanges, true, false);
calcHist(&dst, 1, 0, Mat(), dst_hist, 1, &histSize, &histRanges, true, false);
//创建直方图画布并归一化处理
Mat histImage = Mat::zeros(Size(600, 400), CV_8UC3);
int margin = 50;
int nm = histImage.rows - 2 * margin;
float bh = (float)(histImage.cols - 2 * margin) / (float)histSize;
normalize(gray_hist, gray_hist, 0, nm, NORM_MINMAX, -1, Mat());//使用最小值和最大值进行归一化处理,最小值是0,最大值是nm
normalize(dst_hist, dst_hist, 0, nm, NORM_MINMAX, -1, Mat());
//绘制前后两个统计点
for (int i = 0; i < histSize - 1; i++) {
line(histImage, Point(i * bh + margin, nm - gray_hist.at<float>(i) + margin),
Point((i + 2) * bh + margin, nm - gray_hist.at<float>(i + 1) + margin), Scalar(255, 0, 0), 1, LINE_AA);
line(histImage, Point(i * bh + margin, nm - dst_hist.at<float>(i) + margin),
Point((i + 2) * bh + margin, nm - dst_hist.at<float>(i + 1) + margin), Scalar(0, 255, 0), 1, LINE_AA);
}
imshow("histImage", histImage);
均值滤波
CV_EXPORTS_W void blur( InputArray src, OutputArray dst,
Size ksize, Point anchor = Point(-1,-1),
int borderType = BORDER_DEFAULT );
Mat src = imread("F:/code/images/test1.png");
if (src.empty()) {
printf("fail to read");
return -1;
}
namedWindow("input", WINDOW_AUTOSIZE);
imshow("input", src);
//手工进行图像卷积计算
//均值滤波
int width = src.cols;
int height = src.rows;
Mat result = src.clone();
for (int row = 1; row < height - 1; row++) {
for (int col = 1; col < width - 1; col++) {
// 3*3 卷积核
int sb = result.at<Vec3b>(row, col)[0] + result.at<Vec3b>(row - 1, col - 1)[0] + result.at<Vec3b>(row - 1, col)[0] + result.at<Vec3b>(row - 1, col + 1)[0] + result.at<Vec3b>(row, col - 1)[0] + result.at<Vec3b>(row, col + 1)[0] + result.at<Vec3b>(row + 1, col - 1)[0] + result.at<Vec3b>(row + 1, col)[0] + result.at<Vec3b>(row + 1, col + 1)[0];
int sg = result.at<Vec3b>(row, col)[1] + result.at<Vec3b>(row - 1, col - 1)[1] + result.at<Vec3b>(row - 1, col)[1] + result.at<Vec3b>(row - 1, col + 1)[1] + result.at<Vec3b>(row, col - 1)[1] + result.at<Vec3b>(row, col + 1)[1] + result.at<Vec3b>(row + 1, col - 1)[1] + result.at<Vec3b>(row + 1, col)[1] + result.at<Vec3b>(row + 1, col + 1)[1];
int sr = result.at<Vec3b>(row, col)[2] + result.at<Vec3b>(row - 1, col - 1)[2] + result.at<Vec3b>(row - 1, col)[2] + result.at<Vec3b>(row - 1, col + 1)[2] + result.at<Vec3b>(row, col - 1)[2] + result.at<Vec3b>(row, col + 1)[2] + result.at<Vec3b>(row + 1, col - 1)[2] + result.at<Vec3b>(row + 1, col)[2] + result.at<Vec3b>(row + 1, col + 1)[2];
result.at<Vec3b>(row, col)[0] = sb / 9;
result.at<Vec3b>(row, col)[1] = sg / 9;
result.at<Vec3b>(row, col)[2] = sr / 9;
}
}
imshow("mean filter My", result);
//调用API : blur 进行均值滤波
Mat dst;
blur(src, dst, Size(3,3), Point(-1,-1), BORDER_DEFAULT);
imshow("mean filter API", dst);
CV_EXPORTS_W void boxFilter( InputArray src, OutputArray dst, int ddepth,
Size ksize, Point anchor = Point(-1,-1),
bool normalize = true,
int borderType = BORDER_DEFAULT );
Mat dst;
bluer(src, dst, -1, Size(5, 5), Point(-1, -1), true, BORDER_DEFAULT);
imshow("box blur", dst);在这里插入图片描述
高斯滤波
CV_EXPORTS_W void GaussianBlur( InputArray src, OutputArray dst, Size ksize,
double sigmaX, double sigmaY = 0,
int borderType = BORDER_DEFAULT );
//高斯模糊
Mat dst;
GaussianBlur(src, dst, Size(15, 15), 0);
imshow("gaussian blur", dst);
锐化Schar算子
Mat src = imread("F:/code/images/ttt.png");
if (src.empty()) {
printf("fail to read");
return -1;
}
namedWindow("input", WINDOW_AUTOSIZE);
imshow("input", src);
// robot gradient 计算
Mat robot_x = (Mat_<int>(2, 2) << 1, 0, 0, -1);
Mat robot_y = (Mat_<int>(2, 2) << 0, 1, -1, 0);
Mat grad_x, grad_y;
filter2D(src, grad_x, CV_32F, robot_x, Point(-1, -1), 0, BORDER_DEFAULT);
filter2D(src, grad_y, CV_32F, robot_y, Point(-1, -1), 0, BORDER_DEFAULT);
convertScaleAbs(grad_x, grad_x);
convertScaleAbs(grad_y, grad_y);
Mat result;
add(grad_x, grad_y, result);//L1梯度
imshow("robot gradient", result);
// sobel and scharr
Sobel(src, grad_x, CV_32F, 1, 0);
Sobel(src, grad_y, CV_32F, 0, 1);
convertScaleAbs(grad_x, grad_x);
convertScaleAbs(grad_y, grad_y);
Mat result2;
addWeighted(grad_x, 0.5, grad_y, 0.5, 0, result2);
imshow("sobel gradient", result2);
Scharr(src, grad_x, CV_32F, 1, 0);
Scharr(src, grad_y, CV_32F, 0, 1);
convertScaleAbs(grad_x, grad_x);
convertScaleAbs(grad_y, grad_y);
Mat result3;
addWeighted(grad_x, 0.5, grad_y, 0.5, 0, result3);
imshow("scharr gradient", result3);
形态学运算开闭
Mat src = imread("F:/code/images/hist_02.jpg");
namedWindow("input", WINDOW_AUTOSIZE);
imshow("input", src);
//二值图
Mat gray, binary;
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, binary, 0, 255, THRESH_BINARY | THRESH_OTSU);
namedWindow("binary", WINDOW_AUTOSIZE);
imshow("binary", binary);
//腐蚀
Mat elementRect = getStructuringElement(MORPH_RECT, cv::Size(3, 3), cv::Point(-1, -1));//矩形
erode(binary, binary, elementRect);
namedWindow("erode", WINDOW_AUTOSIZE);
imshow("erode", binary);
//膨胀
Mat elementRect2 = getStructuringElement(MORPH_RECT, cv::Size(3, 3), cv::Point(-1, -1));//矩形
dilate(binary, binary, elementRect2);
namedWindow("dilate", WINDOW_AUTOSIZE);
imshow("dilate", src);