c++ - 如何在 OpenCV 中基于线分割图像

标签 c++ image opencv

我正在尝试进行文本分割。下面的附件是它的结果。

Line Images

我设法形成线条来划分图像。但是,我坚持根据我发现的线条分割图像。 正如所附图片中标记的(红色文字),我想将图像分成 5 个不同的图像,但我不知道应该从哪里开始。我发现的所有方法都只适用于直线。

Header

代码 - 来源:

#include <opencv2/core/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>

#include <iostream>
#include <ctype.h>
#include <fstream>

#define _USE_MATH_DEFINES
#include <math.h>
#define JC_VORONOI_IMPLEMENTATION
#include "jc_voronoi.h"


typedef struct compPoint
{
  cv::Point pointer;
  int siteNum, size ;
};

int maximumSize;
float average=0;

std::vector<compPoint> generatePoint(cv::Mat image);
void generateVoronoi(std::vector<cv::Point> points, int width, int height);
static inline jcv_point remap(const jcv_point* pt, const jcv_point* min, const jcv_point* max, const jcv_point* scale);
static void draw_line(int x0, int y0, int x1, int y1, unsigned char* image, int width, int height, int nchannels, unsigned char* color);
static void plot(int x, int y, unsigned char* image, int width, int height, int nchannels, unsigned char* color);
float areaDifference(int s1,int  s2);

