【QT opencv】使用创建副本.clone()的场景

前言

很多简要的例子都是cv::Mat image = cv::imread(imagePath);之后,直接使用image来处理图像。单一的场景体现不出冲突性,容易让人忽略image是一种公共/共享的资源变量。当存在修改image的场景时就需要注意上下文是否存在冲突了。以下用一个例子进行讲解。

示例功能

对一张图片描绘出20x20的网格(白色)、查找轮廓发现的全部轮廓(绿色),以及最小外接(物体)矩形(红色)。这是一个比较简易的调试物体轮廓的功能,以下是效果图

通用模块说明

(1) 对一张图片描绘出20x20的网格(白色)

要点:

  • 计算网格步长:获取每个网格的宽和高

  • 画网格:通过循环画行和列的直线cv::line(),组成网格

    // 绘制网格函数并返回排除区域 - 添加绘制开关
    void drawGrid(Mat& image, int gridSize = 36, bool drawGridLines = true) {
    int rows = image.rows;
    int cols = image.cols;

    复制代码
      // 计算网格步长
      int gridWidth = cols / gridSize;
      int gridHeight = rows / gridSize;
      // 只在需要时绘制网格线
      if (drawGridLines) {
          // 使用较粗的线条,以便在压缩后仍可见
          int lineThickness = 3;
          // 绘制网格线
          for (int i = 0; i <= gridSize; i++) {
              // 垂直线
              int x = i * gridWidth;
              line(image, Point(x, 0), Point(x, rows), Scalar(255, 255, 255), lineThickness);
    
              // 水平线
              int y = i * gridHeight;
              line(image, Point(0, y), Point(cols, y), Scalar(255, 255, 255), lineThickness);
          }
      }

    }

(2)查找轮廓发现的全部轮廓(绿色)

比较典型的流程:灰色通道->高斯模糊去噪->边缘检测->形态学操作->操作轮廓

复制代码
void getallContours(const cv::Mat& inputImage,std::vector<std::vector<cv::Point>>& contours){
    cv::Mat gray, blurred,edges;
    cv::cvtColor(inputImage, gray, cv::COLOR_BGR2GRAY);
    GaussianBlur(gray, blurred, Size(9, 9), 3.0);
    Canny(blurred, edges, 50, 150);
    // 增强形态学操作,更好地连接边缘
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7));
    cv::morphologyEx(edges, edges, cv::MORPH_CLOSE, kernel);
    cv::dilate(edges, edges, kernel); // 添加膨胀操作
    // 查找轮廓
    cv::findContours(edges, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    std::cout << "find " << contours.size() << " Contours" << std::endl;
}

(3)最小外接(物体)矩形(红色)

要点:找最大轮廓->获取最小外接矩形->获取矩形坐标->绘制直线,顶点

复制代码
void makerectContours(const std::vector<std::vector<cv::Point>>& contours,cv::Mat& inputImage){
    //3.找到面积最大的轮廓
    auto maxContour = *std::max_element(contours.begin(), contours.end(),
        [](const std::vector<cv::Point>& a, const std::vector<cv::Point>& b) {
            return cv::contourArea(a) < cv::contourArea(b);
        });

    // 获取最小外接矩形
    cv::RotatedRect rotatedRect = cv::minAreaRect(maxContour);
    cv::Point2f vertices[4];
    rotatedRect.points(vertices);

    // 转换为整数点坐标
    std::vector<cv::Point> rectPoints;
    for (int i = 0; i < 4; i++) {
        rectPoints.push_back(cv::Point(
            static_cast<int>(vertices[i].x),
            static_cast<int>(vertices[i].y)
        ));
    }
    if (rectPoints.empty()) return ;
    // 用红色绘制矩形轮廓
    for (int i = 0; i < 4; i++) {
        std::cout << "top" << i+1 << ": (" << rectPoints[i].x << ", " << rectPoints[i].y << ")" << std::endl;
        cv::line(inputImage, rectPoints[i], rectPoints[(i+1)%4], cv::Scalar(0, 0, 255), 3);//画矩形线
        cv::circle(inputImage, rectPoints[i], 8, cv::Scalar(0, 255, 255), -1);//顶点画一个原点
    }
    // 计算并显示面积
    double area = cv::contourArea(rectPoints);
    std::cout << "area: " << area << std::endl;
}

测试模块说明

1、不使用clone()创建副本的样例

因为对图片描绘网格,是对图片资源变量的改变,3个模块中(1)画网格修改了图片资源变量,(2)和(3)只是坐标集合的处理,不涉及图片资源变量修改。所以,如果不使用clone()创建副本,就必须把模块(2)放在模块(1)之前,才能保证获取到的轮廓坐标集合是原始的,代码如下

