[opencv][cpp] 学习手册3:案例泛红填充,分水岭

[opencv][cpp] 学习手册3:案例泛红填充,分水岭

  • 感觉学习效率有些低,跟着视频学习代码其实不是明智之举。
  • 练习代码还是应该回归本质:思路+代码(函数+POP/OOP)+调试。
  • 练习时需要结合 opencv doc。随时翻阅。

16_泛洪填充.cpp
17_图像分水岭.cpp



文章原理、概念部分摘抄自课件,侵删!


16_泛洪填充.cpp

原理+概念

  • 泛洪填充算法又称洪水填充算法,最熟悉不过就是 windows paint的油漆桶功能
  • 算法的原理:
    • 从一个点开始,将附近像素点填充成新的颜色,直到封闭区域内的所有像素点都被填充新颜色为止。
  • 常见泛红填充:
    • 四邻域像素填充法,
    • 八邻域像素填充法,
    • 基于扫描线的像素填充方法

cv函数
在这里插入图片描述

联通区域由邻近像素的颜色亮度接进性决定。
点(x,y)属于上色区域,如果(x’,y’) 已知的属于联通区域的点,满足公式
已知点的亮度/颜色值 - 最大低亮度差 <= 当前点的亮度/颜色值 <= 已知点的亮度/颜色 + 最大高亮度差

代码

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

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/utils/logger.hpp>


using namespace std;

namespace cvlog = cv::utils::logging;


/***-------------------- global variable --------------------***/
cv::Mat src;
cv::RNG rng(12345);

/***-------------------- callback function --------------------***/

/*
 * use ctrl + mouseLeft to check function parameters
 * aim: display points on img where mouse clicked
 */
void onMouseClick(int event, int x, int y, int flags, void *userdata) {
//    CV_LOG_INFO(NULL, "hello")    // continuous logging

    if (event == cv::EVENT_LBUTTONDOWN) {
        cv::Point seedPoint(x, y);
        CV_LOG_INFO(MOUSE, "mPt(x, y): " << seedPoint.x << ", " << seedPoint.y)

        cv::Rect rect;
        cv::Scalar rand_scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
        cv::Scalar loDiff(20, 20, 20);
        cv::Scalar upDiff(60, 60, 60);

        // do flood filling
        cv::floodFill(src, seedPoint, rand_scalar, &rect, loDiff, upDiff, cv::FLOODFILL_FIXED_RANGE);

        // dispaly image
        cv::imshow("src", src);
    }
}


/***-------------------- main function --------------------***/
int main(int argc, char **argv) {

    cvlog::setLogLevel(cvlog::LOG_LEVEL_INFO);
    CV_LOG_INFO(NULL, "16_floodFill.cpp")

    // 1. read src
    string filename = "../img/zp.jpg";
    src = cv::imread(filename, cv::IMREAD_COLOR);
    CV_LOG_INFO(NULL, "src.size: " << src.size() << "src.type: " << src.type())
    cv::imshow("src", src);


    // test mouse callback
    cv::setMouseCallback("src", onMouseClick, 0);


    // wait & terminate
    cv::waitKey(0);
    return 0;
}

解释说明

  1. 案例使用鼠标点击功能,涉及到两个函数
    1. cv::setMouseCallback(“src”, onMouseClick, 0);
    2. void onMouseClick(int event, int x, int y, int flags, void *userdata);
      其中,onMouseClick 回调函数的参数可以在 IDE 中通过 ctrl + 鼠标左键点击 setMouseCallback 的第二个函数类型获得。
      在此案例中,由于需要在 onMouseClick 函数中显示对图像 src 处理过的结果,但是,定义在主函数中的 src 不在 onMouseClick 函数作用范围,需要将 src 定义成外部变量(C语言的外部变量)。
  2. 使用 cv::RNG 生成填充颜色的随机数
    cv::RNG rng(12345);
    cv::Scalar rand_scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
    
    这里使用 uniform 生成均匀分布随机数。也可以生成高斯分布等随机数。
  3. onMouseClick 回调函数会不停的接收信号
    void onMouseClick(int event, int x, int y, int flags, void *userdata) {
        CV_LOG_INFO(NULL, "hello")    // continuous logging
    }
    
    会不停的输出 hello。

运行结果
在这里插入图片描述

17_图像分水岭.cpp

