学了B站贾志刚opencv课程 1,2,4,8,9,10,11,12,15,29,31章节(BV号BV1uW411d7Wf)
粗略的学习笔记
!--转换图像空间的意义与函数
RGB空间下,亮度/饱和度不是一个单独的通道,可以转换到其他颜色空间进行操作之后再转回
cv::cvtColor
三个参数,第一个表示源图像(src),第二个表示色彩空间转换之后的图像(dst),第三个表示源和目标色彩空间,如COLOR_BGR2HLS COLOR_BGR2GRAY等
!--Mat对象
void convertTo(Mat dst, int type)
数据转换,比如从8位转换到浮点数
uchar *ptr(i=0)
指针,第i行的
int channels()
通道数
int depth()
深度
bool empty()
判断是否为空
定义小数组
Mat kernel = (Mat_<float>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
filter2D(src, csrc, -1, kernel);
!--绘制形状与文字
cv::Point
二维点坐标
cv::Scalar
四个元素的向量,可包含0~4个
Scalar(a, b, c); 表示BGR三个通道
LINE_8 LINE_AA
cv::line()
cv::rectangle()
cv::Rect
shift表示小数点后的位数,一般不用改
cv::ellipse()
cv::circle()
cv::fillPoly()
putText()
!--随机数生成
RNG rng(randonSeed); //随机数种子
生成正态分布随机数
rng.gaussian(double sigma); //
生成均匀分布随机数
rng.uniform(int a, int b); //生成的数的范围
!--模糊图像
均值滤波
blur(Mat src, Mat sdt, Size(xradius, yradius), Point(-1,-1));
可能本来中心点像素很大,取平均后很小,也就是会损失图像细节,比如边缘
高斯滤波
GaussianBlur(Mat src, Mat dst, Size(x, y), sigmax, sigmay);
Size(x,y)中x,y必须是正数而且是奇数,sigmax,sigmay就是调整正态分布的sigma
一定程度上避免上述问题
但只考虑了空间位置上的权重问题,没有根据具体像素值考虑,因此还是无法完全避免
卷积核并不一定是正方形,也可以做指定方向的模糊
!--模糊图像2
中值滤波
medianBlur(Mat src, Mat dst, Size ksize);
卷积核的大小一定要是奇数
统计排序滤波器,排序后取中值
中值对椒盐噪声(一幅图像上有不必要的白点和黑点,类似老式电视那种雪花点)有很好的抑制作用,因为是极值
双边滤波
bilateralFilter(Mat src, Mat dst, int d, double sigmaColor, double sigmaSpace)
d是计算的半径,如果是-1的话则根据sigmaSpace参数取值
sigmaColor是颜色空间滤波器的sigma值,决定多少差值以内的像素会被计算
sigmaSpace是坐标空间滤波器的sigma值,如果sigmaColor的值大于0则无效,否则根据sigmaSpace计算sigmaColor
高斯双边滤波-边缘保留的滤波方法,避免了边缘信息丢失,保留图像轮廓不变(保留了图像的差异)
提升对比度
可以用filter2D()函数选取适当的kernel实现,
可以在高斯双边之后提升对比度,使边缘更加清晰
!--腐蚀与膨胀
膨胀和腐蚀的主语都是亮区
膨胀
getStructingElement(int shape, Size ksize, Point anchor);
shape有MORPH_RECT , MORPH_CROSS , MORPH_ELLIPSE ,分别是矩形,交叉形,椭圆形
ksize一定要是奇数
锚点默认为(-1,-1)
使用举例: Mat kernel = getStructingElement(MORPH_RECT, Size(5, 5));
dilate(src, dst, kernel);
结构元素B在图像A上移动,B的中心为锚点(如果不改变预设参数的话),用B覆盖下A的最大像素值替换锚点的像素
更详尽的内容参考书上P191
腐蚀
与膨胀类似,以最小值替换
erode(src, dst, kernel); 其他参数一般不改
kernel一般结合之前的getStructingElement()来用
!--形态学操作
主要是用在二值图像和灰度图像处理上的
用到的API
morphologyEx(src, dst, int opt, kernel)
opt可选
CV_MOP_OPEN
CV_MOP_CLOSE
CV_MOP_GRADIENT
CV_MOP_TOPHAT
CV_MOP_BLACKHAT
更详细的内容可看p205
开操作 -open
先腐蚀后膨胀,可以去掉小的对象,假设对象是前景色,背景是黑色
闭操作 -close
先膨胀后腐蚀,可以填充小的洞,假设对象是前景色,背景是黑色
形态学梯度 -Gradient
膨胀减去腐蚀,又称为基本梯度,还有内部梯度(腐蚀减原图)和方向梯度(x和y方向进行计算)
顶帽 -top hat
原图像与开操作的差值
可以保留小的对象
黑帽 -black hat
闭操作与原图像的差值
可以保留小的洞
!--基本阈值操作
threshold(src, dst, threshold_value, threshold_max, method);
如果要让自动选取阈值
threshold(src, dst, threshold_value, threshold_max, THRESH_OTSU | method);这样会忽略输入的阈值
阈值类型
阈值二值化
THRESH_BINARY
大于阈值取最大值,小于阈值取0
阈值反二值化
THRESH_BINARY_INV
大于阈值取0,小于阈值取最大值
截断
THRESH_TRUNC
超过阈值的部分取阈值
阈值取零
THRESH_TOZERO
小于阈值的部分取0
阈值反取零
THRESH_TOZERO_INV
大于阈值的部分取0
获取阈值的方法(图像只能是单通道八位的)
THRESH_OTSU
THRESH_TRIANGLE
!--轮廓发现 find contour
基于图像边缘提取寻找对象轮廓,边缘提取的阈值选定会影响轮廓发现结果
步骤:1.转换成灰度图像 2.Canny 3.findContours
findContours发现轮廓
drawContours绘制轮廓
findContours(src, contours, hierarchy, mode, method)
contours定义为vector<vector<Point>> contours;用于存储轮廓,每一组点的集合是一个轮廓,有多少轮廓contours就有多少元素
hierarchy用于存储拓扑信息
!--轮廓周围绘制矩形框和圆形框
步骤
将图像变为二值图像
发现轮廓
找到轮廓点最小包含矩形和圆,旋转
绘制
approxPolyDP()
基于RDP算法
减少多边形轮廓点数,可以减少边缘轮廓的点数
boundingRect()
得到轮廓周围
minAreaRect
得到一个旋转的矩形
minEnclosingCircle()
最小区域圆形
fitEllipse()
最小椭圆
!--Canny边缘检测
步骤:1.高斯模糊 2.转换成灰度图像 3.计算梯度 4.非最大信号抑制 5.高低阈值
这里面轮廓发现的hierarchy是非常有用的信息
可以参见findContours()函数
考核试题有一个链表,一个识别图片中的小鸡,一个识别视频中要打击的目标以及输出中心点
链表就不说了
1.识别小鸡
思路:
不难看出干扰项是背景中阴影,用画图提取了一下颜色发现可以用图像的R通道和B/G通道作差得出一张新图,对这张图像进行滤波二值化轮廓发现
还有中间眼睛那里,之前不知道findContours可以直接发现外轮廓……
我是用闭操作除掉的那个孔洞
此外,还可以使用HSV颜色空间,更贴近人眼视觉的实际感受
识别效果:
代码
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
Mat src, src_gray, src_bin, src_blur; //分别存储src, src的灰度图,src二值化后的图像,src滤波后的图像
Mat dst;
int thres_val = 75, thres_max = 255;
char bar_a[] = "threshold_val"; //阈值条
char win_a[] = "Settings"; //用于调整阈值的框框
RNG rng(12321);
void on_Change(int, void*);
int main(int argc, char** argv)
{
std::vector<Mat> src_chans;
Mat srcB, srcR,src_diff;
src = imread("D:/Doucuments/VisionGroup_Documents/source/chicken.jpg"); //读取原图像
split(src, src_chans);
srcB = src_chans.at(0);
srcR = src_chans.at(2);
absdiff(srcR, srcB, src_diff);
src_gray = src_diff.clone();
//cvtColor(src, src_gray, CV_BGR2GRAY); //转换成灰度图像
namedWindow("src"); //创建窗口
namedWindow("srcB");
namedWindow("srcR");
namedWindow("src_diff");
//namedWindow("gray");
imshow("src", src);
imshow("srcB", srcB);
imshow("srcR", srcR);
imshow("src_diff", src_diff);
namedWindow("blur");
namedWindow("thres");
namedWindow(win_a);
namedWindow("dst");
namedWindow("morph");
on_Change(0, 0);
createTrackbar(bar_a, win_a, &thres_val, thres_max, on_Change);
waitKey(0);
return 0;
}
void on_Change(int, void*)
{
std::vector<std::vector<Point>> contours; //用于储存轮廓发现的结果
std::vector<Vec4i> hierarchy; //用于储存轮廓发现的拓扑信息
blur(src_gray, src_blur, Size(11, 11)); //均值滤波
imshow("blur", src_blur);
threshold(src_blur, src_bin, thres_val, thres_max, THRESH_BINARY); //二值化
imshow("thres", src_bin);
Mat kernel = getStructuringElement(CV_SHAPE_ELLIPSE, Size(21, 21));
morphologyEx(src_bin, src_bin, MORPH_CLOSE, kernel); //用CLOSE操作除眼睛部位的孔
imshow("morph", src_bin);
findContours(src_bin, contours, hierarchy, RETR_CCOMP, CHAIN_APPROX_SIMPLE); //寻找轮廓
std::vector<std::vector<Point>> contours_poly(contours.size()); //用于存储多边形逼近轮廓
std::vector<Rect> boundRect(contours.size()); //用于存储矩形逼近轮廓
std::vector<Point2f> center(contours.size()); //用于存储圆心
std::vector<float> radius(contours.size()); //用于存储半径
int index = 0;
for (; index < contours.size(); ++index)
{
approxPolyDP(Mat(contours[index]), contours_poly[index], 3, 1); //多边形
boundRect[index] = boundingRect(Mat(contours[index])); //矩形
minEnclosingCircle(Mat(contours[index]), center[index], radius[index]);
}
src = imread("D:/Doucuments/VisionGroup_Documents/source/chicken.jpg");
index = 0;
for (; index < contours.size(); index++)
{
Scalar color(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(src, contours_poly, index, color, 3, 8);
rectangle(src, boundRect[index], color, 1, 8);
circle(src, center[index], radius[index], color, 1, 8);
}
imshow("dst", src);
}
2.识别装甲板
要识别并输出中心点位置以及标出未被打中的装甲板位置
思路:
初步处理与之前类似,这样可以滤掉背景中的灯光;然后做一个外轮廓发现,对这些外轮廓再用最小闭合圆拟合,主观上来说被打中的会被两边撑开,从而最小闭合圆半径会大一些,因此不难看出半径最小的圆是中心点,半径次小的圆是未被打中的装甲板所在的臂
建一张zeros,尺寸与原图相同,把半径次小的圆用-1线宽白色画在上面来生成一张图像,然后这张图与之前的二值化图像进行与操作得到只含这个臂轮廓的图,之后再用拓扑信息找到这个臂轮廓最内层即是装甲板,标出即可
最后输出的视频大概15fps
可改进的地方:
图像分离通道然后再两个通道作差的过程、轮廓发现再用最小闭合圆拟合等这一堆操作垒起来速度非常非常慢
建议改成直接对像素进行操作,省略掉分离通道这步
另一个思路是直接看各个轮廓的拓扑信息,其他的臂都有不止一个内轮廓,而未被打中的臂只有一个内轮廓
这样下来会快很多
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <stdlib.h>
using namespace cv;
Mat src, src_gray, src_bin, src_blur; //分别存储src, src的灰度图,src二值化后的图像,src滤波后的图像
Mat srcB, srcR,src_diff; //blue channel, red channel, 作差后的
Mat dst;
int thres_val = 79, thres_max = 255;
char bar_a[] = "threshold_val"; //阈值条
char win_a[] = "Settings"; //用于调整阈值的框框
RNG rng(12321);
void on_Change(int, void*);
int main(int argc, char** argv)
{
// namedWindow("src"); //创建窗口
// imshow("src", src);
// namedWindow("srcB");
// namedWindow("srcR");
// namedWindow("src_diff");
// imshow("srcB", srcB);
// imshow("srcR", srcR);
// imshow("src_diff", src_diff);
// namedWindow("blur");
// namedWindow("thres");
// namedWindow(win_a);
namedWindow("dst");
double t = 0,fps;
VideoCapture cap;
cap.open("D:/Doucuments/VisionGroup_Documents/source/wind.mp4");
std::vector<Mat> src_chans;
while (1)
{
//src = imread("D:/Doucuments/Vision_Documents/source/wind5.jpg"); //读取原图像
cap >> src;
if (src.empty())
break;
split(src, src_chans); //分离图像通道
srcB = src_chans.at(0);
srcR = src_chans.at(2);
absdiff(srcB, srcR, src_diff); //两通道作差
src_gray = src_diff.clone();
on_Change(0, 0);
t = ((double)getTickCount() - t) / getTickFrequency(); //计算帧率
fps = 1.0 / t;
printf("FPS:%.2f", fps);
//createTrackbar(bar_a, win_a, &thres_val, thres_max, on_Change);
waitKey(5);
t = getTickCount();
}
waitKey(0);
return 0;
}
void on_Change(int, void*)
{
std::vector<std::vector<Point>> contours; //用于储存轮廓发现的结果
std::vector<std::vector<Point>> ex_contours; //用于储存外轮廓
std::vector<Vec4i> ex_hierarchy; //外轮廓拓扑信息
std::vector<Vec4i> hierarchy; //用于储存轮廓发现的拓扑信息
blur(src_gray, src_blur, Size(11, 11)); //均值滤波
// imshow("blur", src_blur);
threshold(src_blur, src_bin, thres_val, thres_max, THRESH_BINARY); //二值化
// imshow("thres", src_bin);
Mat kernel = getStructuringElement(CV_SHAPE_RECT, Size(21, 21));
findContours(src_bin, ex_contours, ex_hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); //外轮廓
std::vector<std::vector<Point>> contours_poly(ex_contours.size()); //用于存储多边形逼近轮廓
std::vector<Point2f> center(ex_contours.size()); //用于存储圆心
std::vector<float> radius(ex_contours.size()); //用于存储半径
int min_circle_index,sec_circle_index; //最小圆(R),次小圆(装甲板)
float min_radius=10000,sec_radius=10000;
int index = 0;
system("CLS");
for (index = 0; index < ex_contours.size(); ++index) //闭合圆拟合外轮廓并寻找半径最小的圆
{
minEnclosingCircle(Mat(ex_contours[index]), center[index], radius[index]);
printf("%d\n", index);
circle(src, center[index], radius[index], Scalar(0, 255, 0), 1, 8);
if (radius[index] < min_radius)
{
min_radius = radius[index];
min_circle_index = index;
}
}
//src = imread("D:/Doucuments/Vision_Documents/source/wind5.jpg");
Scalar color(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
circle(src, center[min_circle_index], min_radius, color, -1, 8);
//printf("index_test:%d\n", index);
//printf("%d", ex_contours.size());
if (index >= 1)
{
for (index = 0; index < ex_contours.size(); index++)
{
if (radius[index]<sec_radius&&radius[index]>min_radius) //找次小圆
{
sec_circle_index = index;
sec_radius = radius[index];
}
}
printf("test:%.2f\n",sec_radius);
if (sec_radius >= 70)
{
Mat src_temp = Mat::zeros(src_bin.size(), src_bin.type());
circle(src_temp, center[sec_circle_index], sec_radius, Scalar(255, 255, 255), -1, 8); //把次小圆画到zeros上
bitwise_and(src_temp, src_bin, src_temp); //与
findContours(src_temp, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE); //找外轮廓
index = 0;
while (hierarchy[index][2] != -1)
index = hierarchy[index][2];
drawContours(src, contours, index, Scalar(0, 0, 255), 4, 8);
}
}
// namedWindow("src_temp");
// imshow("src_temp", src_temp);
printf("center: (%f,%f)\n", center[min_circle_index].x, center[min_circle_index].y);
imshow("dst", src);
}