图像处理之二值图的图像分割

最近工作中需要对二值图进行图像分割,然而书上网上大多讲的是对灰度图的图像分割,思来想去觉得图像轮廓兴许是个突破口,利用轮廓点集中点与点之间的距离关系决定是否分割,事实证明分割效果可以达到不错的效果。

具体思路:待分割区域为下图中白色部分,黑色部分是背景。该图片包括3个轮廓,分别是1个外侧轮廓和2个内侧轮廓(事实上我的项目场景中最多只会存在两层轮廓的嵌套关系),理想的分割方式是把白色连通区域中狭窄的通道切割开,保留相对独立的白色区域。如下图中点a和点b,这两点连线的距离很短,且ab轮廓距离较大(如红色带箭头的线段所示),这时可以考虑将ab连接起来对整个区域进行一次图像分割。另外对于点c和点d,这两点连线的距离也很短,但是点c处于外侧轮廓上,点d处于内侧轮廓2上,无法计算轮廓距离,直接连接起来进行图像分割即可。

代码如下:

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <map>

using namespace std;
using namespace cv;

class CPoint
{
public:
    CPoint(int x, int y)
    {
        x_ = x;
        y_ = y;
    }
    CPoint(cv::Point pt)
    {
        x_ = pt.x;
        y_ = pt.y;
    }
    bool operator<(const CPoint& other) const
    {
        if(x_ == other.x_){
            return y_ < other.y_;
        }
        return x_ < other.x_;
    }

public:
    int x_;
    int y_;
};

// 判断在图片binary_img中start到end是否直线可达(连接直线的灰度值必须全是255)
bool isValidLinePath(Mat& binary_img, Point start, Point end)
{
    int cols = binary_img.cols;
    int min_x = min(start.x, end.x);
    int max_x = max(start.x, end.x);
    int min_y = min(start.y, end.y);
    int max_y = max(start.y, end.y);

    if(min_y != max_y && min_x != max_x)
    {
        float x_slope = float(end.y - start.y) / (end.x - start.x);

        int y_off = x_slope>0? min_y : max_y;
        for(int i=min_x; i<=max_x; i++)
        {
            int yoffset = (i-min_x)*x_slope + y_off;
            if(*(binary_img.data+yoffset*cols+i) == 0)
                return false;
        }
        float y_slope = float(end.x - start.x) / (end.y - start.y);
        int x_off = y_slope>0? min_x : max_x;
        for(int j=min_y; j<=max_y; j++)
        {
            int xoffset = (j-min_y)*y_slope + x_off;
            if(*(binary_img.data+j*cols+xoffset) == 0)
                return false;
        }
   }
   else
   {
       for(int i=min_y; i<=max_y; i++)
       {
           for(int j=min_x; j<=max_x; j++)
           {
               if(*(binary_img.data+i*cols+j) == 0)
                   return false;
           }
       }
   }
   return true;
}

vector<Point> getOffsetTable(int distance, const int theta=0)
{
    vector<Point> offsets;
    double minSlope = tan((double(theta)/180)*CV_PI);
    double maxSlope = tan((double(90-theta)/180)*CV_PI);

    for(int n=1; n<distance; n++)
    {
        for(int i=-n+1; i<n; i++)
        {
            if(theta != 0)
            {
                double slope = abs(double(i) / n);
                if(slope > minSlope && slope < maxSlope)
                    continue;
            }
            offsets.push_back(Point(i,-n));
            offsets.push_back(Point(i,n));
            offsets.push_back(Point(-n,i));
            offsets.push_back(Point(n,i));
        }
        if(theta == 0)
        {
            offsets.push_back(Point(-n,-n));
            offsets.push_back(Point(n,-n));
            offsets.push_back(Point(-n,n));
            offsets.push_back(Point(n,n));
        }
    }
    return offsets;
}

void fillContour(Mat& image, const vector<Point>& contour, int val)
{
    Rect rect = boundingRect(contour);
    for(int i=rect.y; i<rect.y+rect.height; i++)
    {
        for(int j=rect.x; j<rect.x+rect.width; j++)
        {
            if(*(image.data+i*image.cols+j) == 255 &&
               pointPolygonTest(contour, Point(j,i), false) >= 0)
            {
                *(image.data+i*image.cols+j) = val;
            }
        }
    }
}