原理+概念

  • 任何灰度图像都可以看做是地形表面,把高强度看做山峰和丘陵,低强度看做山谷。
  • 分水岭图像分割步骤+注意事项:
    1. 用不同显色的水(label)充满每个单独的山谷(局部最小值)
    2. 随着水位的上升,根据山谷附近的山峰高度(梯度),那些来自不同山谷的不同颜色的水开始合并
    3. 要通过在这些合并的位置建立阻隔,来避免合并的发生。
  • 不断地加水,并修建阻隔,直到所有的山峰被水覆盖。如此,我们创建的阻隔所分割出的结果即是分水岭。
  • OpenCV提供了基于marker的分水岭算法,需要将确定的背景和前景标记出来。
  • 参考:The Watershed Transformation
  • 通过标记控制的分水岭 marker-controlled watershed可以防止过度细分 over-segmentation在这里插入图片描述
    在这里插入图片描述

cv函数
在这里插入图片描述

使用正整数标记区域(1, 2, 3,…),因此每一个意向的联通区域由像素值1,2,3,… 等表示。
输出矩阵:每一个不同的区域由对应的正整数标记填充(1,2,3,… 来填充三个区域),区域之间的边界由 -1 填充

代码1:熟悉函数

  • 目的:
    设计一段代码学习cv::waterShed 函数
  • 实现
    创建四个cv::Mat 矩阵
  1. src:原始图像矩阵,CV_8UC3
  2. markers:注水区域矩阵,尺寸同src,CV_32S
  3. markers_show:初始为 src 的拷贝,显示注水区域,CV_8UC3
  4. wshed:显示分水岭图像矩阵
//
// Created by jacob on 12/31/20.
//

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/utils/logger.hpp>


using namespace std;

namespace cvlog = cv::utils::logging;


/***-------------------- global variable --------------------***/
cv::Mat src, markers, markers_show;
cv::RNG rng(12345);
int n = 1;


/***-------------------- main function --------------------***/
int main(int argc, char **argv) {

    cvlog::setLogLevel(cvlog::LOG_LEVEL_INFO);
    CV_LOG_INFO(NULL, "17_watershed_test.cpp")

    // 1. read src
    string filename = "../img/waternet7.png";
    src = cv::imread(filename, cv::IMREAD_COLOR);
    CV_LOG_INFO(NULL, "src.size: " << src.size() << ", src.type: " << src.type())
    cv::imshow("src", src);

    // do waterShed,实现分水岭
    src.copyTo(markers_show);
    markers = cv::Mat::zeros(src.size(), CV_32S);    // (265, 201), (75, 110)
    int x1 = 70;
    int y1 = 50;
    int x2 = 130;
    int y2 = 10;
    int r = 10;
    cv::circle(markers, cv::Point(x1, y1), r, cv::Scalar(n++), -1);
    cv::circle(markers, cv::Point(x2, y2), r, cv::Scalar(n++), -1);
    cv::imwrite("../img/markers.png", markers);
    cv::circle(markers_show, cv::Point(x1, y1), r, cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), -1);
    cv::circle(markers_show, cv::Point(x2, y2), r, cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), -1);

    cv::imshow("marker_show", markers_show);
    CV_LOG_INFO(NULL, "markers before: \n" << markers)

    cv::watershed(src, markers);
    CV_LOG_INFO(NULL, "markers after: \n" << markers)

    // 定义随机颜色列表,三个区域
    vector<cv::Vec3b> colors = {
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
    };

    cv::Mat wshed = cv::Mat::zeros(src.size(), src.type());
    for (int row = 0; row < markers.rows; ++row) {
        for (int col = 0; col < markers.cols; ++col) {
            // 获取当前位置的标记
            int label = markers.at<int>(row, col);
            if (label > 0 && label < n) { // 1,2
//                CV_LOG_INFO(NULL, "label: " << label)
//                CV_LOG_INFO(NULL, "color: " << colors[label])
                wshed.at<cv::Vec3b>(row, col) = colors[label];
            }
        }
    }
    cv::imshow("wshed", wshed);

    // wait & terminate
    cv::waitKey(0);
    cv::destroyAllWindows();
    return 0;
}

