OpenCV学习笔记(3)_OpenCV中的灰度阈值筛选和连通域分析实例

OpenCV学习笔记(3)_OpenCV中的灰度阈值筛选和连通域分析实例

文章目录

1. 实例来源

今天这个实例来自Halcon的示例程序:

threshold.hdev

read_image (Audi2, 'audi2')
fill_interlace (Audi2, ImageFilled, 'odd')
threshold (ImageFilled, Region, 0, 90)
connection (Region, ConnectedRegions)
select_shape (ConnectedRegions, SelectedRegions, 'width', 'and', 30, 70)
select_shape (SelectedRegions, Letters, 'height', 'and', 60, 110)
dev_clear_window ()
dev_set_colored (12)
dev_display (ImageFilled)
dev_display (Letters)

图片也来自halcon的示例图片audi2.png, 该示例通过简单的灰度阈值分割,和连通域分析,将一幅图像中的车牌字符逐个提取出来.

本文通过OpenCV来实现类似的功能.有所不同的是,例程中有一个对视频图像中的错位进行校正的函数fill_interlace(它的作用是去除图像中的锯齿状错位,如图1所示),为了简化,在OpenCV实例中没有进行相应的处理,而是将Halcon中已处理好的中间过程图像提取了出来用作本实例的源图像文件.

OpenCV学习笔记(3)_OpenCV中的灰度阈值筛选和连通域分析实例

2. 实例核心代码

闲言少叙,直接上全部代码:

#include
#include "opencv2/opencv.hpp"
#include
#include
#include

int test1()
{
    srand(time(NULL));
    std::string filename = "../images/audi2.png";
    cv::Mat src = cv::imread(filename, CV_8UC1);
    cv::namedWindow("test1", CV_WINDOW_AUTOSIZE);

    cv::Mat dst, showpic;
    src.copyTo(dst);
    cv::threshold(src, dst, 90, 255, cv::THRESH_BINARY_INV);
    src.copyTo(showpic);

    cv::Mat labels, stats, centroids;
    int nccomps = cv::connectedComponentsWithStats(dst, labels, stats, centroids);

    std::vector<cv::Vec3b> colors(nccomps);
    std::vector<int> selectFlag(nccomps);
    selectFlag.assign(nccomps, 1);
    colors[0] = cv::Vec3b(0, 0, 0);
    for (int i = 1; i < nccomps; ++i)
    {
        colors[i] = cv::Vec3b(rand() % 256, rand() % 256, rand() % 256);
        if (stats.at<int>(i, cv::CC_STAT_HEIGHT) < 60 || stats.at<int>(i, cv::CC_STAT_HEIGHT) > 110
            || stats.at<int>(i, cv::CC_STAT_WIDTH) < 30 || stats.at<int>(i, cv::CC_STAT_WIDTH) > 70)
        {
            colors[i] = cv::Vec3b(0, 0, 0);
            selectFlag[i] = 0;
        }
    }

    cv::Mat imgBin(src.rows, src.cols, CV_8UC1, cv::Scalar(0));
    for (int y = 0; y < imgBin.rows; ++y)
    {
        for (int x = 0; x < imgBin.cols; ++x)
        {
            int label = labels.at<int>(y, x);

            if (0 == label || 0 == selectFlag[label])
            {
                imgBin.at<uchar>(y, x) = 0;
                continue;
            }
            CV_Assert(0  label && label  nccomps);
            imgBin.at<uchar>(y, x) = 255;
        }
    }

    cv::cvtColor(showpic, showpic, CV_GRAY2RGB);
    for (int y = 0; y < showpic.rows; ++y)
    {
        for (int x = 0; x < showpic.cols; ++x)
        {
            int label = labels.at<int>(y, x);

            if (0 == label || 0 == selectFlag[label])
                continue;
            CV_Assert(0  label && label  nccomps);
            showpic.at<cv::Vec3b>(y, x) = colors[label];
        }
    }

    cv::imshow("test1", showpic);
    cv::waitKey(0);
    return 0;
}

