基于距离变换和分水岭算法的图像分割

1.目标

在本教程中,您将学习如何:

  • 使用OpenCV函数cv::filter2D来执行一些拉普拉斯滤波操作以进行图像锐化
  • 使用OpenCV函数cv::distanceTransform来获得二值图像的衍生表示,其中每个像素的值被其到最近的背景像素的距离所替换
  • 使用OpenCV函数cv::watershed将图像中的物体从背景中分离出来

2.代码实现

2.1 C++代码

#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char *argv[])
{
    //加载源图像,并检查它加载是否没有任何问题,然后显示它:
    CommandLineParser parser( argc, argv, "{@input | cards.png | input image}" );
    Mat src = imread( samples::findFile( parser.get<String>( "@input" ) ) );
    if( src.empty() )
    {
        cout << "Could not open or find the image!\n" << endl;
        cout << "Usage: " << argv[0] << " <Input image>" << endl;
        return -1;
    }
    // 显示原图像
    imshow("Source Image", src);
    // 将背景从白色变为黑色,因为这将有助于在使用距离变换时提取更好的结果
    Mat mask;
    inRange(src, Scalar(255, 255, 255), Scalar(255, 255, 255), mask);
    src.setTo(Scalar(0, 0, 0), mask);
    // 显示黑色背景的图像
    imshow("Black Background Image", src);
    // 创建一个我们将用来锐化我们的图像的核
    Mat kernel = (Mat_<float>(3,3) <<
                  1,  1, 1,
                  1, -8, 1,
                  1,  1, 1); 
    // 二阶导数的近似,一个相当强的核可以很好地进行拉普拉斯滤波
    //  我们需要float类型来转换所有的东西,因为核有一些负值,我们期望得到一个负的拉普拉斯像
    // 但是一个8位无符号整数(我们正在处理的)可以包含0到255的值, 所以可能的负数会被截断
    Mat imgLaplacian;
    filter2D(src, imgLaplacian, CV_32F, kernel);
    Mat sharp;
    src.convertTo(sharp, CV_32F);
    Mat imgResult = sharp - imgLaplacian;
    // 转换回8位
    imgResult.convertTo(imgResult, CV_8UC3);
    imgLaplacian.convertTo(imgLaplacian, CV_8UC3);
    // imshow( "Laplace Filtered Image", imgLaplacian );
    imshow( "New Sharped Image", imgResult );
    // 现在我们将锐化图像分别转换为灰度和二值图像:
    Mat bw;
    cvtColor(imgResult, bw, COLOR_BGR2GRAY);
    threshold(bw, bw, 40, 255, THRESH_BINARY | THRESH_OTSU);
    imshow("Binary Image", bw);
    // 现在我们准备对二值图像应用距离变换。此外,我们对输出图像进行了归一化,以便能够对结果进行可视化和阈值操作:
    Mat dist;
    distanceTransform(bw, dist, DIST_L2, 3);
    // 将距离归一化到{0.0, 1.0},这样我们就可以可视化并设定阈值
    normalize(dist, dist, 0, 1.0, NORM_MINMAX);
    imshow("Distance Transform Image", dist);
    // 获取峰值的阈值
    // 这将是前景对象的标记
    threshold(dist, dist, 0.4, 1.0, THRESH_BINARY);
    // 对dist进行膨胀操作
    Mat kernel1 = Mat::ones(3, 3, CV_8U);
    dilate(dist, dist, kernel1);
    imshow("Peaks", dist);
    // 创建dist图像的CV_8U版本, findContours()需要它
    Mat dist_8u;
    dist.convertTo(dist_8u, CV_8U);
    // 发现所有标记
    vector<vector<Point> > contours;
    findContours(dist_8u, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
    // 为分水岭算法创建标记图像
    Mat markers = Mat::zeros(dist.size(), CV_32S);
    // 绘制前景标记
    for (size_t i = 0; i < contours.size(); i++)
    {
        drawContours(markers, contours, static_cast<int>(i), Scalar(static_cast<int>(i)+1), -1);
    }
    // 绘制背景标记
    circle(markers, Point(5,5), 3, Scalar(255), -1);
    Mat markers8u;
    markers.convertTo(markers8u, CV_8U, 10);
    imshow("Markers", markers8u);
    // 执行分水岭算法
    watershed(imgResult, markers);
    Mat mark;
    markers.convertTo(mark, CV_8U);
    bitwise_not(mark, mark);
    // imshow("Markers_v2", mark);   // 如果您想查看标记图像此时的样子,请取消注释
    // 生成随机颜色
    vector<Vec3b> colors;
    for (size_t i = 0; i < contours.size(); i++)
    {
        int b = theRNG().uniform(0, 256);
        int g = theRNG().uniform(0, 256);
        int r = theRNG().uniform(0, 256);
        colors.push_back(Vec3b((uchar)b, (uchar)g, (uchar)r));
    }
    // 创建结果图像
    Mat dst = Mat::zeros(markers.size(), CV_8UC3);
    // 用随机的颜色填充已标记的物体
    for (int i = 0; i < markers.rows; i++)
    {
        for (int j = 0; j < markers.cols; j++)
        {
            int index = markers.at<int>(i,j);
            if (index > 0 && index <= static_cast<int>(contours.size()))
            {
                dst.at<Vec3b>(i,j) = colors[index-1];
            }
        }
    }
    // 可视化最终结果
    imshow("Final Result", dst);
    waitKey();
    return 0;
}

2.2 Python 代码

from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
import random as rng
rng.seed(12345)
parser = argparse.ArgumentParser(description='Code for Image Segmentation with Distance Transform and Watershed Algorithm.\
    Sample code showing how to segment overlapping objects using Laplacian filtering, \
    in addition to Watershed and Distance Transformation')
parser.add_argument('--input', help='Path to input image.', default='cards.png')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
    print('Could not open or find the image:', args.input)
    exit(0)
# 显示原图像
cv.imshow('Source Image', src)
src[np.all(src == 255, axis=2)] = 0
# 显示输出图像
cv.imshow('Black Background Image', src)
kernel = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]], dtype=np.float32)
// 二阶导数的近似,一个相当强的核可以很好地进行拉普拉斯滤波
//  我们需要float类型来转换所有的东西,因为核有一些负值,我们期望得到一个负的拉普拉斯像
// 但是一个8位无符号整数(我们正在处理的)可以包含0255的值, 所以可能的负数会被截断
imgLaplacian = cv.filter2D(src, cv.CV_32F, kernel)
sharp = np.float32(src)
imgResult = sharp - imgLaplacian
#转换回8位灰度
imgResult = np.clip(imgResult, 0, 255)
imgResult = imgResult.astype('uint8')
imgLaplacian = np.clip(imgLaplacian, 0, 255)
imgLaplacian = np.uint8(imgLaplacian)
#cv.imshow('Laplace Filtered Image', imgLaplacian)
cv.imshow('New Sharped Image', imgResult)
# 现在我们将锐化图像分别转换为灰度和二值图像:
bw = cv.cvtColor(imgResult, cv.COLOR_BGR2GRAY)
_, bw = cv.threshold(bw, 40, 255, cv.THRESH_BINARY | cv.THRESH_OTSU)
cv.imshow('Binary Image', bw)
# 现在我们准备对二值图像应用距离变换。此外,我们对输出图像进行了归一化,以便能够对结果进行可视化和阈值操作:
dist = cv.distanceTransform(bw, cv.DIST_L2, 3)
# 将距离归一化到{0.0, 1.0},这样我们就可以可视化并设定阈值
cv.normalize(dist, dist, 0, 1.0, cv.NORM_MINMAX)
cv.imshow('Distance Transform Image', dist)
_, dist = cv.threshold(dist, 0.4, 1.0, cv.THRESH_BINARY)
# 对dist图像进行膨胀操作
kernel1 = np.ones((3,3), dtype=np.uint8)
dist = cv.dilate(dist, kernel1)
cv.imshow('Peaks', dist)
dist_8u = dist.astype('uint8')
# 发现所有标记
contours, _ = cv.findContours(dist_8u, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# 为分水岭算法创建标记图像
markers = np.zeros(dist.shape, dtype=np.int32)
# 绘制前景标记
for i in range(len(contours)):
    cv.drawContours(markers, contours, i, (i+1), -1)
# 绘制背景标记
cv.circle(markers, (5,5), 3, (255,255,255), -1)
markers_8u = (markers * 10).astype('uint8')
cv.imshow('Markers', markers_8u)
cv.watershed(imgResult, markers)
#mark = np.zeros(markers.shape, dtype=np.uint8)
mark = markers.astype('uint8')
mark = cv.bitwise_not(mark)
# // 如果您想查看标记图像此时的样子,请取消注释
#cv.imshow('Markers_v2', mark)
# 生成随机颜色
colors = []
for contour in contours:
    colors.append((rng.randint(0,256), rng.randint(0,256), rng.randint(0,256)))
# 创建结果图像
dst = np.zeros((markers.shape[0], markers.shape[1], 3), dtype=np.uint8)
# 用随机的颜色填充已标记的物体
for i in range(markers.shape[0]):
    for j in range(markers.shape[1]):
        index = markers[i,j]
        if index > 0 and index <= len(contours):
            dst[i,j,:] = colors[index-1]
# 可视化最终结果
cv.imshow('Final Result', dst)
cv.waitKey()

在这里插入图片描述
在这里插入图片描述

参考目录

https://docs.opencv.org/4.x/d2/dbd/tutorial_distance_transform.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值