复制代码
void testContour1(cv::Mat& inputImage) {
    //1. 查找所有轮廓并用绿色绘制
    std::vector<std::vector<cv::Point>> contours;
    getallContours(inputImage,contours);
    //2.在原始图像上绘制gridsizeXgridsize网格并获取排除区域
    int gridsize=20;
    bool drawGridLines = true;// 控制是否绘制网格线
    drawGrid(inputImage, gridsize, drawGridLines);
    // 用绿色绘制所有轮廓
    cv::drawContours(inputImage, contours, -1, cv::Scalar(0, 255, 0), 2);

    makerectContours(contours,inputImage);
}

2、使用clone()创建副本的样例

先获取一个副本cv::Mat getImagecontours = inputImage.clone();如果先画图再操作轮廓,也不影响返回的轮廓集合,代码如下

复制代码
void testContour2(cv::Mat& inputImage) {
    cv::Mat getImagecontours = inputImage.clone();
    //1.在原始图像上绘制gridsizeXgridsize网格并获取排除区域
    int gridsize=20;
    bool drawGridLines = true;// 控制是否绘制网格线
    drawGrid(inputImage, gridsize, drawGridLines);

    //2. 查找所有轮廓并用绿色绘制
    std::vector<std::vector<cv::Point>> contours;
    getallContours(getImagecontours,contours);

    // 用绿色绘制所有轮廓
    cv::drawContours(inputImage, contours, -1, cv::Scalar(0, 255, 0), 2);

    makerectContours(contours,inputImage);
}

如果以上代码不创建副本,使用与画网格同一个的图片资源变量时,此时返回的轮廓就变化了,如下图红色边框占满了整张图片

完整代码

复制代码
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <string>

using namespace cv;
using namespace std;

// 显示函数 - 保持20%压缩比例
void showimg(const string& name, const Mat& curImage) {
    Mat resizedImage;
    double scale = 0.2; // 固定压缩到20%
    resize(curImage, resizedImage, Size(), scale, scale);
    imshow(name, resizedImage);
}

// 绘制网格函数并返回排除区域 - 添加绘制开关
void drawGrid(Mat& image, int gridSize = 36, bool drawGridLines = true) {
    int rows = image.rows;
    int cols = image.cols;

    // 计算网格步长
    int gridWidth = cols / gridSize;
    int gridHeight = rows / gridSize;
    // 只在需要时绘制网格线
    if (drawGridLines) {
        // 使用较粗的线条,以便在压缩后仍可见
        int lineThickness = 3;
        // 绘制网格线
        for (int i = 0; i <= gridSize; i++) {
            // 垂直线
            int x = i * gridWidth;
            line(image, Point(x, 0), Point(x, rows), Scalar(255, 255, 255), lineThickness);

            // 水平线
            int y = i * gridHeight;
            line(image, Point(0, y), Point(cols, y), Scalar(255, 255, 255), lineThickness);
        }
    }
}

void getallContours(const cv::Mat& inputImage,std::vector<std::vector<cv::Point>>& contours){
    cv::Mat gray, blurred,edges;
    cv::cvtColor(inputImage, gray, cv::COLOR_BGR2GRAY);
    GaussianBlur(gray, blurred, Size(9, 9), 3.0);
    Canny(blurred, edges, 50, 150);
    // 增强形态学操作,更好地连接边缘
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7));
    cv::morphologyEx(edges, edges, cv::MORPH_CLOSE, kernel);
    cv::dilate(edges, edges, kernel); // 添加膨胀操作
    // 查找轮廓
    cv::findContours(edges, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    std::cout << "find " << contours.size() << " Contours" << std::endl;
}

