C++ OpenCV生成九宫格图像

2021-12-01 20:18:00 浏览数 (1)

本文长度为1959,预计阅读5分钟

前言

这几个月一直在做Android的东西,OpenCV的Demo基本没做,正好前两天也刚下载了VS2022,正好借助新的VS2022做个简单的OpenCV图像切割成九宫格的Demo。

实现效果

看上图的最右边的,就是切分成9个图的效果,看过我的《趣玩算法--OpenCV华容道AI自动解题》老朋友应该都知道我要干什么了。没错,做这个嘛是为了再做一个拼图的小游戏,右边每个图像上都用PutText打印出了图像对应的区域数字,现在是为了标识作用的。

实现思路

#

思路

1

加载图像后用Resize将图像缩放成正方形大小

2

按图像起始位置开始,计算每个截取区域的图像大小

3

将截取的区域存入到Vector的容器中,存放的过程中随机排序

4

生成一个新的画布,遍历容器将每个图像显示出来

核心代码讲解

微卡智享

01

关于分割的图像容器

最开始想使用map的方式,后来觉得不太好,就创建了一个结构,就是分割后的图像原来的序号位置,图像Mat,还有一个是现在的位置三个属性。

而生成分割后的图像容器用了一个SplitMats的函数来实现。

代码语言:javascript复制
std::vector<CutMat*> MatSet::SplitMats(cv::Mat& img, int cols, int rows)
{

  std::vector<CutMat*> matvts;
  if (cols == 0 || rows == 0)
  {
    std::cout << "行数和列数不能为0" << std::endl;
    return matvts;
  }
  matvts.resize(cols * rows);

  //计算平均分的格数的width和height
  int width = img.cols / cols;
  int height = img.rows / rows;

  //生成序号列表
  std::vector<int> nums = GetVtsPos(cols, rows);

  //根据输入的行和列划分开矩形
  for (int row = 0; row < rows; row  ) {
    for (int col = 0; col < cols; col  ) {
      //计算当前矩形的起始X和Y坐标
      int x = col * width;
      if (x > 0) x  ;

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

      //计算截取矩形的宽和高,加入控制不能超过源图像的边界
      int rwidth = width;
      if (x   rwidth > img.cols) rwidth = img.cols - x;
      int rheight = height;
      if (y   rheight > img.rows) rheight = img.rows - y;

      //生成截取的矩形并截取图像存放到map中
      cv::Rect rect = cv::Rect(x, y, rwidth, rheight);
      cv::Mat matrect = img(rect);

      //截取后的图像需要判断是否宽高一致,不一致时缩放为一样大,用于在一张图像显示
      if (rwidth != width || rheight != height) {
        cv::resize(matrect, matrect, cv::Size(width, height));
      }

      //当前Mat的序号
      int pos = row * rows   col   1;
      CutMat* tmpcurmat = new CutMat(pos, matrect);
      //随机指定的新位置
      tmpcurmat->curPosition = GetRandNum(nums);

      //根据随机排序后的位置插入到容器中
      matvts[tmpcurmat->curPosition - 1] = tmpcurmat;
    }
  }

  return matvts;
}

载取的过程中要注意的是,根据图像大小平均分割后,最后的矩形长度要判断是否超出图像边缘了,如果超出后,需要长度设为到图像边缘的长度,然后再通过Resize来实现设置图像相同大小。

02

关于图像打乱顺序的解决

前面定义的结构里面,通过生成随机位置赋值给了curPosition属性,考虑到显示出来要按照curPosition属性顺序显示,传统的方法就是两个思路:

  1. 使用Map存储,Key为curPosition,遍历时查找Key找到对应的Map,时间复杂度为O1,用Map空间换时间。
  2. 返回的容器,重新按照curPosition排序,时间复杂度On。

因为我们容器只有9个,所以用上面两个基本的速度也可以忽略,不过即然在生成的过程中已经赋值随机数了,所以当时也直接指定存放位置也可以,完全不需要用上面两种方案。

通过传入的行和列数字,直接设置容器的个数。

根据生成的指定位置,直接修改容器的下标值。

整个项目中新建了一个MatSet的类,绘制和生成图像都在这里实现的,main.cpp就是加载图像和外部调用。

完整代码

MatSet.h

代码语言:javascript复制
#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>

struct CutMat {
public:
  //图像应存放位置
  int Position;
  //图像当前所在位置
  int curPosition;
  //图像数据
  cv::Mat mat;

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

class MatSet
{
public:
  //生成分割后的图像容器
  static std::vector<CutMat*> SplitMats(cv::Mat& img, int cols = 3, int rows = 3);

