Generating Jiugong grid image with C++ OpenCV

This article is 1959 words long and is expected to read for 5 minutes

preface

I've been working on Android in recent months. I didn't do the Demo of OpenCV. I just downloaded VS2022 two days ago. I just made a simple Demo of OpenCV image cut into nine squares with the help of the new VS2022.

Realization effect

The right side of the picture is the effect of dividing into 9 pictures. I've seen it< Fun algorithm -- OpenCV Huarong AI automatic problem solving >Old friends should know what I'm going to do. Yes, this is to make another jigsaw puzzle. The area number corresponding to the image is printed with PutText on each image on the right. Now it is for identification.

Realization idea

#

thinking

1

After loading the image, use Resize to scale the image to a square size

2

The image size of each intercepted area is calculated according to the starting position of the image

3

Store the intercepted area into the container of Vector and sort it randomly during the storage process

4

Generate a new canvas and traverse the container to display each image

Core code explanation

Wechat Zhixiang

01

About segmented image containers

At first, I wanted to use map. Later, I felt it was not very good, so I created a structure, that is, the original sequence number position of the segmented image, the image Mat, and the current position.

The generated segmented image container is implemented with a SplitMats function.

std::vector<CutMat*> MatSet::SplitMats(cv::Mat& img, int cols, int rows)
{

  std::vector<CutMat*> matvts;
  if (cols == 0 || rows == 0)
  {
    std::cout << "The number of rows and columns cannot be 0" << std::endl;
    return matvts;
  }
  matvts.resize(cols * rows);

  //Calculate the width and height of the average number of cells
  int width = img.cols / cols;
  int height = img.rows / rows;

  //Generate sequence number list
  std::vector<int> nums = GetVtsPos(cols, rows);

  //Separate the rectangle according to the rows and columns entered
  for (int row = 0; row < rows; row++) {
    for (int col = 0; col < cols; col++) {
      //Calculates the starting X and Y coordinates of the current rectangle
      int x = col * width;
      if (x > 0) x++;

      int y = row * height;
      if (y > 0) y++;

      //Calculate the width and height of the intercepted rectangle, and add control that it cannot exceed the boundary of the source image
      int rwidth = width;
      if (x + rwidth > img.cols) rwidth = img.cols - x;
      int rheight = height;
      if (y + rheight > img.rows) rheight = img.rows - y;

      //Generate the intercepted rectangle and store the intercepted image in the map
      cv::Rect rect = cv::Rect(x, y, rwidth, rheight);
      cv::Mat matrect = img(rect);

      //The intercepted image needs to judge whether the width and height are consistent. If they are inconsistent, they will be scaled to the same size for display in one image
      if (rwidth != width || rheight != height) {
        cv::resize(matrect, matrect, cv::Size(width, height));
      }

      //Sequence number of the current Mat
      int pos = row * rows + col + 1;
      CutMat* tmpcurmat = new CutMat(pos, matrect);
      //Randomly assigned new location
      tmpcurmat->curPosition = GetRandNum(nums);

      //Insert into the container according to the randomly sorted position
      matvts[tmpcurmat->curPosition - 1] = tmpcurmat;
    }
  }

  return matvts;
}

In the process of loading, it should be noted that after the average segmentation according to the image size, the final rectangular length should judge whether it exceeds the image edge. If it exceeds, the length should be set to the length to the image edge, and then the same size of the image can be set through Resize.

02

Solution to image disorder

In the structure defined above, the curPosition attribute is assigned by generating a random position. Considering that the display should be displayed in order according to the curPosition attribute, the traditional method is two ideas:

  1. Use Map storage. The Key is curPosition. When traversing, find the Key to find the corresponding Map. The time complexity is O1. Use Map space for time.
  2. The returned containers are sorted according to curPosition again, and the time complexity is On.

Because we only have 9 containers, the above two basic speeds can be ignored. However, even if the random number has been assigned in the generation process, it is also possible to directly specify the storage location at that time. There is no need to use the above two schemes at all.