int main(int argc, char** argv)
{
    test1();
    return 0;
}

结果展示一下:

OpenCV学习笔记(3)_OpenCV中的灰度阈值筛选和连通域分析实例

3. 实例知识点

3.1 读取灰度图像

读取灰度图像的核心函数为 cv::imread

该函数的原型是

Mat cv::imread  (   const String &  filename,
int     flags = IMREAD_COLOR
)

其中,filename是string类型的图像地址,flags是读取的颜色类型,例如本例代码中,CV_8UC1表示的是8位1通道的灰度图像.

int test1()
{
    ......
    std::string filename = "../images/audi2.png";
    cv::Mat src = cv::imread(filename, CV_8UC1);
    ......

}

3.2 cv::threshold

读取完图像后,对这个灰度图像进行阈值分割,核心函数为 cv::threshold

该函数的原型是

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

实例中的调用为

int test1
{
    ......
    cv::Mat dst;
    src.copyTo(dst);
    cv::threshold(src, dst, 90, 255, cv::THRESH_BINARY_INV);
    ......

}

注意,这里的type决定了threshold的方法,cv::THRESH_BINARY_INV表示的是

d s t ( x , y ) = { 0 i f s r c ( x , y ) > t h r e s h m a x v a l o t h e r w i s e dst(x,y)= \left { \begin{array} {lr} 0 \qquad \qquad if \quad src(x,y) > thresh \ maxval \qquad otherwise \ \end{array} \right.d s t (x ,y )={0 i f s r c (x ,y )>t h r e s h m a x v a l o t h e r w i s e ​

可参考OpenCV在线文档中关于ThresholdTypes的说明

此处设定的thresh为90,type设置的是cv::THRESH_BINARY_INV,则目标为提取图像中灰度在[0,90)范围内的像素点(黑底白点,白点表示被选中).

3.3 cv::connectedComponentsWithStats

这是本例的核心函数之二,它的功能比较复杂,本文不做详细解释. 参考OpenCV在线文档中关于connectedComponentsWithStats的说明

在Halcon中,有更加强大的select_shape算子,根据连通域特征对连通域进行筛选.

在OpenCV中,通过此函数可完成

  1. 连通域划分
  2. 面积计算
  3. 宽 高 计算
  4. 中心点坐标计算

函数原型为

int cv::connectedComponentsWithStats    (   InputArray  image,
OutputArray     labels,
OutputArray     stats,
OutputArray     centroids,
int     connectivity = 8,
int     ltype = CV_32S
)

labels以不同的灰度来标记不同的连通域,每一个像素的灰度就代表所属连通域的序号;

stats记录连通域的信息,包括面积、宽、高等;

centroids记录连通域的中心点坐标;

实例中的函数调用为

int test1
{
    ......
    cv::Mat labels, stats, centroids;
    int nccomps = cv::connectedComponentsWithStats(dst, labels, stats, centroids);
    ......

}

nccomps表示函数返回的连通域的数量.

3.4 连通域长宽筛选

对于本实例,其实3.1~3.3节的核心函数已经完成主要的功能,但在可视化的方面,需要本节及后续两小节的内容来完成.

连通域的长宽筛选,主要是通过connectedComponentsWithStats函数的返回结果来进行.

主要是对stats的分析来完成,在本实例中:

int test1()
{
    ......

    std::vector<cv::Vec3b> colors(nccomps);
    std::vector<int> selectFlag(nccomps);
    selectFlag.assign(nccomps, 1);
    colors[0] = cv::Vec3b(0, 0, 0);
    for (int i = 1; i < nccomps; ++i)
    {
        colors[i] = cv::Vec3b(rand() % 256, rand() % 256, rand() % 256);
        if (stats.at<int>(i, cv::CC_STAT_HEIGHT) < 60 || stats.at<int>(i, cv::CC_STAT_HEIGHT) > 110
            || stats.at<int>(i, cv::CC_STAT_WIDTH) < 30 || stats.at<int>(i, cv::CC_STAT_WIDTH) > 70)
        {
            colors[i] = cv::Vec3b(0, 0, 0);
            selectFlag[i] = 0;
        }
    }
    ......

}

先忽略掉这段代码中关于colors的处理,这一块会在后面涂色相关的内容中讲述.

此处针对连通域进行遍历,取得 stats.at<int>(i, cv::CC_STAT_HEIGHT)</int>stats.at<int>(i, cv::CC_STAT_WIDTH)</int>的高和宽. 代码段中通过selectFlag来对每一个连通域是否选中进行标记.

3.5 筛选结果提取

通过3.4小节的标记,通过以下代码生成一张筛选后的结果二值图(黑底白图),结果保存在imgBin中:

int test1()
{
    ......
    cv::Mat imgBin(src.rows, src.cols, CV_8UC1, cv::Scalar(0));
    for (int y = 0; y < imgBin.rows; ++y)
    {
        for (int x = 0; x < imgBin.cols; ++x)
        {
            int label = labels.at<int>(y, x);

            if (0 == label || 0 == selectFlag[label])
            {
                imgBin.at<uchar>(y, x) = 0;
                continue;
            }
            CV_Assert(0  label && label  nccomps);
            imgBin.at<uchar>(y, x) = 255;
        }
    }
    ......

}

label为0时,实际上表示的是背景连通域.

结果在ImageWatch中显示如下

OpenCV学习笔记(3)_OpenCV中的灰度阈值筛选和连通域分析实例

3.6 筛选结果涂色显示

3.5节中的结果已经能够把选中的连通域和背景区分开(二值图),但连通域之间的区别不是很明显,要想和HDevelop中那样能够对不同的连通域进行涂色,我们需要通过随机数生成颜色,再加上3.4中标记的结果来进行.

相关的代码段为

int test1()
{   ......

    std::vector<cv::Vec3b> colors(nccomps);
    std::vector<int> selectFlag(nccomps);
    selectFlag.assign(nccomps, 1);
    colors[0] = cv::Vec3b(0, 0, 0);
    for (int i = 1; i < nccomps; ++i)
    {
        colors[i] = cv::Vec3b(rand() % 256, rand() % 256, rand() % 256);
        if (stats.at<int>(i, cv::CC_STAT_HEIGHT) < 60 || stats.at<int>(i, cv::CC_STAT_HEIGHT) > 110
            || stats.at<int>(i, cv::CC_STAT_WIDTH) < 30 || stats.at<int>(i, cv::CC_STAT_WIDTH) > 70)
        {
            colors[i] = cv::Vec3b(0, 0, 0);
            selectFlag[i] = 0;
        }
    }
    ......
    cv::cvtColor(showpic, showpic, CV_GRAY2RGB);
    for (int y = 0; y < showpic.rows; ++y)
    {
        for (int x = 0; x < showpic.cols; ++x)
        {
            int label = labels.at<int>(y, x);

            if (0 == label || 0 == selectFlag[label])
                continue;
            CV_Assert(0  label && label  nccomps);
            showpic.at<cv::Vec3b>(y, x) = colors[label];
        }
    }

    cv::imshow("test1", showpic);
    cv::waitKey(0);
    ......

}

绘制的方式是逐像素绘制,通过labels中标记好的像素所归属的连通域来分配不同的颜色.

在3.4节中对不同连通域进行标记的同时,也通过随机数,给不同连通域生成不同的颜色,这是通过一个 cv::Vec3b的vector实现的,每一个元素为一个三元数,表示RGB的数值. 每一个分量的数值是通过随机数的方式生成.

背景及筛选未通过的连通域使用黑色,其它的连通域使用彩色.

OpenCV学习笔记(3)_OpenCV中的灰度阈值筛选和连通域分析实例

Original: https://blog.csdn.net/horsee/article/details/122848231
Author: CarnivoreRabbit
Title: OpenCV学习笔记(3)_OpenCV中的灰度阈值筛选和连通域分析实例

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/641674/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球