bool segment(Mat& image, Mat& markImg, double lenThresh)
{
    // 分割线的两端点若在同一条轮廓,则它们的轮廓距离须大于thresh2
    const double thresh2 = 6*lenThresh;

    vector<vector<Point>> contours;
    vector<Vec4i> hierarchy;
    findContours(markImg, contours, hierarchy, CV_RETR_TREE, CHAIN_APPROX_NONE);
    if(contours.size() == 0)
        return false;

    // 找出面积最小的外侧轮廓进行处理
    int minContourId = -1;
    double minArea = static_cast<double>(INT_MAX);
    Mat contour_img = Mat::zeros(image.size(), CV_8U);
    for(int i=0; i<contours.size(); i++)
    {
        drawContours(contour_img, contours, i, Scalar(255));

        double contour_area = contourArea(contours[i]);
        if(hierarchy[i][3] == -1 && contour_area < minArea)
        {
            minContourId = i;
            minArea = contour_area;
        }
    }

    // 获取图像轮廓集合,并标明每个轮廓点集中每个坐标点的顺序索引
    map<CPoint,int> contourMap;
    int pointIndex = 0;
    for(auto point : contours[minContourId]){
        contourMap.insert(pair<CPoint,int>(CPoint(point),pointIndex++));
    }

    //获取阈值内的邻域偏移,临近点与中心点连线的角度限制在-5~5、85~95
    vector<Point> offsets = getOffsetTable(lenThresh, 5);

    // 遍历轮廓点集,将满足分割条件的分割点对保存
    Point pt1,pt2;
    double minDistance = static_cast<double>(INT_MAX);
    for(auto curPoint : contourMap)
    {
        for(auto offset : offsets)
        {
            int x_off = curPoint.first.x_ + offset.x;
            int y_off = curPoint.first.y_ + offset.y;

            // 忽略非轮廓点
            if(*(contour_img.data+y_off*image.cols+x_off) == 0)
                continue;

            auto findIter = contourMap.find(CPoint(x_off,y_off));
            // 该轮廓点的邻近轮廓点位于本轮廓内增加轮廓顺序距离判断
            if(findIter != contourMap.end())
            {
                // 若邻近点与该点的顺序索引距离小于thresh2则无效
                int distance = abs(findIter->second - curPoint.second);
                if(distance < thresh2 || distance > contourMap.size()-thresh2){
                    continue;
                }
            }

            Point tempPt1 = Point(curPoint.first.x_,curPoint.first.y_);
            Point tempPt2 = Point(x_off,y_off);

            if(!isValidLinePath(markImg, tempPt1, tempPt2))
                continue;

            double distance = pow(double(tempPt1.x-tempPt2.x),2) + pow(double(tempPt1.y-tempPt2.y),2);
            if(distance < minDistance)
            {
                pt1 = tempPt1;
                pt2 = tempPt2;
                minDistance = distance;
            }
        }
    }

    if(minDistance != static_cast<double>(INT_MAX))
    {
        // 满足分割条件
        line(image, pt1, pt2, Scalar(0), 1);
        line(markImg, pt1, pt2, Scalar(0), 1);
        return true;
    }
    else
    {
        // 若该区域无法分割则将该区域设置为背景色
        fillContour(markImg, contours[minContourId], 0);
        return true;
    }

    return false;
}


void regionSegment(Mat& binary_img, int lenThresh)
{
    Mat image = binary_img.clone();
    Mat markImg = binary_img.clone();

    while (segment(image, markImg, lenThresh));
    imshow("分割效果", image);
}

int main()
{
    Mat src_img = imread("/home/gk/program/C++/regionSegmentation/house.jpg", IMREAD_GRAYSCALE);
    // 二值化
    Mat binary_img;
    threshold(src_img, binary_img, 128, 255, CV_THRESH_BINARY);
    // 区域分割
    regionSegment(binary_img, 30);

    waitKey();
    return 0;
}

原图和分割效果图如下所示:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值