Directly set the number of containers by passing in row and column numbers.

Directly modify the subscript value of the container according to the generated specified location.

In the whole project, a new class of MatSet is created. Drawing and generating images are implemented here. main.cpp is to load images and external calls.

Complete code

MatSet.h

#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>

struct CutMat {
public:
  //Where images should be stored
  int Position;
  //Current location of image
  int curPosition;
  //image data
  cv::Mat mat;

  CutMat(int _pos, cv::Mat _mat) : Position(_pos), curPosition(_pos), mat(_mat)
  {
  }
};

class MatSet
{
public:
  //Generate a segmented image container
  static std::vector<CutMat*> SplitMats(cv::Mat& img, int cols = 3, int rows = 3);

  //Displays a jigsaw puzzle image
  static void DrawPuzzleMat(std::vector<CutMat*>& cutmats, std::vector<std::vector<cv::Point>>& contours, bool iscontours = false, int cols = 3);


private:
  //Generate corresponding position sequence number containers according to rows and columns
  static std::vector<int> GetVtsPos(int cols = 3, int rows = 3);
  //Get the current random sequence number
  static int GetRandNum(std::vector<int>& nums);
  //Insert profile
  static void InsertContours(std::vector<std::vector<cv::Point>>& contours, cv::Rect rect);
};

MatSet.cpp

#include "MatSet.h"

std::vector<CutMat*> MatSet::SplitMats(cv::Mat& img, int cols, int rows)
{

  std::vector<CutMat*> matvts;
  if (cols == 0 || rows == 0)
  {
    std::cout << "The number of rows and columns cannot be 0" << std::endl;
    return matvts;
  }
  //Set the number of containers directly according to rows and columns
  matvts.resize(cols * rows);

  //Calculate the width and height of the average number of cells
  int width = img.cols / cols;
  int height = img.rows / rows;

  //Generate sequence number list
  std::vector<int> nums = GetVtsPos(cols, rows);

  //Separate the rectangle according to the rows and columns entered
  for (int row = 0; row < rows; row++) {
    for (int col = 0; col < cols; col++) {
      //Calculates the starting X and Y coordinates of the current rectangle
      int x = col * width;
      if (x > 0) x++;

      int y = row * height;
      if (y > 0) y++;

      //Calculate the width and height of the intercepted rectangle, and add control that it cannot exceed the boundary of the source image
      int rwidth = width;
      if (x + rwidth > img.cols) rwidth = img.cols - x;
      int rheight = height;
      if (y + rheight > img.rows) rheight = img.rows - y;

      //Generate the intercepted rectangle and store the intercepted image in the map
      cv::Rect rect = cv::Rect(x, y, rwidth, rheight);
      cv::Mat matrect = img(rect);

      //The intercepted image needs to judge whether the width and height are consistent. If they are inconsistent, they will be scaled to the same size for display in one image
      if (rwidth != width || rheight != height) {
        cv::resize(matrect, matrect, cv::Size(width, height));
      }

      //Sequence number of the current Mat
      int pos = row * rows + col + 1;
      CutMat* tmpcurmat = new CutMat(pos, matrect);
      //Randomly assigned new location
      tmpcurmat->curPosition = GetRandNum(nums);

      //Insert into the container according to the randomly sorted position
      matvts[tmpcurmat->curPosition - 1] = tmpcurmat;
    }
  }

  return matvts;
}

