[opencv][cpp] 学习手册2:卷积、算子、变换

[opencv][cpp] 学习手册2:卷积、算子、变换

07_图像卷积-filter2d.cpp
08_Sobel算子.cpp
09_Scharr算子.cpp
10_拉普拉斯算子.cpp
11_找你妹案例.cpp
12_形态学变化.cpp


文章目录

  • [opencv][cpp] 学习手册2:卷积、算子、变换
    • 00_配置_opencv_cpp环境
    • 00_OpenCV_日志系统
    • 07_图像卷积-filter2d.cpp
    • 08_Sobel算子.cpp
    • 09_Scharr算子.cpp
    • 10_拉普拉斯算子.cpp
    • 11_找你妹_案例.cpp
    • 12_形态学变化.cpp
      • 膨胀(dilation)
      • 腐蚀(erosion)
      • 开操作,先腐蚀,再膨胀(opening)
      • 闭操作,先膨胀,再腐蚀(closing)
      • 形态学变换补充
    • &&_参考
    • &&_问题解决


注:文中部分知识点截取自课件,侵删!


00_配置_opencv_cpp环境

1. 创建新项目 studyopencv02
[opencv][cpp] 学习手册2:卷积、算子、变换_第1张图片

2. 创建cmake文件夹,将 FindOpenCV.cmake 文件放入


# once done, this will define
#OpenCV_FOUND        -  whether there is OpenCV in the prebuilt libraries
#OpenCV_INCLUDE_DIRS -  include directories for OpenCV
#OpenCV_LIBRARY_DIRS -  library directories for OpenCV
#OpenCV_LIBRARIES    -  link this to use OpenCV

unset(OpenCV_FOUND)

# set( ... [PARENT_SCOPE])
# Signatures of this command that specify a ... placeholder expect zero or more arguments.
# Multiple arguments will be joined as a semicolon-separated list to form the actual variable value to be set.
# Zero arguments will cause normal variables to be unset.

# 指定项目依赖库所在的路径变量,这里都放在 /robotDepends/libs目录下
# /home/jacob/devtools/robotDepends/libs/opencv_3.4.12/libs
set(PREBUILT_DIR /home/jacob/devtools/robotDepends/libs)

# 指定opencv头文件库文件所在的路径变量,这里放在 /libs/opencv_3.4.12目录下
set(OpenCV_DIR ${
     PREBUILT_DIR}/opencv_3.4.12)

MESSAGE(STATUS "TEST  ${PREBUILT_DIR}")

# 指定opencv头文件所在的路径变量,这里放在 /opencv_3.4.12/include目录下
set(OpenCV_INCLUDE_DIRS ${
     OpenCV_DIR}/include)

# eg: set(srcs a.c b.c c.c) # sets "srcs" to "a.c;b.c;c.c"
set(OpenCV_LIB_COMPONENTS
        opencv_videostab;
        opencv_video;
        opencv_superres;
        opencv_stitching;
        opencv_photo;
        opencv_objdetect;
        opencv_ml;
        opencv_imgproc;
        opencv_highgui;
        opencv_flann;
        opencv_features2d;
        opencv_core;
        opencv_calib3d;
        opencv_xfeatures2d;
        opencv_imgcodecs)

# This command is used to find a directory containing the named file.
# find_path (
#         <VAR>
#         name | NAMES name1 [name2 ...]
#         [HINTS path1 [path2 ... ENV var]]
#         [CMAKE_FIND_ROOT_PATH_BOTH |
#         ONLY_CMAKE_FIND_ROOT_PATH |
#         NO_CMAKE_FIND_ROOT_PATH]
# )
find_path(OpenCV_INCLUDE_DIRS NAMES opencv.h HINTS ${
     OpenCV_DIR}/include NO_SYSTEM_ENVIRONMENT_PATH)

set(OpenCV_LIBRARY_DIRS ${
     OpenCV_DIR}/lib)

FOREACH (cvcomponent ${
     OpenCV_LIB_COMPONENTS})
    find_library(lib_${
     cvcomponent} NAMES ${
     cvcomponent} HINTS ${
     OpenCV_DIR}/lib NO_DEFAULT_PATH)

    set(OpenCV_LIBRARIES ${
     OpenCV_LIBRARIES};${
     lib_${
     cvcomponent}})
ENDFOREACH ()

set(OpenCV_LIBS ${
     OpenCV_LIBRARIES})

set(OpenCV_INCLUDE_DIRS
        ${
     OpenCV_INCLUDE_DIRS};
        ${
     OpenCV_INCLUDE_DIRS}/opencv)