解释说明

  1. 在标记注水点矩阵 markers 上手动标记两个注水区域(1,2),并用圆形标记区域
    cv::circle(markers, cv::Point(x1, y1), r, cv::Scalar(n++), -1);
    cv::circle(markers, cv::Point(x2, y2), r, cv::Scalar(n++), -1);
    
  2. 在显示注水点矩阵 markers_show 上绘制注水点
    cv::circle(markers_show, cv::Point(x1, y1), r, cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), -1);
    cv::circle(markers_show, cv::Point(x2, y2), r, cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), -1);
    
  3. 定义两个区域的随机颜色,并使用 for 循环进行颜色填充
    vector<cv::Vec3b> colors = {
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
    };
    

运行结果
进行分水岭函数运算之前的 marker
在这里插入图片描述
进行分水岭函数运算之前的 marker
在这里插入图片描述
在这里插入图片描述

代码2:案例

目的:

  1. 实现代码1中所有功能
  2. 引入 cv 的鼠标操作
  3. 鼠标左键按下用来标记注水点
  4. 鼠标右键按下用来执行分水岭函数
//
// Created by jacob on 12/31/20.
//

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/utils/logger.hpp>


using namespace std;

namespace cvlog = cv::utils::logging;


/***-------------------- global variable --------------------***/
cv::Mat src, markers, markers_show;
cv::RNG rng(12345);
int n = 1;  // store max_no. of region
vector<cv::Vec3b> colors;

/***-------------------- callback function --------------------***/

/*
 * use ctrl + mouseLeft to check function parameters
 * aim: display points on img where mouse clicked
 */
void onMouseClick(int event, int x, int y, int flags, void *userdata) {

    vector<cv::Vec3b> color = *(vector<cv::Vec3b> *)userdata;

    // 当用户点击鼠标左键的时候,打标记
    if (event == cv::EVENT_LBUTTONDOWN) {
        int label = n;

        cv::Point seedPoint(x, y);
        CV_LOG_INFO(MOUSE, "mPt(x, y): " << seedPoint.x << ", " << seedPoint.y)

        circle(markers, cv::Point(x, y), 10, cv::Scalar(label), -1);
        circle(markers_show, cv::Point(x, y), 10, colors[label], -1);

        cv::imshow("markers_show", markers_show);
        n++;

    }

    // 当用户点击鼠标右键的时候,执行分水岭算法
    if (event == cv::EVENT_RBUTTONDOWN) {
        // do watershed
        CV_LOG_INFO(NULL, "markers before: \n" << markers)
        cv::watershed(src, markers);
        CV_LOG_INFO(NULL, "markers after: \n" << markers)

        cv::Mat wShed = cv::Mat::zeros(src.size(), src.type());
        for (int row = 0; row < markers.rows; ++row) {
            for (int col = 0; col < markers.cols; ++col) {
                // 获取当前位置的标记
                int label = markers.at<int>(row, col);
                if (label > 0 && label < n) {
                    wShed.at<cv::Vec3b>(row, col) = colors[label];
                }
            }
        }
        cv::imshow("wShed", wShed);
    }
}


/***-------------------- main function --------------------***/
int main(int argc, char **argv) {

    cvlog::setLogLevel(cvlog::LOG_LEVEL_INFO);
    CV_LOG_INFO(NULL, "17_watershed.cpp")

    // 1. read src
    string filename = "../img/wan.png";
    src = cv::imread(filename, cv::IMREAD_COLOR);
    CV_LOG_INFO(NULL, "src.size: " << src.size() << "src.type: " << src.type())
    cv::imshow("src", src);


    // test mouse callback
    markers = cv::Mat(src.size(), CV_32S);
    src.copyTo(markers_show);
    colors = {
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
    };
    cv::setMouseCallback("src", onMouseClick, &colors);


    // wait & terminate
    cv::waitKey(0);
    return 0;
}

运行结果
在这里插入图片描述

传值给回调函数

{
    colors = {
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
            cv::Vec3b(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)),
    };
    cv::setMouseCallback("src", onMouseClick, &colors);
}

void onMouseClick(int event, int x, int y, int flags, void *userdata) {

    vector<cv::Vec3b> color = *(vector<cv::Vec3b> *) userdata;
}

opencv sample tutorial 的代码写的很好,应该学习。


&&_参考

链接:C语言的外部变量
链接:The Watershed Transformation
链接:Clion 添加命令行参数并调试运行


&&_问题解决

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值