float areaDifference(int s1, int  s2)
{
  if (s1 > s2)
  {
    return s1 / s2;
  }
  else
  {
    return s2 / s1;
  }
}
std::vector<compPoint> generatePoint(cv::Mat image)
{
  cv::Mat grayscale, binary;
  cv::cvtColor(image, grayscale, cv::COLOR_BGR2GRAY);
  cv::threshold(grayscale, binary, 190, 255, 1);

  std::vector<std::vector<cv::Point> > contours;
  std::vector<cv::Vec4i> hierarchy;

  cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_NONE, cv::Point(0, 0));

  std::vector<compPoint> extractedPoint;
  cv::Mat drawing = cv::Mat::zeros(binary.size(), CV_8UC3);
  cv::Scalar color = cv::Scalar(255, 255, 255);
  maximumSize = cv::contourArea(contours[0]);
  int skip = 0;

  for (int i = 0; i < contours.size(); i++)
  {
    int jumpPoint = contours[i].size() / (contours[i].size() * 0.12);
    bool isInner = false;
    cv::Vec4i currentHierarchy = hierarchy[i];

    if (contours[i].size() <= 20)  //Remove small component 
      continue;

    for (int g = 0; g < contours[i].size(); g = g + jumpPoint) //Sample point from connected component
    {
      compPoint temp;
      temp.pointer = contours[i].at(g);
      line(drawing, contours[i].at(g), contours[i].at(g), color, 1, 8, 0);
      if (currentHierarchy.val[3] != -1)
      {
          int currentIndex = currentHierarchy.val[3];
          while (hierarchy[currentIndex].val[3] != -1)
          {
            currentIndex = hierarchy[currentIndex].val[3];
          }
          temp.siteNum = currentIndex;
          temp.size = cv::contourArea(contours[currentIndex]);
          isInner = true;

      }
      else
      {
        temp.siteNum = i;
        temp.size = cv::contourArea(contours[i]);

        if (cv::contourArea(contours[i])>maximumSize)
        {
          maximumSize = cv::contourArea(contours[i]);
        }

      }

      extractedPoint.push_back(temp);

    }

    if (isInner == false)
    {
      average = average + cv::contourArea(contours[i]);
      skip++;
    }

  }
  average = average/skip;
  return extractedPoint;

}
static inline jcv_point remap(const jcv_point* pt, const jcv_point* min, const jcv_point* max, const jcv_point* scale)
{
  jcv_point p;
  p.x = (pt->x - min->x) / (max->x - min->x) * scale->x;
  p.y = (pt->y - min->y) / (max->y - min->y) * scale->y;
  return p;
}
static void plot(int x, int y, unsigned char* image, int width, int height, int nchannels, unsigned char* color)
{
  if (x < 0 || y < 0 || x >(width - 1) || y >(height - 1))
    return;
  int index = y * width * nchannels + x * nchannels;
  for (int i = 0; i < nchannels; ++i)
  {
    image[index + i] = color[i];
  }
}
static void draw_line(int x0, int y0, int x1, int y1, unsigned char* image, int width, int height, int nchannels, unsigned char* color)
{
  int dx = abs(x1 - x0), sx = x0<x1 ? 1 : -1;
  int dy = -abs(y1 - y0), sy = y0<y1 ? 1 : -1;
  int err = dx + dy, e2; // error value e_xy

  for (;;)
  {  // loop
    plot(x0, y0, image, width, height, nchannels, color);
    if (x0 == x1 && y0 == y1) break;
    e2 = 2 * err;
    if (e2 >= dy) { err += dy; x0 += sx; } // e_xy+e_x > 0
    if (e2 <= dx) { err += dx; y0 += sy; } // e_xy+e_y < 0
  }
}
void generateVoronoi(std::vector<compPoint> points, int width, int height)
{
  int size = points.size();

  jcv_point* voronoiPoint = (jcv_point*)malloc(sizeof(jcv_point) * (size_t)size);

  for (int i = 0; i < size; i++)
  {
    voronoiPoint[i].x = (float)points[i].pointer.x;
    voronoiPoint[i].y = (float)points[i].pointer.y;
    voronoiPoint[i].site = points[i].siteNum;
    voronoiPoint[i].totalPoint = points[i].size;
  }

  jcv_rect* rect = 0;
  size_t imagesize = (size_t)(width*height * 3);
  unsigned char* image = (unsigned char*)malloc(imagesize);
  unsigned char* image2 = (unsigned char*)malloc(imagesize);

  memset(image, 0, imagesize);
  unsigned char color_pt[] = { 255, 255, 255 };
  unsigned char color_line[] = { 220, 220, 220 };

  jcv_diagram diagram;
  jcv_point dimensions;
  dimensions.x = (jcv_real)width;
  dimensions.y = (jcv_real)height;

  memset(&diagram, 0, sizeof(jcv_diagram));
  jcv_diagram_generate(size, (const jcv_point*)voronoiPoint, rect, &diagram);

  //Edge
  const jcv_edge* edge = jcv_diagram_get_edges(&diagram);
  std::vector<filtered_edge> filteredEdge;

  float min_x = 0.0, min_y = 0.0;

  while (edge) //Remove edge from the same connected component
  {
    jcv_point p0 = edge->pos[0]; 
    jcv_point p1 = edge->pos[1]; 

    if (edge->sites[0]->p.site != edge->sites[1]->p.site)
    {
      filteredEdge.push_back(jcv_save_edge(edge));
      min_x = min_x + abs(edge->sites[0]->p.x - edge->sites[1]->p.x);
      min_y = min_y + abs(edge->sites[0]->p.y - edge->sites[1]->p.y);
     }
    edge = edge->next;
  }

  min_x = min_x / filteredEdge.size();
  min_y = min_y / filteredEdge.size();
  std::vector<filtered_edge> selectedEdge;

  for (int i = 0; i < filteredEdge.size(); i++)
  {

    jcv_point p0 = remap(&filteredEdge.at(i).pos[0], &diagram.min, &diagram.max, &dimensions);
    jcv_point p1 = remap(&filteredEdge.at(i).pos[1], &diagram.min, &diagram.max, &dimensions);

    float site_x = abs(filteredEdge.at(i).sites[0]->p.x - filteredEdge.at(i).sites[1]->p.x);
    float site_y = abs(filteredEdge.at(i).sites[0]->p.y - filteredEdge.at(i).sites[1]->p.y);

    float x_difference = abs(filteredEdge.at(i).pos[0].x- filteredEdge.at(i).pos[1].x);
    float y_difference = abs(filteredEdge.at(i).pos[0].y - filteredEdge.at(i).pos[1].y);

    float areaDiff = areaDifference(filteredEdge.at(i).sites[0]->p.totalPoint, filteredEdge.at(i).sites[1]->p.totalPoint);

    if (p0.x - p1.x == 0 && p0.y - p1.y == 0.0) //Remove short edges
      continue;

    if (areaDiff > 20) //Keep edge between small(text) and big(image) component
    {

      float difference = abs(filteredEdge.at(i).sites[0]->p.totalPoint - filteredEdge.at(i).sites[1]->p.totalPoint);
      if (difference > average*4 )
      {
        unsigned char color_line2[] = { 0, 220, 220 };
        selectedEdge.push_back(filteredEdge.at(i));
        draw_line((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, image, width, height, 3, color_line2);
        continue;

      }

    }

    if (x_difference > y_difference) //Remove edge between close component
    {

      if (site_y > min_y*1.6)
      {
        unsigned char color_line2[] = { 220, 0, 220 };

        selectedEdge.push_back(filteredEdge.at(i));
        draw_line((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, image, width, height, 3, color_line2);
      }

    }
    else
    {
      if (site_x > min_x*2.5)
      {
        unsigned char color_line2[] = { 220, 220, 0 };

        selectedEdge.push_back(filteredEdge.at(i));
        draw_line((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, image, width, height, 3, color_line2);
      }
    }
  }

  jcv_diagram_free(&diagram);

  for (int i = 0; i < size; ++i)
  {
    jcv_point p = remap(&voronoiPoint[i], &diagram.min, &diagram.max, &dimensions);
    plot((int)p.x, (int)p.y, image, width, height, 3, color_pt);

  }

  free(voronoiPoint);
  cv::Mat segmentedImg = cv::Mat(height, width, CV_8UC3, image);
  cv::imshow("Testing", segmentedImg);
  cv::waitKey(0);

  free(image);

}


int main()
{
  cv::Mat image, skewCorrected;

  image = cv::imread("C:\\figure5.PNG");
  if (!image.data)
  {
    std::cout << "Error" << std::endl;
    system("PAUSE");

    return 0;
  }
  std::vector<compPoint> points = generatePoint(image);

  int width = image.size().width, height = image.size().height;

  generateVoronoi(points, width, height);

  cv::waitKey(0);

}

输入图片:

Original Image

最佳答案

我不明白你代码中的很多东西,所以我只是附加了一些行来做你想做的。

1 - 创建一个零垫来画线 (CV_8U)

Mat dst = cv::Mat(height, width, CV_8U, cvScalar(0.));

2 - 画线(使用你的点)

line( dst, Point((int)p0.x, (int)p0.y), Point((int)p1.x, (int)p1.y), Scalar( 255, 255, 255 ), 1, 8);

enter image description here

3 - 关闭线条之间的“孔”(CLOSE 形态学操作)

int morph_size = 20; // adjust this values to your image
Mat element = getStructuringElement( MORPH_RECT, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
// Apply the CLOSE morphology operation
morphologyEx( dst, closed, MORPH_CLOSE, element );

enter image description here

4 - 对蒙版进行洪水填充(=“绘制”分割区域)

// iterate through the points
for (int i = 0; i < closed.rows; i++ ) {
    for (int j = 0; j < closed.cols; j++) {
        // if point is not "painted" yet
        if (closed.at<uchar>(i, j) == 0) {
            // copy Mat before Flood fill
            Mat previous_closed = closed.clone();
            // Flood fill that seed point ("paint" that area)
            floodFill(closed, Point(j, i), 255);
            // Get mask with the "painted" area
            Mat mask = closed - previous_closed;

            /// Copy from segmentedImg using the mask
            Mat outputMat;
            segmentedImg.copyTo(outputMat, mask);
            cv::imshow("Closed lines", closed);
            imshow("Splitted Area", outputMat);
            waitKey(0);
            break;
        }
    }
}

区域 1:

enter image description here

区域 2:

enter image description here

区域 3:

enter image description here

...等等,对于 5 个区域,该循环基本上继续将“黑色区域”绘制为白色,并根据每次填充前后的差异创建垫子。

完整代码(您的代码 + 这几行):

#include <opencv2/core/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/opencv.hpp>

using namespace cv;

#define JC_VORONOI_IMPLEMENTATION
#include "jc_voronoi.h"


typedef struct compPoint
{
    cv::Point pointer;
    int siteNum, size ;
};

int maximumSize;
float average=0;

std::vector<compPoint> generatePoint(cv::Mat image);
void generateVoronoi(std::vector<cv::Point> points, int width, int height);
static inline jcv_point remap(const jcv_point* pt, const jcv_point* min, const jcv_point* max, const jcv_point* scale);
static void draw_line(int x0, int y0, int x1, int y1, unsigned char* image, int width, int height, int nchannels, unsigned char* color);
static void plot(int x, int y, unsigned char* image, int width, int height, int nchannels, unsigned char* color);
float areaDifference(int s1,int  s2);

float areaDifference(int s1, int  s2)
{
    if (s1 > s2)
    {
        return s1 / s2;
    }
    else
    {
        return s2 / s1;
    }
}
std::vector<compPoint> generatePoint(cv::Mat image)
{
    cv::Mat grayscale, binary;
    cv::cvtColor(image, grayscale, cv::COLOR_BGR2GRAY);
    cv::threshold(grayscale, binary, 190, 255, 1);

    std::vector<std::vector<cv::Point> > contours;
    std::vector<cv::Vec4i> hierarchy;

    cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_NONE, cv::Point(0, 0));

    std::vector<compPoint> extractedPoint;
    cv::Mat drawing = cv::Mat::zeros(binary.size(), CV_8UC3);
    cv::Scalar color = cv::Scalar(255, 255, 255);
    maximumSize = cv::contourArea(contours[0]);
    int skip = 0;

    for (int i = 0; i < contours.size(); i++)
    {
        int jumpPoint = contours[i].size() / (contours[i].size() * 0.12);
        bool isInner = false;
        cv::Vec4i currentHierarchy = hierarchy[i];

        if (contours[i].size() <= 20)  //Remove small component
            continue;

        for (int g = 0; g < contours[i].size(); g = g + jumpPoint) //Sample point from connected component
        {
            compPoint temp;
            temp.pointer = contours[i].at(g);
            line(drawing, contours[i].at(g), contours[i].at(g), color, 1, 8, 0);
            if (currentHierarchy.val[3] != -1)
            {
                int currentIndex = currentHierarchy.val[3];
                while (hierarchy[currentIndex].val[3] != -1)
                {
                    currentIndex = hierarchy[currentIndex].val[3];
                }
                temp.siteNum = currentIndex;
                temp.size = cv::contourArea(contours[currentIndex]);
                isInner = true;

            }
            else
            {
                temp.siteNum = i;
                temp.size = cv::contourArea(contours[i]);

                if (cv::contourArea(contours[i])>maximumSize)
                {
                    maximumSize = cv::contourArea(contours[i]);
                }

            }

            extractedPoint.push_back(temp);

        }

        if (isInner == false)
        {
            average = average + cv::contourArea(contours[i]);
            skip++;
        }

    }
    average = average/skip;
    return extractedPoint;

}
static inline jcv_point remap(const jcv_point* pt, const jcv_point* min, const jcv_point* max, const jcv_point* scale)
{
    jcv_point p;
    p.x = (pt->x - min->x) / (max->x - min->x) * scale->x;
    p.y = (pt->y - min->y) / (max->y - min->y) * scale->y;
    return p;
}
static void plot(int x, int y, unsigned char* image, int width, int height, int nchannels, unsigned char* color)
{
    if (x < 0 || y < 0 || x >(width - 1) || y >(height - 1))
        return;
    int index = y * width * nchannels + x * nchannels;
    for (int i = 0; i < nchannels; ++i)
    {
        image[index + i] = color[i];
    }
}
static void draw_line(int x0, int y0, int x1, int y1, unsigned char* image, int width, int height, int nchannels, unsigned char* color)
{
    int dx = abs(x1 - x0), sx = x0<x1 ? 1 : -1;
    int dy = -abs(y1 - y0), sy = y0<y1 ? 1 : -1;
    int err = dx + dy, e2; // error value e_xy

    for (;;)
    {  // loop
        plot(x0, y0, image, width, height, nchannels, color);
        if (x0 == x1 && y0 == y1) break;
        e2 = 2 * err;
        if (e2 >= dy) { err += dy; x0 += sx; } // e_xy+e_x > 0
        if (e2 <= dx) { err += dx; y0 += sy; } // e_xy+e_y < 0
    }
}
void generateVoronoi(std::vector<compPoint> points, int width, int height)
{
    /// 1 - Create Mat of zeros to draw the lines
    Mat dst = cv::Mat(height,width, CV_8U, cvScalar(0.));

    int size = points.size();

    jcv_point* voronoiPoint = (jcv_point*)malloc(sizeof(jcv_point) * (size_t)size);

    for (int i = 0; i < size; i++)
    {
        voronoiPoint[i].x = (float)points[i].pointer.x;
        voronoiPoint[i].y = (float)points[i].pointer.y;
        voronoiPoint[i].site = points[i].siteNum;
        voronoiPoint[i].totalPoint = points[i].size;
    }

    jcv_rect* rect = 0;
    size_t imagesize = (size_t)(width*height * 3);
    unsigned char* image = (unsigned char*)malloc(imagesize);

    memset(image, 0, imagesize);
    unsigned char color_pt[] = { 255, 255, 255 };

    jcv_diagram diagram;
    jcv_point dimensions;
    dimensions.x = (jcv_real)width;
    dimensions.y = (jcv_real)height;

    memset(&diagram, 0, sizeof(jcv_diagram));
    jcv_diagram_generate(size, (const jcv_point*)voronoiPoint, rect, &diagram);

    //Edge
    const jcv_edge* edge = jcv_diagram_get_edges(&diagram);
    std::vector<filtered_edge> filteredEdge;

    float min_x = 0.0, min_y = 0.0;

    while (edge) //Remove edge from the same connected component
    {
        jcv_point p0 = edge->pos[0];
        jcv_point p1 = edge->pos[1];

        if (edge->sites[0]->p.site != edge->sites[1]->p.site)
        {
            filteredEdge.push_back(jcv_save_edge(edge));
            min_x = min_x + abs(edge->sites[0]->p.x - edge->sites[1]->p.x);
            min_y = min_y + abs(edge->sites[0]->p.y - edge->sites[1]->p.y);
        }
        edge = edge->next;
    }

    min_x = min_x / filteredEdge.size();
    min_y = min_y / filteredEdge.size();
    std::vector<filtered_edge> selectedEdge;

    for (int i = 0; i < filteredEdge.size(); i++)
    {

        jcv_point p0 = remap(&filteredEdge.at(i).pos[0], &diagram.min, &diagram.max, &dimensions);
        jcv_point p1 = remap(&filteredEdge.at(i).pos[1], &diagram.min, &diagram.max, &dimensions);

        float site_x = abs(filteredEdge.at(i).sites[0]->p.x - filteredEdge.at(i).sites[1]->p.x);
        float site_y = abs(filteredEdge.at(i).sites[0]->p.y - filteredEdge.at(i).sites[1]->p.y);

        float x_difference = abs(filteredEdge.at(i).pos[0].x- filteredEdge.at(i).pos[1].x);
        float y_difference = abs(filteredEdge.at(i).pos[0].y - filteredEdge.at(i).pos[1].y);

        float areaDiff = areaDifference(filteredEdge.at(i).sites[0]->p.totalPoint, filteredEdge.at(i).sites[1]->p.totalPoint);

        if (p0.x - p1.x == 0 && p0.y - p1.y == 0.0) //Remove short edges
            continue;

        /// 2 - Draw lines
        if (areaDiff > 20) //Keep edge between small(text) and big(image) component
        {

            float difference = abs(filteredEdge.at(i).sites[0]->p.totalPoint - filteredEdge.at(i).sites[1]->p.totalPoint);
            if (difference > average*4 )
            {
                unsigned char color_line2[] = { 0, 220, 220 };
                selectedEdge.push_back(filteredEdge.at(i));
                draw_line((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, image, width, height, 3, color_line2);
                line( dst, Point((int)p0.x, (int)p0.y), Point((int)p1.x, (int)p1.y), Scalar( 255, 255, 255 ), 1, 8);
                continue;

            }

        }

        if (x_difference > y_difference) //Remove edge between close component
        {

            if (site_y > min_y*1.6)
            {
                unsigned char color_line2[] = { 220, 0, 220 };

                selectedEdge.push_back(filteredEdge.at(i));
                draw_line((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, image, width, height, 3, color_line2);
                line( dst, Point((int)p0.x, (int)p0.y), Point((int)p1.x, (int)p1.y), Scalar( 255, 255, 255 ), 1, 8);
            }

        }
        else
        {
            if (site_x > min_x*2.5)
            {
                unsigned char color_line2[] = { 220, 220, 0 };

                selectedEdge.push_back(filteredEdge.at(i));
                draw_line((int)p0.x, (int)p0.y, (int)p1.x, (int)p1.y, image, width, height, 3, color_line2);
                line( dst, Point((int)p0.x, (int)p0.y), Point((int)p1.x, (int)p1.y), Scalar( 255, 255, 255 ), 1, 8);
            }
        }
    }

    jcv_diagram_free(&diagram);

    for (int i = 0; i < size; ++i)
    {
        jcv_point p = remap(&voronoiPoint[i], &diagram.min, &diagram.max, &dimensions);
        plot((int)p.x, (int)p.y, image, width, height, 3, color_pt);

    }

    free(voronoiPoint);
    cv::Mat segmentedImg = cv::Mat(height, width, CV_8UC3, image);
    cv::imshow("Testing", segmentedImg);
    cv::imshow("Lines", dst);
    /// New code:
    Mat closed = dst.clone();
    /// 3 - Close the "holes" between the lines
    int morph_size = 20; // adjust this values to your image
    Mat element = getStructuringElement( MORPH_RECT, Size( 2*morph_size + 1, 2*morph_size+1 ), Point( morph_size, morph_size ) );
    // Apply the CLOSE morphology operation
    morphologyEx( dst, closed, MORPH_CLOSE, element );
    imshow("Closed lines", closed);
    waitKey(0);
    /// 4 - Flood fill to a mask
    // iterate through the points
    for (int i = 0; i < closed.rows; i++ ) {
        for (int j = 0; j < closed.cols; j++) {
            // if point is not "painted" yet
            if (closed.at<uchar>(i, j) == 0) {
                // copy Mat before Flood fill
                Mat previous_closed = closed.clone();
                // Flood fill that seed point ("paint" that area)
                floodFill(closed, Point(j, i), 255);
                // Get mask with the "painted" area
                Mat mask = closed - previous_closed;

                /// 5 - Copy from segmentedImg using the mask
                Mat outputMat;
                segmentedImg.copyTo(outputMat, mask);
                cv::imshow("Closed lines", closed);
                imshow("Splitted Area", outputMat);
                waitKey(0);
                break;
            }
        }
    }

    free(image);
}


int main()
{
    cv::Mat image, skewCorrected;

    image = cv::imread("/home/tribta/Downloads/HI2IT.png");
    if (!image.data)
    {
        std::cout << "Error" << std::endl;
        system("PAUSE");

        return 0;
    }
    std::vector<compPoint> points = generatePoint(image);

    int width = image.size().width, height = image.size().height;
    generateVoronoi(points, width, height);

    cv::waitKey(0);

}

关于c++ - 如何在 OpenCV 中基于线分割图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49153229/

相关文章:

c++ - 从 std::string 的开头和结尾删除数字

c++ - 如何获得两个 std::map 的公共(public)键?

javascript - 动态调整图像映射和图像的大小

html - CSS悬停在特定位置显示文本

c++ - 在 armv7l (Odroid XU4) 上编译时出现 OpenCV "conflicting declaration"问题

c++ - OpenCV WarpPerspective 问题

Python OpenCV 调整大小(插值)

c++ - C++ 中的 operator()() 是做什么的?

C++ vector::size_type:有符号与无符号;整数与长

iphone - 有人有 iOS 上 vImage 处理的示例代码吗?