void MatSet::DrawPuzzleMat(std::vector<CutMat*>& cutmats, std::vector<std::vector<cv::Point>>& contours, 
  bool iscontours, int cols)
{
  if (cutmats.empty()) return;
  int starttop = 20;
  int startleft = 20;
  int rectwidth = cutmats[0]->mat.cols;
  int rectheight = cutmats[0]->mat.rows;

  int nums = cutmats.size();

  //Create image
  cv::Mat src = cv::Mat(cv::Size(600, 700), CV_8UC3, cv::Scalar(240, 240, 240));

  for (int i = 0; i < nums; ++i) {
    //Calculate the rows and columns of the two-dimensional array in the current order
    int row = i / cols;
    int col = i % cols;

    //Generate corresponding rectangular box
    cv::Rect rect = cv::Rect(startleft + col * rectwidth, starttop + row * rectheight, rectwidth, rectheight);
    //Replace the image at the corresponding position
    cutmats[i]->mat.copyTo(src(rect));
      //Contour insertion
    if (iscontours) InsertContours(contours, rect);
  }

  imshow("puzzle", src);
}

//Generate sequence number container
std::vector<int> MatSet::GetVtsPos(int cols, int rows)
{
  std::vector<int> nums;
  int total = cols * rows;
  if (total > 0) {
    for (int i = 1; i <= total; ++i) {
      nums.push_back(i);
    }
  }
  return nums;
}

int MatSet::GetRandNum(std::vector<int>& nums)
{
  //Initialize random number seed
  srand((int)time(0));
  //The last assignment in the lower right corner is incorrect, so the remainder is not counted
  int index = nums.size() == 1 ? 0 : rand() % (nums.size() - 1);
  //Get return value
  int resint = nums[index];
  //Delete the assigned number from the container
  nums.erase(nums.begin() + index);
  return resint;
}

void MatSet::InsertContours(std::vector<std::vector<cv::Point>>& contours, cv::Rect rect)
{
  std::vector<cv::Point> vetpt;
  cv::Point pt1 = cv::Point(rect.x, rect.y);
  vetpt.push_back(pt1);
  cv::Point pt2 = cv::Point(rect.x + rect.width, rect.y);
  vetpt.push_back(pt2);
  cv::Point pt3 = cv::Point(rect.x + rect.width, rect.y + rect.height);
  vetpt.push_back(pt3);
  cv::Point pt4 = cv::Point(rect.x, rect.y + rect.height);
  vetpt.push_back(pt4);

  contours.push_back(vetpt);
}

main.cpp

#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>
#include "MatSet.h"

using namespace std;
using namespace cv;

//Define contour area
vector<vector<Point>> contours;

int main(int argc, char** argv) {

  try
  {
    Mat src = imread("E:/DCIM/test8.jpg");

    if (src.empty()) {
      cout << "Image loading failed...." << endl;
      waitKey(0);
      return -1;
    }

    //Set image zoom to 500 * 500
    Mat tmpsrc;
    resize(src, tmpsrc, Size(500, 500));

    imshow("src", src);
    imshow("tmpsrc", tmpsrc);

    //Get the set after image segmentation
    vector<CutMat*> vtsmat = MatSet::SplitMats(tmpsrc);
    //List the serial numbers corresponding to the set after image segmentation
    for (int i = 0; i < vtsmat.size(); ++i) {
      Mat tmpmat = vtsmat[i]->mat;
      string title = to_string(vtsmat[i]->Position);
      putText(tmpmat, title, Point(tmpmat.cols/2, tmpmat.rows/2), 2, 2, Scalar(0, 0, 255));
    }

    //Draw image
    MatSet::DrawPuzzleMat(vtsmat, contours, true);

    cv::waitKey(0);
    return 0;
  }
  catch (const std::exception& ex)
  {
    cout << ex.what() << endl;
    cv::waitKey(0);
    return -1;
  }
}

The whole Demo is made with VS2022 and OpenCV4.5.4. In C + + with VS2022, the smart prompt feels like VS2019, which is not as powerful as that in c# I said in the previous article. Another problem is that after using OpenCV4.5.4, there are more loading error outputs on the console during operation. Although it does not affect the operation, it looks uncomfortable. The figure is as follows:

If you have a little partner who knows how to solve the problem, please leave a message. Thank you very much.

finish

Posted on Wed, 01 Dec 2021 17:40:22 -0500 by poison6feet