#include //所有的头文件
或者
//[core]核心功能模块
//主要包含了opencv基本数据结构,动态数据结构,绘图函数,数组操作相关函数,辅助功能与系统函数和宏。
#include
//[imgproc]图像处理模块
//主要包含了图像的变换,滤波直方图相关结构分析,形状描述 。
#include
//[highgui]高层GUI图像交互模块
//主要包含了图形交互界面,媒体I/O的输入输出,视频信息的捕捉和提取,图像视频编码等。
#include
//[dnn]深度学习模块
#include
using namespace cv;
用mat定义矩阵的方法很多,这里简单介绍一些
更详细内容参考这篇博客
Mat mask(rows,cols, CV_8UC3, Scalar::all(0));
第三个参数C后面的数字表示矩阵的通道数
第四个通道表示颜色’all(0)'全为0,其他颜色可以用Scalar(0, 0, 255)红色等
Mat img = imread("图片地址", flags);
flags表示读入图片的方式,默认为IMREAD_COLOR ,以BGR格式读取
不改变原图设置为IMREAD_UNCHANGED
第一种,用at
int px = img.at<Vec3b>(row, col)[channel]; //读取row行col列channel通道的像素
第二种, 用ptr指针(遍历像素,修改)
//将img中的第一行地址赋予pxVec
uchar* pxVec=img.ptr<uchar>(0);
//遍历所有元素
int px;
for (int i = 0; i < img.rows; i++)
{
pxvec = img.ptr<uchar>(i);
//三通道数据都在第一行依次排列,按照BGR顺序
//依次赋值为1
for (int j = 0; j < img.cols*img.channels(); j++)
{
//修改像素,只能通过ptr指针的方式
pxvec[j] = 0;
}
}
高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。
CV_EXPORTS_W void GaussianBlur( InputArray src,
OutputArray dst, Size ksize,
double sigmaX, double sigmaY=0,
int borderType=BORDER_DEFAULT );
第一个参数,
InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。它可以是单独的任意通道数的图片,但需要注意,图片深度应该为CV_8U,CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二个参数,
OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
第三个参数,
Size类型的ksize高斯内核的大小。其中ksize.width和ksize.height可以不同,但他们都必须为正数和奇数。或者,它们可以是零的,它们都是由sigma计算而来。
第四个参数,
double类型的sigmaX,表示高斯核函数在X方向的的标准偏差。
第五个参数,
double类型的sigmaY,表示高斯核函数在Y方向的的标准偏差。若sigmaY为零,就将它设为sigmaX,如果sigmaX和sigmaY都是0,那么就由ksize.width和ksize.height计算出来。
为了结果的正确性着想,最好是把第三个参数Size,第四个参数sigmaX和第五个参数sigmaY全部指定到。
第六个参数,
int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT
更多滤波方法:参考资料
CV_EXPORTS_W void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 );
第三个参数’code’转换格式:
图像转灰度图:COLOR_BGR2GRAY
BGR图像转RGB图像:COLOR_BGR2RGB
图像转直方图:COLOR_BGR2HSV
CV_EXPORTS_W void adaptiveThreshold( InputArray src, OutputArray dst,
double maxValue, int adaptiveMethod,
int thresholdType, int blockSize, double C );
//example
adaptiveThreshold(~img_gray, img_binary, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 15, -10);
int adaptiveMethod:
在一个邻域内计算阈值所采用的算法,有两个取值,分别为ADAPTIVE_THRESH_MEAN_C 和 ADAPTIVE_THRESH_GAUSSIAN_C 。ADAPTIVE_THRESH_MEAN_C的计算方法是计算出领域的平均值再减去第七个参数double C的值。
ADAPTIVE_THRESH_GAUSSIAN_C的计算方法是计算出领域的高斯均值再减去第七个参数double C的值。
int thresholdType:
这是阈值类型,只有两个取值,分别为 THRESH_BINARY 和THRESH_BINARY_INV 。
int blockSize:
adaptiveThreshold的计算单位是像素的邻域块,这是局部邻域大小,3、5、7等。
double C:
这个参数实际上是一个偏移值调整量,用均值和高斯计算阈值后,再减或加这个值就是最终阈值。
//注:example里面img_gray为何前面加"~"不清楚=.=
CV_EXPORTS void findContours( InputArray image, OutputArrayOfArrays contours,
int mode, int method, Point offset = Point());
//example
vector<vector<Point>> contours;
findContours(img_binary, contours, RETR_LIST, CHAIN_APPROX_NONE);
第二个参数
是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量,每一组Point点集就是一个轮廓。
第三个参数mode:
取值一:CV_RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略
取值二:CV_RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系,这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,
取值三:CV_RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息,则内围内的所有轮廓均归属于顶层
取值四:CV_RETR_TREE, 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。
第四个参数:int型的method,定义轮廓的近似方法:
取值一:CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内
取值二:CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours向量内,拐点与拐点之间直线段上的信息点不予保留
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat dilateImg;
Mat imgde;
//膨胀
dilate(mask, dilateImg, kernel);
//腐蚀
imgde = dilateImg.clone();
erode(imgde, imgde, kernel);
第一个参数
矩形:MORPH_RECT;
交叉形:MORPH_CROSS;
椭圆形:MORPH_ELLIPSE;
段落3中边缘检测生成的点都保存在contours中,本章将对contours里每个轮廓点的集合contour进行一些基本操作处理
RotatedRect rect = minAreaRect(contour);// 返回值: 中心点坐标; 长、宽; 旋转角
Point rectCenter = rect.center;
double w = rect.size.width;
double h = rect.size.height;
double angel = rect.angle;
CV_EXPORTS_W void fitLine( InputArray points, OutputArray line, int distType,
double param, double reps, double aeps );
//example
vector<pair<double, double>> kbs;
Vec4f scalesLines;
fitLine(contour, scalesLines, 2, 0, 0.001, 0.001);
double k = scalesLines[1] / scalesLines[0];
double b = scalesLines[3] - k * scalesLines[2];
kbs.push_back(make_pair(k, b));
int distType, // 距离类型
double param, // 距离参数
double reps, // 径向的精度参数 表示直线到原点距离的精度,建议取 0.01。设为0,则自动选用最优值
double aeps // 角度精度参数 表示直线角度的精度,建议取 0.01
CV_EXPORTS_W void HoughLinesP( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double minLineLength = 0, double maxLineGap = 0 );
//example
HoughLinesP(imgde, lines, 1, acos(-1) / 180, 100, r / 2, 2);
第一个参数,
InputArray类型的image,输入图像,即源图像,需为8位的单通道二进制图像,可以将任意的源图载入进来后由函数修改成此格式后,再填在这里。
第二个参数,
InputArray类型的lines,经过调用HoughLinesP函数后后存储了检测到的线条的输出矢量,每一条线由具有四个元素的矢量(x_1,y_1, x_2, y_2) 表示,其中,(x_1, y_1)和(x_2, y_2) 是是每个检测到的线段的结束点。
第三个参数,
double类型的rho,以像素为单位的距离精度。另一种形容方式是直线搜索时的进步尺寸的单位半径。
第四个参数,
double类型的theta,以弧度为单位的角度精度。另一种形容方式是直线搜索时的进步尺寸的单位角度。
第五个参数,
int类型的threshold,累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才可以被检测通过并返回到结果中。
第六个参数,
double类型的minLineLength,有默认值0,表示最低线段的长度,比这个设定参数短的线段就不能被显现出来。
第七个参数,
double类型的maxLineGap,有默认值0,允许将同一行点与点之间连接起来的最大的距离。
Net Model::loadModel() {
// cfg文件和weight文件地址
String modelConfiguration = "../models/yolov3-tiny-test.cfg";
String modelWeights = "../models/yolov3-tiny_4000.weights";
// 加载网络模型
Net net = readNetFromDarknet(modelConfiguration, modelWeights);
//net.setPreferableBackend(DNN_BACKEND_OPENCV);
net.setPreferableTarget(DNN_TARGET_CPU);
return net;
}
Model model;
Net net = model.loadModel();
vector<bbox_t> Model::get_vector(Mat &input, Net net, vector<bbox_t> result_vec)
{
Mat blob;
bbox_t box;
if (input.empty()) {
cout << "No input image" << endl;
}
// Create a 4D blob from a frame.
blobFromImage(input, blob, 1/255.0, Size(inpWidth, inpHeight), Scalar(0,0,0), true, false);
//Sets the input to the network
net.setInput(blob);
// Runs the forward pass to get output of the output layers
vector<Mat> outs;
net.forward(outs, getOutputsNames(net));
// Remove the bounding boxes with low confidence
vector<Rect> boxes = postprocess(input, outs);
int length = boxes.size();
for(int i=0; i<length; i++)
{
if (!boxes.empty() && boxes[i].x > 0 && boxes[i].y > 0 &&
boxes[i].x + boxes[i].width < input.size().width &&
boxes[i].y + boxes[i].height < input.size().height ) {// 越界判断,这里只保存完整的检测框
Rect rect(boxes[i].x, boxes[i].y, boxes[i].width, boxes[i].height);
//保存边界框信息
box.x = boxes[i].x;box.y = boxes[i].y;box.w = boxes[i].width;box.h = boxes[i].height;
result_vec.push_back(box);
}
}
return result_vec;
}
result_vec = model.get_vector(frame, net, result_vec);
void line(InputOutputArray img, Point pt1, Point pt2, const Scalar& color,
int thickness = 1, int lineType = LINE_8, int shift = 0);
void circle(InputOutputArray img, Point center, int radius,
const Scalar& color, int thickness = 1,
int lineType = LINE_8, int shift = 0);
void rectangle(InputOutputArray img, Point pt1, Point pt2,
const Scalar& color, int thickness = 1,
int lineType = LINE_8, int shift = 0);
第一个参数img:要划的线所在的图像;
第二个参数pt1:直线起点
第二个参数pt2:直线终点
第三个参数color:直线的颜色 Scalor(0,0,255)
第四个参数thickness=1:线条粗细
void draw_boxes(std::map<unsigned int, double> meter_result, cv::Mat mat_img, std::vector<bbox_t> result_vec,
std::vector<std::string> obj_names)
{
int result_size = result_vec.size();
for (int i=0;i<result_size;i++)
{
cv::Scalar color = 随机生成一个颜色;
cv::rectangle(mat_img, cv::Rect(result_vec[i].x,result_vec[i].y, result_vec[i].w, result_vec[i].h), color, 2);
if (obj_names.size() > result_vec[i].obj_id)
{
std::string obj_name = obj_names[result_vec[i].obj_id];
cv::Size const text_size = getTextSize(obj_name, cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2, 2, 0);
int max_width = (text_size.width > result_vec[i].w + 2) ? text_size.width : (result_vec[i].w + 2);
max_width = std::max(max_width, (int) result_vec[i].w + 2);
//max_width = std::max(max_width, 283);
cv::rectangle(mat_img, cv::Point2f(std::max((int) result_vec[i].x - 1, 0), std::max((int) result_vec[i].y - 35, 0)),
cv::Point2f(std::min((int) result_vec[i].x + max_width, mat_img.cols - 1),
std::min((int) result_vec[i].y, mat_img.rows - 1)),
color, CV_FILLED, 8, 0);
obj_name = obj_name + to_string(meter_result[i]);
putText(mat_img, obj_name, cv::Point2f(result_vec[i].x, result_vec[i].y - 16), cv::FONT_HERSHEY_COMPLEX_SMALL, 1.2,
cv::Scalar(0, 0, 0), 2);
}
}
}
其中
cv::Size cv::getTextSize(const string& text, int fontFace,
double fontScale,int thickness,int* baseLine );
text为文本,fontFace为文本的字体类型,fontScale为文本大小的倍数(以字体库中的大小为基准而放大的倍数),thickness为文本的粗细。最后一个参数baseLine是指距离文本最低点对应的y坐标。返回一个文本整体高度height。
namedWindow("Meter Detection" , WINDOW_NORMAL); //可以改变窗口大小
cv::imshow("Meter Detection", show_frame); //与窗口名字要一致
int key = cv::waitKey(3); // 3ms
if (key == 'q'|| key == 27) //按下q或esc
{
cv::destroyWindow("Meter Detection");
break;
}
保存图片
imwrite("路径+name.格式",save_img);
保存视频
VideoWriter writer_obj;
writer_obj.open("保存路径文件", 0, 25, Size(1280,720)); //fourcc参数0,保存格式,fps25
writer_obj<<frame; //每帧进行保存
void setMousecallback(const string& winname, MouseCallback onMouse, void* userdata=0)
//视频读取图像
cap >> img;
imshow("image",img);
setMouseCallback("image",on_Mouse,0);
winname:窗口的名字
onMouse:鼠标响应函数,回调函数。指定窗口里每次鼠标时间发生的时候,被调用的函数指针。 这个函数的原型应该为void on_Mouse(int event, int x, int y, int flags, void* param);
userdate:传给回调函数的参数
void on_Mouse(int event,int x,int y,int flags, void*param)
{
Point previousPoint;
Point nowPoint;
if(event == EVENT_LBUTTONDOWN) //左键点击
{
select_rect.x = x;
select_rect.y = y;
select_flag = true;
}
else if(event==EVENT_MOUSEMOVE && select_flag) //桉住左键他拖拽
{
previousPoint = Point(select_rect.x,select_rect.y);
nowPoint = Point(x,y);
rectangle(img,previousPoint,nowPoint,Scalar(255,0,0),5);
imshow("image",img);
}
else if(event==EVENT_LBUTTONUP) //左键释放
{
previousPoint = Point(select_rect.x,select_rect.y);
nowPoint = Point(x,y);
rectangle(img,previousPoint,nowPoint,Scalar(255,0,0),5);
imshow("image",img);
select_flag = false;
int min_x = min(previousPoint.x, nowPoint.x);
int min_y = min(previousPoint.y, nowPoint.y);
int width = abs(previousPoint.x-nowPoint.x);
int height = abs(previousPoint.y-nowPoint.y);
Rect rect(min_x,min_y,width,height);
Mat cut_img = img(rect);
string name = "/路径/" + to_string(min_x) + "_" + to_string(min_y) + ".jpg";
imwrite(name,cut_img);
}
}
参考博客