OpenCV技术文档


记录一下平时所做工程时用到的一些OpenCV技术,不定期更新(应该会吧哈哈)
版本:OpenCV4.2.0 语言:C++

1.头文件和命名空间

#include <opencv2/opencv.hpp>  //所有的头文件

或者

//[core]核心功能模块
//主要包含了opencv基本数据结构,动态数据结构,绘图函数,数组操作相关函数,辅助功能与系统函数和宏。
#include <opencv2/core.hpp> 
//[imgproc]图像处理模块
//主要包含了图像的变换,滤波直方图相关结构分析,形状描述 。
#include <opencv2/imgproc.hpp>
//[highgui]高层GUI图像交互模块
//主要包含了图形交互界面,媒体I/O的输入输出,视频信息的捕捉和提取,图像视频编码等。
#include <opencv2/highgui.hpp>
//[dnn]深度学习模块
#include <opencv2/dnn.hpp>
using namespace cv;

2.读取文件

2.1 使用Mat定义矩阵

用mat定义矩阵的方法很多,这里简单介绍一些
更详细内容参考这篇博客

Mat mask(rows,cols, CV_8UC3, Scalar::all(0));

第三个参数C后面的数字表示矩阵的通道数
第四个通道表示颜色’all(0)'全为0,其他颜色可以用Scalar(0, 0, 255)红色等

2.2 读取图片

Mat img = imread("图片地址", flags);

flags表示读入图片的方式,默认为IMREAD_COLOR ,以BGR格式读取
不改变原图设置为IMREAD_UNCHANGED

2.3 访问图像像素

第一种,用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;
    }
} 

3.图像基本预处理操作

3.1 图像滤波

高斯滤波

高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。

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

更多滤波方法:参考资料

3.2 图像转换

CV_EXPORTS_W void cvtColor( InputArray src, OutputArray dst, int code, int dstCn = 0 );

第三个参数’code’转换格式:

图像转灰度图:COLOR_BGR2GRAY
BGR图像转RGB图像:COLOR_BGR2RGB
图像转直方图:COLOR_BGR2HSV

3.3 二值化

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为何前面加"~"不清楚=.=

3.4 边缘检测

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向量内,拐点与拐点之间直线段上的信息点不予保留

3.5 图像膨胀腐蚀

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;

4.对轮廓集合点的基本操作

段落3中边缘检测生成的点都保存在contours中,本章将对contours里每个轮廓点的集合contour进行一些基本操作处理

4.1 求外接矩形

RotatedRect rect =  minAreaRect(contour);// 返回值: 中心点坐标; 长、宽; 旋转角
Point rectCenter = rect.center;
double w = rect.size.width;
double h = rect.size.height;
double angel = rect.angle;

4.2 拟合直线

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

4.3 轮廓点中找直线

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,允许将同一行点与点之间连接起来的最大的距离。

5.调用darknet

5.1 加载darknet模型

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();

5.2 获取检测目标

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);

6.绘图

6.1 画直线、圆、矩形

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:线条粗细

6.2 绘制检测框

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。

7.显示与保存

7.1 图像显示

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;
}

7.2 图像保存

保存图片

imwrite("路径+name.格式",save_img);

保存视频

VideoWriter writer_obj;
writer_obj.open("保存路径文件", 0, 25, Size(1280,720)); //fourcc参数0,保存格式,fps25
writer_obj<<frame; //每帧进行保存

8.鼠标响应

8.1 setMousecallback()函数

 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:传给回调函数的参数

8.2 on_Mouse()函数

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);
    }
}

参考博客

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值