void makerectContours(const std::vector<std::vector<cv::Point>>& contours,cv::Mat& inputImage){
    //3.找到面积最大的轮廓
    auto maxContour = *std::max_element(contours.begin(), contours.end(),
        [](const std::vector<cv::Point>& a, const std::vector<cv::Point>& b) {
            return cv::contourArea(a) < cv::contourArea(b);
        });

    // 获取最小外接矩形
    cv::RotatedRect rotatedRect = cv::minAreaRect(maxContour);
    cv::Point2f vertices[4];
    rotatedRect.points(vertices);

    // 转换为整数点坐标
    std::vector<cv::Point> rectPoints;
    for (int i = 0; i < 4; i++) {
        rectPoints.push_back(cv::Point(
            static_cast<int>(vertices[i].x),
            static_cast<int>(vertices[i].y)
        ));
    }
    if (rectPoints.empty()) return ;
    // 用红色绘制矩形轮廓
    for (int i = 0; i < 4; i++) {
        std::cout << "top" << i+1 << ": (" << rectPoints[i].x << ", " << rectPoints[i].y << ")" << std::endl;
        cv::line(inputImage, rectPoints[i], rectPoints[(i+1)%4], cv::Scalar(0, 0, 255), 3);//画矩形线
        cv::circle(inputImage, rectPoints[i], 8, cv::Scalar(0, 255, 255), -1);//顶点画一个原点
    }
    // 计算并显示面积
    double area = cv::contourArea(rectPoints);
    std::cout << "area: " << area << std::endl;
}

void testContour1(cv::Mat& inputImage) {
    //1. 查找所有轮廓并用绿色绘制
    std::vector<std::vector<cv::Point>> contours;
    getallContours(inputImage,contours);
    //2.在原始图像上绘制gridsizeXgridsize网格并获取排除区域
    int gridsize=20;
    bool drawGridLines = true;// 控制是否绘制网格线
    drawGrid(inputImage, gridsize, drawGridLines);
    // 用绿色绘制所有轮廓
    cv::drawContours(inputImage, contours, -1, cv::Scalar(0, 255, 0), 2);

    makerectContours(contours,inputImage);
}


void testContour2(cv::Mat& inputImage) {
    cv::Mat getImagecontours = inputImage.clone();
    //1.在原始图像上绘制gridsizeXgridsize网格并获取排除区域
    int gridsize=20;
    bool drawGridLines = true;// 控制是否绘制网格线
    drawGrid(inputImage, gridsize, drawGridLines);

    //2. 查找所有轮廓并用绿色绘制
    std::vector<std::vector<cv::Point>> contours;
    getallContours(inputImage,contours);

    // 用绿色绘制所有轮廓
    cv::drawContours(inputImage, contours, -1, cv::Scalar(0, 255, 0), 2);

    makerectContours(contours,inputImage);
}

int main(int argc, char* argv[]) {
    std::string imagePath = "test.png";
    // 读取图片
    cv::Mat image = cv::imread(imagePath);
    if (image.empty()) {
        std::cout << "no can open: " << imagePath << std::endl;
        return -1;
    }
    std::cout << "size: " << image.cols << "x" << image.rows << std::endl;
    // 创建副本用于显示
    cv::Mat result = image.clone();
    testContour2(result);
    // 显示结果
    showimg("result", result);
    cv::waitKey(0);
    return 0;
}

篇尾

同一个事件流程中遇到绘图需求场景时,需要关注是哪个步骤修改图片资源变量,通过.clone创建副本的代码健壮性会更好。另外多线程读取公共图片资源时,也应该使用.clone创建副本,避免影响其他线程读取不到原始图片。

相关推荐
还不秃顶的计科生4 小时前
如何快速用cmd知道某个文件夹下的子文件以及子文件夹的这个目录分支具体的分支结构
人工智能
九河云4 小时前
不同级别华为云代理商的增值服务内容与质量差异分析
大数据·服务器·人工智能·科技·华为云
Elastic 中国社区官方博客4 小时前
Elasticsearch:Microsoft Azure AI Foundry Agent Service 中用于提供可靠信息和编排的上下文引擎
大数据·人工智能·elasticsearch·microsoft·搜索引擎·全文检索·azure
大模型真好玩4 小时前
Gemini3.0深度解析,它在重新定义智能,会是前端工程师噩梦吗?
人工智能·agent·deepseek
机器之心4 小时前
AI终于学会「读懂人心」,带飞DeepSeek R1,OpenAI o3等模型
人工智能·openai
AAA修煤气灶刘哥4 小时前
从Coze、Dify到Y-Agent Studio:我的Agent开发体验大升级
人工智能·低代码·agent
陈佬昔没带相机5 小时前
MiniMax M2 + Trae 编码评测:能否与 Claude 4.5 扳手腕?
前端·人工智能·ai编程
美狐美颜SDK开放平台5 小时前
从0到1开发直播美颜SDK:算法架构、模型部署与跨端适配指南
人工智能·架构·美颜sdk·直播美颜sdk·第三方美颜sdk·美狐美颜sdk
小陈phd5 小时前
RAG从入门到精通(四)——结构化数据读取与导入
人工智能·langchain
玖日大大5 小时前
Trae:字节跳动 AI 原生 IDE 的技术革命与实战指南
ide·人工智能