  //显示拼图游戏图像
  static void DrawPuzzleMat(std::vector<CutMat*>& cutmats, std::vector<std::vector<cv::Point>>& contours, bool iscontours = false, int cols = 3);


private:
  //根据行和列生成对应的位置序号容器
  static std::vector<int> GetVtsPos(int cols = 3, int rows = 3);
  //获取当前随机序号
  static int GetRandNum(std::vector<int>& nums);
  //插入轮廓
  static void InsertContours(std::vector<std::vector<cv::Point>>& contours, cv::Rect rect);
};

MatSet.cpp

代码语言:javascript复制
#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 << "行数和列数不能为0" << std::endl;
    return matvts;
  }
  //根据行和列直接设置容器的个数
  matvts.resize(cols * rows);

  //计算平均分的格数的width和height
  int width = img.cols / cols;
  int height = img.rows / rows;

  //生成序号列表
  std::vector<int> nums = GetVtsPos(cols, rows);

  //根据输入的行和列划分开矩形
  for (int row = 0; row < rows; row  ) {
    for (int col = 0; col < cols; col  ) {
      //计算当前矩形的起始X和Y坐标
      int x = col * width;
      if (x > 0) x  ;

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

      //计算截取矩形的宽和高,加入控制不能超过源图像的边界
      int rwidth = width;
      if (x   rwidth > img.cols) rwidth = img.cols - x;
      int rheight = height;
      if (y   rheight > img.rows) rheight = img.rows - y;

      //生成截取的矩形并截取图像存放到map中
      cv::Rect rect = cv::Rect(x, y, rwidth, rheight);
      cv::Mat matrect = img(rect);

      //截取后的图像需要判断是否宽高一致,不一致时缩放为一样大,用于在一张图像显示
      if (rwidth != width || rheight != height) {
        cv::resize(matrect, matrect, cv::Size(width, height));
      }

      //当前Mat的序号
      int pos = row * rows   col   1;
      CutMat* tmpcurmat = new CutMat(pos, matrect);
      //随机指定的新位置
      tmpcurmat->curPosition = GetRandNum(nums);

      //根据随机排序后的位置插入到容器中
      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();

  //创建图像
  cv::Mat src = cv::Mat(cv::Size(600, 700), CV_8UC3, cv::Scalar(240, 240, 240));

  for (int i = 0; i < nums;   i) {
    //计算当前顺序为二维数组的几行几列
    int row = i / cols;
    int col = i % cols;

    //生成对应的矩形框
    cv::Rect rect = cv::Rect(startleft   col * rectwidth, starttop   row * rectheight, rectwidth, rectheight);
    //替换对应位置的图像
    cutmats[i]->mat.copyTo(src(rect));
      //轮廓插入
    if (iscontours) InsertContours(contours, rect);
  }

  imshow("puzzle", src);
}

//生成序号容器
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)
{
  //初始化随机数种子
  srand((int)time(0));
  //右下角的最后一个赋值不对,所以取余数时不计算在内
  int index = nums.size() == 1 ? 0 : rand() % (nums.size() - 1);
  //获取到返回值
  int resint = nums[index];
  //容器中删除已经赋值的数字
  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

代码语言:javascript复制
#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>
#include "MatSet.h"

using namespace std;
using namespace cv;

//定义轮廓区域
vector<vector<Point>> contours;

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

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

    if (src.empty()) {
      cout << "图像加载失败。。。。" << endl;
      waitKey(0);
      return -1;
    }

    //设置图像缩放到500*500
    Mat tmpsrc;
    resize(src, tmpsrc, Size(500, 500));

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

    //获取图像分割后的集合
    vector<CutMat*> vtsmat = MatSet::SplitMats(tmpsrc);
    //将图像分割后的集合对应的序号列出来
    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));
    }

    //绘制图像
    MatSet::DrawPuzzleMat(vtsmat, contours, true);

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

整个Demo用的VS2022和OpenCV4.5.4做的,用VS2022的C 里,智能提示感觉和VS2019差不多,并不像我上篇说的和C#中一样强大。另一个问题就是用了OpenCV4.5.4后,运行过程中控制台多了一些加载错误的输出,虽然并不影响运行,不过看着不舒服。图如下:

如果有知道怎么解决的小伙伴麻烦留言告之一下,万分谢谢。

0 人点赞