if (OpenCV_INCLUDE_DIRS AND OpenCV_LIBRARIES)
    set(OpenCV_FOUND TRUE)
endif (OpenCV_INCLUDE_DIRS AND OpenCV_LIBRARIES)

if (OpenCV_FOUND)
    if (NOT OpenCV_FIND_QUIETLY)
        message(STATUS "Found OpenCV: ${OpenCV_LIBRARIES}")
    endif (NOT OpenCV_FIND_QUIETLY)
else (OpenCV_FOUND)
    if (OpenCV_FIND_REQUIRED)
        message(FATAL_ERROR "Could not find the OpenCV library")
    endif ()
endif (OpenCV_FOUND)

# 导入opencv相关的头文件
include_directories(${
     OpenCV_INCLUDE_DIRS})

3. 修改cmakelist.txt

cmake_minimum_required(VERSION 3.17)
project(studyopencv002)

set(CMAKE_CXX_STANDARD 14)

# load opencv files under cmake directory
set(CMAKE_MODULE_PATH ${
     CMAKE_MODULE_PATH}/cmake)

# find dependencies for opencv
find_package(OpenCV REQUIRED)

add_executable(studyopencv002 main.cpp)
target_link_libraries(studyopencv002 ${
     OpenCV_LIBS})

4. 源代码中导入 opencv 头文件,运行测试

#include 
#include 

using namespace std;

