OpenCV 4.x API 详解与C++实例-图像变换

第三节 图像变换杂项

本节主要介绍图像分割相关的一些API函数,比如theshold、watershed等。

1、cv::threshold、cv::adaptiveThreshold


1)cv::threshold:将固定级别阈值应用于每个数组元素。

double cv::threshold(InputArray src,OutputArray dst,double thresh,double maxval,int type)

threshold函数将固定级别的阈值应用于多通道数组或图像。经常应用于灰度图像获取二值图像或用于消除噪声,即过滤具有太小或太大值的像素。threshold函数支持以下几种阈值处理类型:

类型 类型描述
THRESH_BINARY dst ( x , y ) = { maxval i f src ( x , y ) > thresh ) 0 o t h e r w i s e \texttt{dst} (x,y) = \begin{cases}{\texttt{maxval}}&{if \texttt{src}(x,y) > \texttt{thresh})}\\{0} &{otherwise} \end{cases} dst(x,y)={ maxval0ifsrc(x,y)>thresh)otherwise
THRESH_BINARY_INV dst ( x , y ) = { 0 i f src ( x , y ) > thresh ) m a x v a l o t h e r w i s e \texttt{dst} (x,y) = \begin{cases}{\texttt{0}}&{if \texttt{src}(x,y) > \texttt{thresh})}\\{maxval} &{otherwise} \end{cases} dst(x,y)={ 0maxvalifsrc(x,y)>thresh)otherwise
THRESH_TRUNC dst ( x , y ) = { threshold i f ( src ( x , y ) > thresh ) src ( x , y ) o t h e r w i s e \texttt{dst} (x,y) = \begin{cases}{\texttt{threshold}}&{if (\texttt{src}(x,y) > \texttt{thresh})}\\{\texttt{src}(x,y)}&{otherwise} \end{cases} dst(x,y)={ thresholdsrc(x,y)if(src(x,y)>thresh)otherwise
THRESH_TOZERO dst ( x , y ) = { src ( x , y ) i f ( src ( x , y ) > thresh ) 0 o t h e r w i s e \texttt{dst} (x,y) = \begin{cases}{\texttt{src}(x,y)}&{if (\texttt{src}(x,y) > \texttt{thresh})}\\{0}&{otherwise}\end{cases} dst(x,y)={ src(x,y)0if(src(x,y)>thresh)otherwise
THRESH_TOZERO_INV dst ( x , y ) = { 0 i f ( src ( x , y ) > thresh ) src ( x , y ) o t h e r w i s e \texttt{dst} (x,y) = \begin{cases}{0}&{if (\texttt{src}(x,y) > \texttt{thresh})}\\{\texttt{src}(x,y)}&{otherwise}\end{cases} dst(x,y)={ 0src(x,y)if(src(x,y)>thresh)otherwise
THRESH_MASK
THRESH_OTSU 标记,使用Otsu算法选择最佳阈值
THRESH_TRIANGLE 标记,使用三角算法选择最佳阈值

参数如下:

参数名称 参数描述
src 输入数组(多通道,8位或32位浮点)。
dst 输出数组。与src具有相同大小,类型和相同通道数的
thresh 指定阈值
maxval THRESH_BINARY和THRESH_BINARY_INV阈值类型使用的最大值。
type 阈值化类型(参考 ThresholdTypes).
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int minThreshold = 128;
int maxThreshold = 255;
int threshType = cv::THRESH_BINARY;
cv::Mat src,dst;

void apply(){
    cv::threshold(src,dst,minThreshold,maxThreshold,threshType);
    cv::imshow("threshold",dst);
}

void onMinThresholdChanged(int pos,void*userdata){
    apply();
}

void onMaxThresholdChanged(int pos,void*userdata){
    apply();
}

int main()
{
    // 读取图像
    src = cv::imread("images/f1.jpg",0);
    if(src.empty()){
        cerr << "cannot read image.\n";
        return EXIT_FAILURE;
    }
    cv::Mat dst;

    cv::namedWindow("threshold");
    cv::createTrackbar("min thresh","threshold",&minThreshold,255,onMinThresholdChanged);
    cv::createTrackbar("max thresh","threshold",&maxThreshold,255,onMaxThresholdChanged);
    char key = 0;
    cv::imshow("threshold",src);
    while(true){

        key = cv::waitKey(10);
        if(key == '1'){
            threshType = cv::THRESH_BINARY;
            apply();
        }
        if(key == '2'){
            threshType = cv::THRESH_BINARY_INV;
            apply();
        }
        if(key == '3'){
            threshType = cv::THRESH_TOZERO;
            apply();
        }
        if( key == '4'){
            threshType = cv::THRESH_TOZERO_INV;
            apply();
        }
        if(key == '5'){
            threshType = cv::THRESH_TRIANGLE;
            apply();
        }

        if(key == 27){
            break;
        }

    }

    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

当参数type为THRESH_OTSU时,计算最佳的阈值,最后再将阈值应用至图像。

2)cv::adaptiveThreshold:将自适应阈值应用于数组或图像。

void cv::adaptiveThreshold(InputArray src,OutputArray dst,double maxValue,int adaptiveMethod,int thresholdType,int blockSize,double C)

该函数根据以下公式将灰度图像转换为二进制图像:

  • THRESH_BINARY dst ( x , y ) = { maxval i f src ( x , y ) > thresh ) 0 o t h e r w i s e \texttt{dst} (x,y) = \begin{cases}{\texttt{maxval}}&{if \texttt{src}(x,y) > \texttt{thresh})}\\{0} &{otherwise} \end{cases} dst(x,y)={ maxval0ifsrc(x,y)>thresh)otherwise
  • THRESH_BINARY_INV dst ( x , y ) = { 0 i f src ( x , y ) > thresh ) m a x v a l o t h e r w i s e \texttt{dst} (x,y) = \begin{cases}{\texttt{0}}&{if \texttt{src}(x,y) > \texttt{thresh})}\\{maxval} &{otherwise} \end{cases} dst(x,y)={ 0maxvalifsrc(x,y)>thresh)otherwise

其中 T ( x , y ) T(x,y) T(x,y)为每个像素单独计算的阈值(请参见adaptiveMethod参数)。

该函数可以就地处理图像。参数如下:

参数 参数描述
src 输入图像。8位单通道图像
dst 输出图像,与src大小相同
maxValue 分配给满足条件的像素的非零值
adaptiveMethod 指定的自适应方法,请参考 AdaptiveThresholdTypes. 类型BORDER_REPLICATE | BORDER_ISOLATED 用于处理边界。
thresholdType 阈值处置方法类型,只能是THRESH_BINARYTHRESH_BINARY_INV, 请参考ThresholdTypes.
blockSize 用于计算像素阈值的像素邻域的大小:3、5、7,依此类推。
C 从平均值或加权平均值中减去常数。 通常,它为正,但也可以为零或负。

adaptiveThreshold的类型如下:

名称 描述
ADAPTIVE_THRESH_MEAN_C 阈值 T ( x , y ) T(x,y) T(x,y) ( x , y ) (x,y) (x,y) b l o c k S i z e × b l o c k S i z e blockSize×blockSize blockSize×blockSize邻域的平均值减去 C C C
ADAPTIVE_THRESH_GAUSSIAN_C 阈值 T ( x , y ) T(x,y) T(x,y) ( x , y ) (x,y) (x,y)减去C的 b l o c k S i z e × b l o c k S i z e blockSize×blockSize blockSize×blockSize邻域的加权和(与高斯窗的互相关)。 默认sigma(标准差)用于指定的 b l o c k S i z e blockSize blockSize。 参见getGaussianKernel
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    // 读取图像
    cv::Mat src = cv::imread("images/f1.jpg",0);
    if(src.empty()){
        cerr << "cannot read image.\n";
        return EXIT_FAILURE;
    }
    cv::Mat dst1,dst2;

    // 应用自适应滤波
    cv::adaptiveThreshold(src,dst1,180,cv::ADAPTIVE_THRESH_MEAN_C,cv::THRESH_BINARY,3,0);
    cv::adaptiveThreshold(src,dst2,180,cv::ADAPTIVE_THRESH_GAUSSIAN_C,cv::THRESH_BINARY,3,0);

    cv::imshow("src",src);
    cv::imshow("thresh mean",dst1);
    cv::imshow("thresh gaussian",dst2);

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

    return 0;
}

在这里插入图片描述

2、cv::blendLinear


对两个图像执行线性融合:

dst ( i , j ) = weights1 ( i , j ) ∗ src1 ( i , j ) + weights2 ( i , j ) ∗ src2 ( i , j ) \texttt{dst}(i,j) = \texttt{weights1}(i,j)*\texttt{src1}(i,j) + \texttt{weights2}(i,j)*\texttt{src2}(i,j) dst(i,j)=weights1(i,j)src1(i,j)+weights2(i,j)src2(i,j)

void cv::blendLinear(InputArray src1,InputArray weights1,InputArray weights2,OutputArray dst)

参数如下:

参数名称 参数描述
src1 第一张图像,它的类型为CV_8UC(n)
src2 第二张图像,大小、类型与src1相同
weights1 它的类型为CV_32FC1,大小与src1相同。
weights2 它的类型为CV_32FC1,大小与src1相同。
dst 输出图像;如果它的大小和类型与src1不同,则会创建它。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

int main()
{
    // 读取图像
    cv::Mat src1 = cv::imread("images/f1.jpg");
    if(src1.empty()){
        cerr << "cannot read image.\n";
        return EXIT_FAILURE;
    }
    cv::Mat src2 = cv::imread("images/f2.jpg");
    if(src2.empty()){
        cerr << "cannot read image.\n";
        return EXIT_FAILURE;
    }
    cv::Mat dst;

    cv::Mat weights1(src1.size(),CV_32FC1,cv::Scalar::all(0.5));
    cv::Mat weights2(src1.size(),CV_32FC1,cv::Scalar::all(0.5));

    cv::blendLinear(src1,src2,weights1,weights2,dst);

    cv::imshow("src1",src1);
    cv::imshow("src2",src2);
    cv::imshow("dst",dst);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

3、cv::distanceTransform


计算源图像的每个像素到最接近的零像素的距离。

  • void cv::distanceTransform(InputArray src,OutputArray dst,OutputArray labels,int distanceType,int maskSize,int labelType = DIST_LABEL_CCOMP)

函数cv :: distanceTransform计算每个二进制图像像素到最近的零像素之间的近似或精确距离。对于零图像像素,该距离显然将为零。

参数名称 参数描述
src 输入图像;8位单通道二值图像
dst 输出具有计算出的距离的图像。 它是与src大小相同的8位或32位浮点单通道图像。
labels 输出标签的二维数组(离散Voronoi图)。 它的类型为CV_32SC1,大小与src相同。
distanceType 距离类型,请参考 DistanceTypes
maskSize 距离变换蒙版的大小, 请参考DistanceTransformMasks。不支持 DIST_MASK_PRECISE 。 对于 DIST_L1DIST_C 距离类型, 该参数被强制为3,因为3×3遮罩的结果与5×5相同或更大。
labelType 要构建的标签数组的类型, 参考DistanceTransformLabelTypes.

重载函数:

  • void cv::distanceTransform(InputArray src,OutputArray dst,int distanceType,int maskSize,int dstType = CV_32F)
参数名称 参数描述
8src 输入图像;8位单通道二值图像
dst 输出具有计算出的距离的图像。 它是与src大小相同的8位或32位浮点单通道图像。
distanceType 距离类型,请参考 DistanceTypes
maskSize 距离变换蒙版的大小, 请参考DistanceTransformMasks。不支持 DIST_MASK_PRECISE 。 对于 DIST_L1DIST_C 距离类型, 该参数被强制为3,因为3×3遮罩的结果与5×5相同或更大。
dstType 输出图像的类型。 它可以是CV_8U或CV_32F。 CV_8U类型只能用于该函数的第一个变体,且distanceType == DIST_L1

距离类型如下:

类型名称 类型描述
DIST_USER 用户定义的距离。
DIST_L1 distance = |x1-x2| + |y1-y2|
DIST_L2 简单欧氏距离
DIST_C distance = max(|x1-x2|,|y1-y2|)
DIST_L12 L1-L2 metric: distance = 2(sqrt(1+x*x/2) - 1))
DIST_FAIR $distance = c^2(
DIST_WELSCH d i s t a n c e = c 2 / 2 ( 1 − e x p ( − ( x / c ) 2 ) ) , c = 2.9846 distance = c^2/2(1-exp(-(x/c)^2)), c = 2.9846 distance=c2/2(1exp((x/c)2)),c=2.9846
DIST_HUBER d i s t a n c e = ∥ x ∥ < c ? x 2 / 2 : c ( ∥ x ∥ − c / 2 ) , c = 1.345 distance = \|x\|<c ? x^2/2 : c(\|x\|-c/2), c=1.345 distance=x<c?x2/2:c(xc/2),c=1.345
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;

void apply(const cv::Mat& src,cv::Mat& dst,cv::Mat& labels,int distType){
    // 执行距离变换
    cv::Mat binary;
    cv::threshold(src,binary,100,255,cv::THRESH_BINARY);
    cv::distanceTransform(binary,dst,labels,cv::DIST_L1,3);
    cv::convertScaleAbs(dst,dst);
    cv::imshow("src",src);
    cv::imshow("dst",dst);
}

int main()
{
    // 读取图像
    cv::Mat src = cv::imread("images/f1.jpg",0);
    if(src.empty()){
        cerr << "cannot read image.\n";
        return EXIT_FAILURE;
    }

    cv::Mat dst,labels;

    // 阈值化

    int distType = cv::DIST_USER;
    char key = -1;
    cv::imshow("src",src);
    while(true){
        key = cv::waitKey(10);
        if(key == '1'){
            distType = cv::DIST_USER;
            apply(src,dst,labels,distType);
            cv::setWindowTitle("dst","Distance transform:DIST_USER");
        }
        if(key == '2'){
            distType = cv::DIST_L1;
            apply(src,dst,labels,distType);
            cv::setWindowTitle("dst","Distance transform:DIST_L1");
        }
        if(key == '3'){
            distType = cv::DIST_L2;
            apply(src,dst,labels,distType);
            cv::setWindowTitle("dst","Distance transform:DIST_L2");
        }
        if(key == '4'){
            distType = cv::DIST_C;
            apply(src,dst,labels,distType);
            cv::setWindowTitle("dst","Distance transform:DIST_C");
        }
        if(key == '5'){
            distType = cv::DIST_L12;
            apply(src,dst,labels,distType);
            cv::setWindowTitle("dst","Distance transform:DIST_L12");
        }
        if(key == '6'){
            distType = cv::DIST_FAIR;
            apply(src,dst,labels,distType);
            cv::setWindowTitle("dst","Distance transform:DIST_FAIR");
        }
        if(key == '7'){
            distType = cv::DIST_WELSCH;
            apply(src,dst,labels,distType);
            cv::setWindowTitle("dst","Distance transform:DIST_WELSCH");
        }
        if(key == '8'){
            distType = cv::DIST_HUBER;
            apply(src,dst,labels,distType);
            cv::setWindowTitle("dst","Distance transform:DIST_HUBER");
        }

        if(key == 27){
            break;
        }
    }

    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

4、cv::floodFill


用给定的颜色填充连接的组件。

  • int cv::floodFill(InputOutputArray image,InputOutputArray mask,Point seedPoint,Scalar newVal,Rect * rect = 0,Scalar loDiff = Scalar(),Scalar upDiff = Scalar(),int flags = 4)

函数cv :: floodFill使用指定的颜色从种子点开始填充连接的组件。 连通性由相邻像素的颜色/亮度接近度确定。 在以下情况下, ( x , y ) (x,y) (x,y)处的像素被认为属于重绘的域:

  • 在灰度图像和浮动范围的情况下: src ( x ′ , y ′ ) − loDiff ≤ src ( x , y ) ≤ src ( x ′ , y ′ ) + upDiff \texttt{src} (x',y')- \texttt{loDiff} \leq \texttt{src} (x,y) \leq \texttt{src} (x',y')+ \texttt{upDiff} src(x,y)loDiffsrc(x,y)src(x,y)+upDiff
  • 在灰度图像和固定范围的情况下: src ( seedPoint . x , seedPoint . y ) − loDiff ≤ src ( x , y ) ≤ src ( seedPoint . x , seedPoint . y ) + upDiff \texttt{src} ( \texttt{seedPoint} .x, \texttt{seedPoint} .y)- \texttt{loDiff} \leq \texttt{src} (x,y) \leq \texttt{src} ( \texttt{seedPoint} .x, \texttt{seedPoint} .y)+ \texttt{upDiff} src(seedPoint.x,seedPoint.y)loDiffsrc(x,y)src(seedPoint.x,seedPoint.y)+upDiff
  • 在彩色图像和浮动范围的情况下:
    • src ( x ′ , y ′ ) r − loDiff r ≤ src ( x , y ) r ≤ src ( x ′ , y ′ ) r + upDiff r , \texttt{src} (x',y')_r- \texttt{loDiff} _r \leq \texttt{src} (x,y)_r \leq \texttt{src} (x',y')_r+ \texttt{upDiff} _r, src(x,y)rloDiffrsrc(x,y)rsrc(x,y)r+upDiffr,
    • src ( x ′ , y ′ ) g − loDiff g ≤ src ( x , y ) g ≤ src ( x ′ , y ′ ) g + upDiff g \texttt{src} (x',y')_g- \texttt{loDiff} _g \leq \texttt{src} (x,y)_g \leq \texttt{src} (x',y')_g+ \texttt{upDiff} _g src(x,y)gloDiffgsrc(x,y)gsrc(x,y)g+upDiffg
    • src ( x ′ , y ′ ) b − loDiff b ≤ src ( x , y ) b ≤ src ( x ′ , y ′ ) b + upDiff b \texttt{src} (x',y')_b- \texttt{loDiff} _b \leq \texttt{src} (x,y)_b \leq \texttt{src} (x',y')_b+ \texttt{upDiff} _b src(x,y)bloDiffbsrc(x,y)bsrc(x,y)b+upDiffb
  • 在彩色图像和固定范围的情况下:
    • src ( seedPoint . x , seedPoint . y ) r − loDiff r ≤ src ( x , y ) r ≤ src ( seedPoint . x , seedPoint . y ) r + upDiff r , \texttt{src} ( \texttt{seedPoint} .x, \texttt{seedPoint} .y)_r- \texttt{loDiff} _r \leq \texttt{src} (x,y)_r \leq \texttt{src} ( \texttt{seedPoint} .x, \texttt{seedPoint} .y)_r+ \texttt{upDiff} _r, src(seedPoint.x,seedPoint.y)rloDiffrsrc(x,y)rsrc(seedPoint.x,seedPoint.y)r+upDiffr,
    • src ( seedPoint . x , seedPoint . y ) g − loDiff g ≤ src ( x , y ) g ≤ src ( seedPoint . x , seedPoint . y ) g + upDiff g \texttt{src} ( \texttt{seedPoint} .x, \texttt{seedPoint} .y)_g- \texttt{loDiff} _g \leq \texttt{src} (x,y)_g \leq \texttt{src} ( \texttt{seedPoint} .x, \texttt{seedPoint} .y)_g+ \texttt{upDiff} _g src(seedPoint.x,seedPoint.y)gloDiffgsrc(x,y)gsrc(seedPoint.x,seedPoint.y)g+upDiffg
    • src ( seedPoint . x , seedPoint . y ) b − loDiff b ≤ src ( x , y ) b ≤ src ( seedPoint . x , seedPoint . y ) b + upDiff b \texttt{src} ( \texttt{seedPoint} .x, \texttt{seedPoint} .y)_b- \texttt{loDiff} _b \leq \texttt{src} (x,y)_b \leq \texttt{src} ( \texttt{seedPoint} .x, \texttt{seedPoint} .y)_b+ \texttt{upDiff} _b src(seedPoint.x,seedPoint.y)bloDiffbsrc(x,y)bsrc(seedPoint.x,seedPoint.y)b+upDiffb

其中 s r c ( x ′ , y ′ ) src(x',y') src(x,y)是已知属于该分量的像素邻居之一的值。 也就是说,要添加到连接的组件中,像素的颜色/亮度应足够接近:

  • 在浮动范围内,已经属于连接的组件的邻居之一的颜色/亮度。
  • 在固定范围内的情况下,种子点的颜色/亮度。

使用这些功能可以就地用指定的颜色标记连接的组件,或者构建遮罩然后提取轮廓,或者将该区域复制到另一个图像等等。

参数名称 参数描述
image 输入/输出1或3通道,8位或浮点图像。除非通过FLOODFILL_MASK_ONLY的第二个标志在其中设置了此功能,否则该函数会对其进行修改。 请参阅下面的详细信息。
mask 操作掩码应该是单通道8位图像,比图像宽2像素,高2像素。 由于这既是输入参数,也是输出参数,因此必须负责对其进行初始化。填充填充不能跨越输入蒙版中的非零像素。例如,边缘检测器的输出可用作遮罩,以停止填充边缘。 在输出时,与图像中填充像素相对应的遮罩中的像素将设置为1或设置为标志中指定的a值,如下所述。此外,该功能会用一个填充边框的边框,以简化内部处理。 因此,可以在多次调用该函数时使用相同的遮罩,以确保填充区域不重叠。
seedPoint 起始位置
newVal 重绘的域像素的新值。
loDiff 当前观察到的像素与属于该组件的相邻像素之一或添加到该组件的种子像素之间的最大较低亮度/色差。
upDiff 当前观察到的像素与属于该组件的相邻像素之一或添加到该组件的种子像素之间的最大上亮度/色差。
rect 该函数将可选的输出参数设置为重绘域的最小边界矩形。
flags 操作标志。 前8位包含连接性值。 默认值4表示仅考虑四个最近的相邻像素(共享边缘的像素)。 连通性值为8表示将考虑八个最近的相邻像素(共享一个角的像素)。 接下来的8位(8-16)包含一个介于1和255之间的值,用于填充掩码(默认值为1)。 例如,4 \ | (255 << 8)将考虑4个最近的邻居,并使用255的值填充掩码。以下附加选项占用较高的位,因此可以进一步使用逐位或(\ |)与连接性和掩码填充值组合 ,请参阅FloodFillFlags

FloodFillFlags描述如下:

标志名称 标志描述
FLOODFILL_FIXED_RANGE 如果设置,则考虑当前像素和种子像素之间的差异。 否则,将考虑相邻像素之间的差异(即,范围是浮动的)。
FLOODFILL_MASK_ONLY 如果设置,则该函数不会更改图像(将忽略newVal),并且仅使用标志的位8-16中指定的值填充掩码,如上所述。 该选项仅在具有mask参数的功能变量中有意义。
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
int main()
{
    // 读取图像
    cv::Mat src = cv::imread("images/f1.jpg");
    if(src.empty()){
        cerr << "cannot read image.\n";
        return EXIT_FAILURE;
    }

    cv::Point seed(src.cols / 2,src.rows / 2);
    int maskVal = 255;  //掩码图像的数值
    int connectivity = 4;  //连通邻域方式
    int flags = connectivity|(maskVal<<8)|
            cv::FLOODFILL_FIXED_RANGE;  //漫水填充操作方式标志

    //设置与选中像素点的差值
    cv::Scalar loDiff = cv::Scalar(20, 20, 20);
    cv::Scalar upDiff = cv::Scalar(20, 20, 20);

    cv::Mat mask = cv::Mat::zeros(src.rows + 2, src.cols + 2, CV_8UC1);
    cv::RNG rng;
    cv::Rect rect;
    for(int i = 0;i < src.rows;i++){
        for(int j = 0;j < src.cols;j++){
            //随机产生图像中某一像素点
            int py = rng.uniform(0,src.rows-1);
            int px = rng.uniform(0, src.cols - 1);
            seed.x = px;
            seed.y = py;
            cv::Scalar newVal = cv::Scalar(rng.uniform(0, 255), rng.uniform(0, 255),
                                           rng.uniform(0, 255));
            cv::floodFill(src, mask, seed, newVal, &rect,loDiff,upDiff,flags);
        }
    }
    cv::imshow("src",src);
    cv::imshow("mask",mask);
    cv::waitKey();
    cv::destroyAllWindows();

    return 0;
}

在这里插入图片描述

5、cv::watershed


使用分水岭算法执行基于标记的图像分割。

void cv::watershed(InputArray image,InputOutputArray markers)

该函数实现了分水岭的一种变体,基于非参数标记的分割算法:Fernand Meyer. Color image segmentation. In Image Processing and its Applications, 1992., International Conference on, pages 303–306. IET, 1992.

在将图像传递给函数之前,您必须使用正(> 0)索引在图像标记中大致勾勒出所需区域。因此,每个区域都表示为一个或多个连接的组件,像素值分别为1、2、3等。可以使用findContours和drawContours从二进制掩码中检索此类标记。这些标记是将要处理图像区域的“种子”。标记中与轮廓区域的关系未知并应由算法定义的所有其他像素应设置为0。在函数输出中,标记中的每个像素在区域之间的边界处设置为“种子”分量的值或-1。

注意:任何两个相邻连接的组件都不必由分水岭边界(-1的像素)分隔; 例如,他们可以在传递给该功能的初始标记图像中相互触摸。

参数名称 参数描述
image 输入8位三通道图像
markers 标记的输入/输出32位单通道图像(映射)。 它的大小应与image相同。
#include"opencv2/opencv.hpp"
#include <string>

using namespace cv;
using namespace std;

class WatershedSegmenter{
private:
    cv::Mat markers;
public:
    void setMarkers(cv::Mat& markerImage)
    {
        markerImage.convertTo(markers, CV_32S);
    }

    cv::Mat process(cv::Mat &image)
    {
        cv::watershed(image, markers);
        markers.convertTo(markers,CV_8U);
        return markers;
    }
};


int main(int argc, char* argv[])
{
    // 读取图像
    cv::Mat image = cv::imread("images/f1.jpg");
    if(image.empty()){
        cerr << "cannot read image.\n";
        return EXIT_FAILURE;
    }
    cv::Mat blank(image.size(),CV_8UC3,cv::Scalar(0xFF));
    cv::Mat dest(image.size(),CV_8UC3,cv::Scalar(0xFF));
    imshow("originalimage", image);

    // 创建标记图像
    cv::Mat markers(image.size(),CV_32SC1,cv::Scalar(-1));
    markers(Rect(image.cols/2,image.rows/2,50, 50)) = Scalar::all(2);


    // 创建watershed分割对象
    WatershedSegmenter segmenter;
    segmenter.setMarkers(markers);
    cv::Mat result = segmenter.process(image);
    result.convertTo(result,CV_8U);

    bitwise_and(image, blank, dest, result);
    imshow("final_result", dest);

    cv::waitKey(0);

    return 0;
}

在这里插入图片描述

6、cv::grabCut


运行grabcut算法对图像进行分割。

void cv::grabCut(InputArray img,InputOutputArray mask,Rect rect,InputOutputArray bgdModel,InputOutputArray fgdModel,int iterCount,int mode = GC_EVAL)

参数如下

参数名称 参数描述
img 输入图像,8位3通道图像
mask 输入/输出8位单通道掩码。 当模式设置为时,掩码由函数初始化 GC_INIT_WITH_RECT,为 GrabCutClasses包含的类型
rect 包含分割对象的ROI.ROI外部的像素被标记为“明显背景。. 该参数仅在mode ==GC_INIT_WITH_RECT时使用。
bgdModel 背景模型的临时数组。 处理同一图像时,请勿对其进行修改。
fgdModel 前景模型的临时数组。 处理同一图像时,请勿对其进行修改。
iterCount 算法在返回结果之前应进行的迭代次数。 请注意,可以通过使用mode GC_INIT_WITH_MASK或modeGC_EVAL 来进行进一步调用来完善结果
mode 操作模式,请参考GrabCutModes
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main( )
{
    // 读取图像我
    Mat image = cv::imread("images/leaf.jpg");

    if(! image.data )
    {
        cout <<  "Could not open or find the image" << std::endl ;
        return -1;
    }

    // 定义边界框
    int border = 20;
    int border2 = border + border;
    cv::Rect rectangle(border,border,image.cols-border2,image.rows-border2);

    cv::Mat result;
    cv::Mat bgModel,fgModel;

    // 使用GrabCut算法进行分割
    cv::grabCut(image,
        result,
        rectangle,
        bgModel,fgModel,
        1,
        cv::GC_INIT_WITH_RECT);

    // 获取标记为可能前景的像素
    cv::compare(result,cv::GC_PR_FGD,result,cv::CMP_EQ);
    // 生成输出图像
    cv::Mat foreground(image.size(),CV_8UC3,cv::Scalar(255,255,255));
    image.copyTo(foreground,result); // bg pixels not copied

    // 绘制矩形
    cv::rectangle(image, rectangle, cv::Scalar(255,255,255),1);
    cv::namedWindow("Image");
    cv::imshow("Image",image);

    // 显示结果
    cv::namedWindow("Segmented Image");
    cv::imshow("Segmented Image",foreground);


    waitKey();
    return 0;

}


在这里插入图片描述

7、cv::integral


计算图像积分。

void cv::integral(InputArray src,OutputArray sum,OutputArray sqsum,OutputArray tilted,int sdepth = -1,int sqdepth = -1)

该函数为源图像计算一个或多个积分图像,如下所示:

sum ( X , Y ) = ∑ x < X , y < Y image ( x , y ) \texttt{sum} (X,Y) = \sum _{x<X,y<Y} \texttt{image} (x,y) sum(X,Y)=x<X,y<Yimage(x,y)

sqsum ( X , Y ) = ∑ x < X , y < Y image ( x , y ) 2 \texttt{sqsum} (X,Y) = \sum _{x<X,y<Y} \texttt{image} (x,y)^2 sqsum(X,Y)=x<X,y<Yimage(x,y)2

tilted ( X , Y ) = ∑ y < Y , a b s ( x − X + 1 ) ≤ Y − y − 1 image ( x , y ) \texttt{tilted} (X,Y) = \sum _{y<Y,abs(x-X+1) \leq Y-y-1} \texttt{image} (x,y) tilted(X,Y)=y<Y,abs(xX+1)Yy1image(x,y)

使用这些积分图像,您可以在恒定时间内计算出图像的特定直立或旋转矩形区域上的和,均值和标准偏差,例如:

∑ x 1 ≤ x < x 2 ,   y 1 ≤ y < y 2 image ( x , y ) = sum ( x 2 , y 2 ) − sum ( x 1 , y 2 ) − sum ( x 2 , y 1 ) + sum ( x 1 , y 1 ) \sum _{x_1 \leq x < x_2, \, y_1 \leq y < y_2} \texttt{image} (x,y) = \texttt{sum} (x_2,y_2)- \texttt{sum} (x_1,y_2)- \texttt{sum} (x_2,y_1)+ \texttt{sum} (x_1,y_1) x1x<x2,y1y<y2image(x,y)=sum(x2,y2)sum(x1,y2)sum(x2,y1)+sum(x1,y1)

例如,可以使用可变的窗口大小进行快速模糊或快速块相关。 在多通道图像的情况下,每个通道的总和是独立累加的。

作为一个实际示例,下图显示了直矩形Rect(3,3,3,2)和倾斜矩形Rect(5,1,2,3)的积分计算。 显示了原始图像中的选定像素,以及积分图像中的相对像素之和。

在这里插入图片描述

参数如下:

参数名称 参数描述
src 输入图像为W×H,8位或浮点数(32f或64f)。
sum 整数图像为(W + 1)×(H + 1),32位整数或浮点数(32f或64f)。
sqsum 积分像素值的积分图像; 它是(W + 1)×(H + 1),双精度浮点(64f)数组。
tilted 旋转45度的图像的积分; 它是(W + 1)×(H + 1)数组,其数据类型与sum相同。
sdepth 积分和倾斜积分图像的所需深度CV_32S,CV_32F或CV_64F。
sqdepth 平方像素值CV_32F或CV_64F的积分图像的所需深度。

另外还有以下重载形式:

  • void cv::integral(InputArray src,OutputArray sum,OutputArray sqsum,int sdepth = -1,int sqdepth = -1)
  • void cv::integral(InputArray src,OutputArray sum,int sdepth = -1)
#include <ostream>
#include<opencv2/opencv.hpp>

int main(int argc, char *argv[])
{
    // 读取图像
    cv::String fileName = "images/leaf.jpg";
    cv::Mat src = cv::imread(fileName);
    cv::Mat src1 = src.clone();
    if(src.data == NULL){
        printf("Image failed to read\n");
        return -1;
    }
    cv::imshow("src",src);


    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
    gray.convertTo(gray,CV_32F);
    cv::Mat dstInte;//(gray.size(),CV_32S);

    // 计算积分图
    cv::integral(gray,dstInte);
    cv::Mat dstInterNORM = dstInte.clone();
    cv::normalize(dstInterNORM,dstInterNORM,255,0,cv::NORM_L2);
    cv::imshow("integral image", dstInterNORM);

    cv::waitKey(0);
    cv::destroyAllWindows();
    return 1;
}

#include <ostream>
#include<opencv2/opencv.hpp>

int main(int argc, char *argv[])
{
    // 读取图像
    cv::String fileName = "images/leaf.jpg";
    cv::Mat src = cv::imread(fileName);
    cv::Mat src1 = src.clone();
    if(src.data == NULL){
        printf("Image failed to read\n");
        return -1;
    }
    cv::imshow("src",src);


    cv::Mat gray;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
    gray.convertTo(gray,CV_32F);
    cv::Mat dstInte;//(gray.size(),CV_32S);

    // 计算积分图
    cv::integral(gray,dstInte);
    cv::Mat dstInterNORM = dstInte.clone();
    cv::normalize(dstInterNORM,dstInterNORM,255,0,cv::NORM_L2);
    cv::imshow("integral image", dstInterNORM);

    cv::waitKey(0);
    cv::destroyAllWindows();
    return 1;
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/wujuxKkoolerter/article/details/112300485
今日推荐