int main() {
     
    std::cout << "Hello, World!" << std::endl;

    // 1. read image
    string fileName = "../img/lena.jpg";
    cv::Mat src = cv::imread(fileName, cv::IMREAD_COLOR);

    // 2. display image, wait key press
    cv::imshow("src", src);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

/home/jacob/CVWS/studyopencv002/cmake-build-debug/studyopencv002
Hello, World!

[opencv][cpp] 学习手册2:卷积、算子、变换_第2张图片


00_OpenCV_日志系统

需求

学会使用 opencv 原生的日志库函数。

代码

//
// Created by jacob on 12/29/20.
//

#include 

#include 
#include 

using namespace std;
namespace cvlog = cv::utils::logging;

int main(int argc, char **argv) {
     

    // setting up cv log
    cvlog::setLogLevel(cvlog::LOG_LEVEL_INFO);

    cv::Mat img = cv::Mat::eye(3, 3, CV_8U);

    // logging method 1 (X)
    char logMsg[] = "";
    sprintf(logMsg, "img.type(): %d str: %s", img.type(), "abc");
    cvlog::internal::writeLogMessage(cvlog::LOG_LEVEL_INFO, logMsg);

    // logging method 2 (X)
    std::stringstream ss;
    ss << "img.type(): " << img.type();
    const char *str = ss.str().c_str();
    cvlog::internal::writeLogMessage(cvlog::LOG_LEVEL_WARNING, str);

    // logging method 3 (recommanded)
    CV_LOG_INFO(NULL, "hello: " << 32)
    CV_LOG_INFO(ABC, "hello: " << img.type())
    CV_LOG_WARNING(XYZ, "warning: " << img.type() << 32)

    return 0;
}

解释说明

使用 logger.hpp 中定义的 日志宏 如:CV_LOG_INFO,CV_LOG_WARNING 等写日志。

运行结果
[opencv][cpp] 学习手册2:卷积、算子、变换_第3张图片


07_图像卷积-filter2d.cpp

代码

//
// Created by jacob on 12/29/20.
// enable cv-logging::https://stackoverflow.com/questions/54828885/how-to-enable-logging-for-opencv
//

#include 

#include 
#include 

using namespace std;

int main() {
     

    // 0. set logging
    cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_INFO);
    cv::utils::logging::internal::writeLogMessage(cv::utils::logging::LOG_LEVEL_INFO, "hello");

    // 1. read image
    string fileName = "../img/lena.jpg";
    cv::Mat src = cv::imread(fileName, cv::IMREAD_COLOR);

    // 2. do convolution
    cv::Mat dst;
    cv::Mat kernel = (cv::Mat_<char>(3, 3)
            << -1, -1, -1,
            -1, 9, -1,
            -1, -1, -1);
    cv::filter2D(src, dst, -1, kernel);

    // display & wait key press
    cv::imshow("src", src);
    cv::imshow("dst", dst);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

解释说明

  1. 需要使用 opencv 的日志功能,步骤:

    1. 引入头文件:#include
    2. 设置全局日志等级:cv::utils::logging::setLogLevel(cv::utils::logging::LOG_LEVEL_INFO);
    3. 使用日志(示例):cv::utils::logging::internal::writeLogMessage(cv::utils::logging::LOG_LEVEL_INFO, "hello");
  2. 初始化卷积核的 cv::Mat 矩阵类型:参考 Mat - The Basic Image Container
    [opencv][cpp] 学习手册2:卷积、算子、变换_第4张图片

  3. 此程序中的卷积核对图形进行锐化处理

    // 锐化滤波卷积核
    cv::Mat kernel = (cv::Mat_<char>(3, 3)
            << -1, -1, -1,
            -1, 9, -1,
            -1, -1, -1);
    

运行结果
[opencv][cpp] 学习手册2:卷积、算子、变换_第5张图片


08_Sobel算子.cpp

代码

//
// Created by jacob on 12/29/20.
// enable cv-logging::https://stackoverflow.com/questions/54828885/how-to-enable-logging-for-opencv
//

#include 

#include 
#include 


using namespace std;
namespace cvlog = cv::utils::logging;


int main() {
     

    // 0. set logging
    cvlog::setLogLevel(cv::utils::logging::LOG_LEVEL_INFO);
    cvlog::internal::writeLogMessage(cvlog::LOG_LEVEL_INFO, "hello");

    // 1. read src image
//    string fileName = "../img/zqbb.jpg";
    string fileName = "../img/lena.jpg";
    cv::Mat src = cv::imread(fileName, cv::IMREAD_COLOR);

    // 2. do Sobel
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    CV_LOG_INFO(NULL, "gray.type(): " << gray.type())

    cv::Mat dst_x_v;
    cv::Mat dst_y_h;
    cv::Mat dst;

	// 使用 -1 参数使得像素的取值范围在(CV_8U) 0-255,图像处理是会丢失信息
//    cv::Sobel(gray, dst_x_v, -1, 1, 0);
//    cv::Sobel(gray, dst_y_h, -1, 0, 1);
//    dst = dst_x_v + dst_y_h;

	// 将图像转化为 CV_16S 对图像进行处理,取值范围 2^16,不会丢失信息
    cv::Sobel(gray, dst_x_v, CV_16S, 1, 0);
    cv::convertScaleAbs(dst_x_v, dst_x_v);
    cv::Sobel(gray, dst_y_h, CV_16S, 0, 1);
    cv::convertScaleAbs(dst_y_h, dst_y_h);
    cv::add(dst_x_v, dst_y_h, dst);
    cv::convertScaleAbs(dst, dst);

    // display & wait key press
    cv::namedWindow("src", cv::WINDOW_NORMAL);

    cv::imshow("src", src);
    cv::imshow("gray", gray);
    cv::imshow("dst_x_v", dst_x_v);
    cv::imshow("dst_y_h", dst_y_h);
    cv::imshow("dst", dst);

    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

解释说明

  1. 在使用 索贝尔 函数对 灰度图像进行处理的时候,由于灰度图像的类型 为 CV_8U,取值范围在 0-255,所以会丢失图像信息;因此,使用 CV_16S 类型进行 索贝尔算子运算。
  2. 之后,要使用函数 convertScaleAbs 将图像矩阵中的每一个像素点进行三个操作:1. 比例、2.计算绝对值、3.将像素转换成 CV_8U 类型,以防止数据溢出:

    Scales, calculates absolute values, and converts the result to 8-bit.
    On each element of the input array, the function convertScaleAbs performs three operations sequentially: scaling, taking an absolute value, conversion to an unsigned 8-bit type:

运行结果

使用 CV_16S 处理的图像,信息保留更多


09_Scharr算子.cpp

代码

//
// Created by jacob on 12/29/20.
// enable cv-logging::https://stackoverflow.com/questions/54828885/how-to-enable-logging-for-opencv
//

#include 

#include 
#include 


using namespace std;
namespace cvlog = cv::utils::logging;


int main() {
     

    // 0. set logging
    cvlog::setLogLevel(cv::utils::logging::LOG_LEVEL_INFO);
    cvlog::internal::writeLogMessage(cvlog::LOG_LEVEL_INFO, "hello");

    // 1. read src image
//    string fileName = "../img/zqbb.jpg";
    string fileName = "../img/lena.jpg";
    cv::Mat src = cv::imread(fileName, cv::IMREAD_COLOR);

    // 2. do Scharr
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    CV_LOG_INFO(NULL, "gray.type(): " << gray.type())

    cv::Mat dst_x_v;
    cv::Mat dst_y_h;
    cv::Mat dst;

    cv::Scharr(gray, dst_x_v, CV_16S, 1, 0);
    cv::convertScaleAbs(dst_x_v, dst_x_v);
    cv::Scharr(gray, dst_y_h, CV_16S, 0, 1);
    cv::convertScaleAbs(dst_y_h, dst_y_h);
    cv::add(dst_x_v, dst_y_h, dst);


    // display & wait key press
    cv::namedWindow("src", cv::WINDOW_NORMAL);

    cv::imshow("src", src);
    cv::imshow("gray", gray);
    cv::imshow("dst_x_v", dst_x_v);
    cv::imshow("dst_y_h", dst_y_h);
    cv::imshow("dst", dst);

    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

运行结果


10_拉普拉斯算子.cpp

代码

//
// Created by jacob on 12/29/20.
// enable cv-logging::https://stackoverflow.com/questions/54828885/how-to-enable-logging-for-opencv
//

#include 

#include 
#include 


using namespace std;
namespace cvlog = cv::utils::logging;


int main() {
     

    // 0. set logging
    cvlog::setLogLevel(cv::utils::logging::LOG_LEVEL_INFO);
    cvlog::internal::writeLogMessage(cvlog::LOG_LEVEL_INFO, "hello");

    // 1. read src image
//    string fileName = "../img/zqbb.jpg";
    string fileName = "../img/lena.jpg";
    cv::Mat src = cv::imread(fileName, cv::IMREAD_COLOR);

    // 2. do laplacian
    cv::Mat gray;
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
    CV_LOG_INFO(NULL, "gray.type(): " << gray.type())

    cv::Mat dst;
    cv::Laplacian(gray, dst, CV_16S, 5);
    cv::convertScaleAbs(dst, dst);			// 防止数据溢出,变成全灰图像


    // display & wait key press
    cv::namedWindow("src", cv::WINDOW_NORMAL);

    cv::imshow("src", src);
    cv::imshow("gray", gray);
    cv::imshow("dst", dst);

    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

运行结果
[opencv][cpp] 学习手册2:卷积、算子、变换_第6张图片


11_找你妹_案例.cpp

模板匹配

  • 模板匹配就是在整个图像区域发现与给定子图像匹配的小块区域。
  • 在OpenCV中提供了6种匹配度量方法。
    1. 平方差匹配法:CV_TM_SQDIFF
    2. 归一化平方差匹配法:CV_TM_SQDIFF_NORMED
    3. 相关匹配法:CV_TM_CCORR
    4. 归一化相关匹配法:CV_TM_CCORR_NORMED
    5. 系数匹配法:CV_TM_CCOEFF
    6. 化相关系数匹配法:CV_TMCCOEFF_NORMED
  • 注意 :对于方法SQDIFF和SQDIFF_NORMED两种方法来讲,越小的值就有着更高的匹配结果,而其余的方法则是数值越大匹配效果越好。

上下采样

  • 缩小图像(或称为下采样(subsampled)或降采样(downsampled))的主要目的有两个:1、使得图像符合显示区域的大小;2、生成对应图像的缩略图。
  • 放大图像(或称为上采样(upsampling)或图像插值(interpolating))的主要目的是放大原图像,从而可以显示在更高分辨率的显示设备上。

代码

//
// Created by jacob on 12/29/20.
// enable cv-logging::https://stackoverflow.com/questions/54828885/how-to-enable-logging-for-opencv
//

#include 

#include 
#include 


using namespace std;
using namespace cv;
namespace cvlog = cv::utils::logging;


int main() {
     

    // 0. set logging
    cvlog::setLogLevel(cv::utils::logging::LOG_LEVEL_INFO);
    cvlog::internal::writeLogMessage(cvlog::LOG_LEVEL_INFO, "hello");

    // 1. read src image
    string fileName = "../img/zhaonimei.jpg";
    string fileName_t = "../img/mei.jpg";
    cv::Mat src = cv::imread(fileName, cv::IMREAD_COLOR);
    cv::Mat temp = cv::imread(fileName_t, cv::IMREAD_COLOR);

    // 2. do template matching
    // 上采样
    pyrUp(temp,temp);

    // 模板匹配
    Mat result;
    matchTemplate(src,temp,result,TM_SQDIFF);

    // 找到最小的匹配结果
    double minVal=0;
    double maxVal=0;
    Point minLoc;
    Point maxLoc;
    minMaxLoc(result,&minVal,&maxVal,&minLoc,&maxLoc);

    cout<<minVal<<endl;
    cout<<minLoc<<endl;

    rectangle(src,minLoc,Point(minLoc.x + temp.cols,minLoc.y+temp.rows),Scalar(0,0,255),2);


    // display & wait key press
    cv::imshow("src", src);
    cv::imshow("temp", temp);


    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

cv函数
[opencv][cpp] 学习手册2:卷积、算子、变换_第7张图片
[opencv][cpp] 学习手册2:卷积、算子、变换_第8张图片

运行结果(错误)
[opencv][cpp] 学习手册2:卷积、算子、变换_第9张图片

匹配失败,还没有找到原因。


12_形态学变化.cpp

膨胀与腐蚀

  • 学会使用 API
  • 根据运行结果选择合适的处理方法

膨胀(dilation)

原理

  • 跟卷积操作非常类似.有图像A和3x3的结构元素,结构元素在A上进行滑动。
  • 计算结构元素在A上覆盖的最大像素值来替换当前结构元素对应的正中间的元素。
  • 膨胀的作用:
    1. 对象边缘增加一个像素
    2. 使对象边缘平滑
    3. 减少了对象与对象之间的距离

代码

/**
 * 膨胀的使用
 */
void testDilation() {
     
    Mat src = imread("../img/morph-j.jpg", IMREAD_GRAYSCALE);

    // 进行形态学变化
    Mat dst;

    // 创建结构元素
    Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
    dilate(src, dst, kernel, Point(-1, -1), 3);

    imshow("dst", dst);
    imshow("src", src);
}

运行结果
[opencv][cpp] 学习手册2:卷积、算子、变换_第10张图片

腐蚀(erosion)

原理

  • 腐蚀和膨胀过程类似,唯一不同的是以覆盖的最小值替换当前的像素值
    1. 对象边缘减少一个像素
    2. 对象边缘平滑
    3. 弱化了对象与对象之间连接

代码

/**
 * 腐蚀的使用
 */
void testErode() {
     
    Mat src = imread("../img/morph-j.jpg", IMREAD_GRAYSCALE);

    // 进行形态学变化
    Mat dst;

    // 创建结构元素
    Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
    erode(src, dst, kernel);

    imshow("src", src);
    imshow("dst", dst);
}

运行结果
[opencv][cpp] 学习手册2:卷积、算子、变换_第11张图片

开操作,先腐蚀,再膨胀(opening)

原理

  • 先腐蚀,后膨胀,主要应用在二值图像或灰度图像
    1. 先腐蚀: 让当前窗口中最小的颜色值替换当前颜色值
    2. 后膨胀: 让当前窗口中最大的颜色值替换当前颜色值
  • 作用:
    1. 一般用于消除小的干扰或噪点(验证码图的干扰线)
    2. 可以通过此操作配合结构元素,提取图像的横线和竖线

代码

/**
 * 先腐蚀后膨胀
 */
void testOpening(){
     
    Mat src = imread("../img/morph-opening.jpg",IMREAD_GRAYSCALE);

    // 创建结构元素
    Mat kernel = getStructuringElement(MORPH_RECT,Size(5,5));

    Mat dst;
    morphologyEx(src,dst,MORPH_OPEN,kernel);

    imshow("dst",dst);
    imshow("src",src);
}

运行结果
[opencv][cpp] 学习手册2:卷积、算子、变换_第12张图片

闭操作,先膨胀,再腐蚀(closing)

原理

  • 先膨胀,后侵蚀
  • 一般用于填充内部缝隙

代码

/**
 * 先膨胀后腐蚀
 */
void testClosing() {
     

    Mat src = imread("../img/morph-closing.jpg", IMREAD_GRAYSCALE);

    // 创建结构元素
    Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));

//    // 进行形态学变化
//    Mat dilatation;  // 膨胀
//    dilate(src,dilatation,kernel);
//
//    Mat erode_img;
//    erode(dilatation,erode_img,kernel);
//
//    imshow("dilatation",dilatation);
//    imshow("erode_img",erode_img);

    // 通用 api 进行闭操作
    Mat dst;
    // 通用的形态学变化
    morphologyEx(src, dst, MORPH_CLOSE, kernel);

    imshow("dst", dst);
    imshow("src", src);

    imshow("morph-closing.jpg", src);
}

运行结果
手动处理
[opencv][cpp] 学习手册2:卷积、算子、变换_第13张图片

通用api:morphologyEx
[opencv][cpp] 学习手册2:卷积、算子、变换_第14张图片

形态学变换补充

[opencv][cpp] 学习手册2:卷积、算子、变换_第15张图片


&&_参考

链接:【OpenCV3】cv::Mat的定义与初始化
链接:OpenCV 日志写法
链接:logger.hpp File Reference
链接:Making your own linear filters!


&&_问题解决

你可能感兴趣的:(opencv)