【QT opencv】手动去噪--网格化获取区域坐标

前言

上一篇【QT opencv】使用创建副本.clone()的场景展示了如何给图片网格化。本篇讲解的是在固定背景环境下(比如高拍仪的黑色底板),查找轮廓之后,把轮廓和网格全部显示出来,通过指定区域把固定背景中的噪点轮廓去除的方法。

效果图

手动区域去噪之前

手动区域去噪之后

由上图可看到受左侧光线的影响,左侧第一列和底部一行有明显的边缘轮廓,另外第五行第四列、第五列有一个"┏━",去掉之后的效果如下

注:不去掉正中间绿色的轮廓的原因是这个轮廓占了7格区域,容易导致错误

功能讲解

1、获取边缘4条边缘行/列区域

上一篇【QT opencv】使用创建副本.clone()的场景 的划网格函数是void类型,因为4条边缘行/列区域都是需要去噪的区域,所以以下改造下划网格的函数,并返回改造的区域,代码如下:

复制代码
// 绘制网格函数并返回排除区域 - 添加绘制开关
vector<cv::Rect> 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;

    // 定义排除区域向量
    vector<cv::Rect> exclusionAreas;

    // 添加四个边缘区域
    exclusionAreas.push_back(cv::Rect(0, 0, cols, gridHeight)); // 第一行区域
    exclusionAreas.push_back(cv::Rect(0, (gridSize-1)*gridHeight, cols,
                                     rows - (gridSize-1)*gridHeight)); // 最后一行区域
    exclusionAreas.push_back(cv::Rect(0, 0, gridWidth, rows)); // 第一列区域
    exclusionAreas.push_back(cv::Rect((gridSize-1)*gridWidth, 0,
                                     cols - (gridSize-1)*gridWidth, rows)); // 最后一列区域

    // 只在需要时绘制网格线
    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);
        }
    }

    // 打印关键网格坐标(基于原始图像尺寸)
    for (size_t i = 0; i < exclusionAreas.size(); i++) {
        const auto& area = exclusionAreas[i];
        string areaName;
        switch (i) {
            case 0: areaName = "topRow"; break;
            case 1: areaName = "bottomRow"; break;
            case 2: areaName = "leftCol"; break;
            case 3: areaName = "rightCol"; break;
        }
        cout << areaName << " area: leftuppos(" << area.x << "," << area.y
             << "), rightdownpos(" << area.x + area.width << ","
             << area.y + area.height << ")" << endl;
    }

    return exclusionAreas;
}

此时的检测函数如下:

复制代码
void detectContour(cv::Mat& inputImage) {
    cv::Mat getImagecontours = inputImage.clone();
    //1.在原始图像上绘制gridsizexgridsize网格并获取排除区域
    int gridsize = 20;
    // 控制是否绘制网格线--用于调试
    bool drawGridLines = true;
    vector<cv::Rect> exclusionAreas;
    //drawGrid的行和列的索引
    {//四条边界
        exclusionAreas=drawGrid(inputImage, gridsize, drawGridLines);
    }
}

2、获取第五行第四列、第五列区域

增加第五行第四列、第五列去掉"┏━",需要增加一个通过行/列位置提取区域的函数,代码如下:

复制代码
// 获取指定网格区域的函数(使用从1开始的索引)
cv::Rect getGridCell(const cv::Mat& image, int gridSize, int row, int col) {
    int rows = image.rows;
    int cols = image.cols;
    if(row<1)row=1;
    if(col<1)col=1;
    // 将1-based索引转换为0-based索引
    int rowIndex = row - 1;
    int colIndex = col - 1;

    // 确保转换后的索引在有效范围内
    rowIndex = max(0, min(rowIndex, gridSize - 1));
    colIndex = max(0, min(colIndex, gridSize - 1));

    // 计算网格步长
    int gridWidth = cols / gridSize;
    int gridHeight = rows / gridSize;

    // 计算网格位置和尺寸
    int x = colIndex * gridWidth;
    int y = rowIndex * gridHeight;
    int width = gridWidth;
    int height = gridHeight;

    // 处理边界情况,确保不超出图像范围
    if (colIndex == gridSize - 1) width = cols - x;
    if (rowIndex == gridSize - 1) height = rows - y;

//    cout << "Grid cell (row=" << row << ", col=" << col << "): "
//         << "(" << x << "," << y << "),(" << x+width << "," << y+height << ")" << endl;

    return cv::Rect(x, y, width, height);
}

void detectContour(cv::Mat& inputImage) {
    cv::Mat getImagecontours = inputImage.clone();
    //1.在原始图像上绘制gridsizexgridsize网格并获取排除区域
    int gridsize = 20;
    // 控制是否绘制网格线--用于调试
    bool drawGridLines = true;
    vector<cv::Rect> exclusionAreas;
    //drawGrid的行和列的索引
    {//四条边界
        exclusionAreas=drawGrid(inputImage, gridsize, drawGridLines);
    }
    {//第5行的第4和第5列网格需要排除
        cv::Rect pos;
        pos=getGridCell(inputImage,gridsize,5,4);
        exclusionAreas.push_back(pos);
        pos=getGridCell(inputImage,gridsize,5,5);
        exclusionAreas.push_back(pos);
    }
}

3、获取图片的所有轮廓

这里做了简要的封装,代码如下

复制代码
void getallContours(const cv::Mat& inputImage, vector<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);
    cout << "find " << contours.size() << " Contours" << endl;
}

void detectContour(cv::Mat& inputImage) {
    cv::Mat getImagecontours = inputImage.clone();
    //1.在原始图像上绘制gridsizexgridsize网格并获取排除区域
    int gridsize = 20;
    // 控制是否绘制网格线--用于调试
    bool drawGridLines = true;
    vector<cv::Rect> exclusionAreas;
    //drawGrid的行和列的索引
    {//四条边界
        exclusionAreas=drawGrid(inputImage, gridsize, drawGridLines);
    }
    {//2.第5行的第4和第5列网格需要排除
        cv::Rect pos;
        pos=getGridCell(inputImage,gridsize,5,4);
        exclusionAreas.push_back(pos);
        pos=getGridCell(inputImage,gridsize,5,5);
        exclusionAreas.push_back(pos);
    }

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

4、过滤待排除轮廓

通过遍历contours,提取坐标点,判断在排除区域中的占比是否超过指定的阈值(0.75),只返回不超过阈值的轮廓,代码如下:

复制代码
//检查轮廓是否在排除区域内
bool isContourInExclusionArea(const vector<Point>& contour,
                             const vector<cv::Rect>& exclusionAreas,
                             double threshold = 0.75) {
    int pointsInArea = 0;
    int totalPoints = contour.size();

    // 手动检查每个点是否在排除区域内
    for (const auto& point : contour) {
        bool inArea = false;

        // 手动检查点是否在任何一个排除区域内
        for (size_t i = 0; i < exclusionAreas.size(); i++) {
            const auto& area = exclusionAreas[i];

            // 手动实现点是否在矩形内的检查
            if (point.x >= area.x && point.x < area.x + area.width &&
                point.y >= area.y && point.y < area.y + area.height) {
                pointsInArea++;
                inArea = true;
                break;
            }
        }

    }

    double ratio = (double)pointsInArea / totalPoints;

    // 如果超过阈值比例的点在排除区域内,则返回true
    return ratio > threshold;
}


int filterContours(const vector<vector<cv::Point>>& contours,
                   vector<vector<cv::Point>>& filteredContours,
                   const vector<cv::Rect>& exclusionAreas) {
    int excludedCount = 0;

    for (const auto& contour : contours) {
        if (isContourInExclusionArea(contour, exclusionAreas, 0.75)) {
            excludedCount++;
            continue; // 跳过这个轮廓
        }
        filteredContours.push_back(contour);
    }

    cout << "filter " << excludedCount << " edge Contours" << endl;
    return excludedCount;
}

void detectContour(cv::Mat& inputImage) {
    cv::Mat getImagecontours = inputImage.clone();
    //1.在原始图像上绘制gridsizexgridsize网格并获取排除区域
    int gridsize = 20;
    // 控制是否绘制网格线--用于调试
    bool drawGridLines = true;
    vector<cv::Rect> exclusionAreas;
    //drawGrid的行和列的索引
    {//四条边界
        exclusionAreas=drawGrid(inputImage, gridsize, drawGridLines);
    }
    {//2.第5行的第4和第5列网格需要排除
        cv::Rect pos;
        pos=getGridCell(inputImage,gridsize,5,4);
        exclusionAreas.push_back(pos);
        pos=getGridCell(inputImage,gridsize,5,5);
        exclusionAreas.push_back(pos);
    }

    //3. 查找所有轮廓并用绿色绘制
    vector<vector<cv::Point>> contours;
    getallContours(getImagecontours, contours);
    //filterContours(contours, contours, exclusionAreas);//未过滤之前的所有轮廓

    //4.过滤待排除轮廓
    vector<vector<cv::Point>> filteredContours;
    filterContours(contours, filteredContours, exclusionAreas);//过滤之后的轮廓
    cout << "filteredContours " << filteredContours.size() << " Contours" << endl;
}

全部代码

复制代码
#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);
}

// 获取指定网格区域的函数(使用从1开始的索引)
cv::Rect getGridCell(const cv::Mat& image, int gridSize, int row, int col) {
    int rows = image.rows;
    int cols = image.cols;
    if(row<1)row=1;
    if(col<1)col=1;
    // 将1-based索引转换为0-based索引
    int rowIndex = row - 1;
    int colIndex = col - 1;

    // 确保转换后的索引在有效范围内
    rowIndex = max(0, min(rowIndex, gridSize - 1));
    colIndex = max(0, min(colIndex, gridSize - 1));

    // 计算网格步长
    int gridWidth = cols / gridSize;
    int gridHeight = rows / gridSize;

    // 计算网格位置和尺寸
    int x = colIndex * gridWidth;
    int y = rowIndex * gridHeight;
    int width = gridWidth;
    int height = gridHeight;

    // 处理边界情况,确保不超出图像范围
    if (colIndex == gridSize - 1) width = cols - x;
    if (rowIndex == gridSize - 1) height = rows - y;

//    cout << "Grid cell (row=" << row << ", col=" << col << "): "
//         << "(" << x << "," << y << "),(" << x+width << "," << y+height << ")" << endl;

    return cv::Rect(x, y, width, height);
}

// 绘制网格函数并返回排除区域 - 添加绘制开关
vector<cv::Rect> 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;

    // 定义排除区域向量
    vector<cv::Rect> exclusionAreas;

    // 添加四个边缘区域
    exclusionAreas.push_back(cv::Rect(0, 0, cols, gridHeight)); // 第一行区域
    exclusionAreas.push_back(cv::Rect(0, (gridSize-1)*gridHeight, cols,
                                     rows - (gridSize-1)*gridHeight)); // 最后一行区域
    exclusionAreas.push_back(cv::Rect(0, 0, gridWidth, rows)); // 第一列区域
    exclusionAreas.push_back(cv::Rect((gridSize-1)*gridWidth, 0,
                                     cols - (gridSize-1)*gridWidth, rows)); // 最后一列区域

    // 只在需要时绘制网格线
    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);
        }
    }

    // 打印关键网格坐标(基于原始图像尺寸)
    for (size_t i = 0; i < exclusionAreas.size(); i++) {
        const auto& area = exclusionAreas[i];
        string areaName;
        switch (i) {
            case 0: areaName = "topRow"; break;
            case 1: areaName = "bottomRow"; break;
            case 2: areaName = "leftCol"; break;
            case 3: areaName = "rightCol"; break;
        }
//        cout << areaName << " area: leftuppos(" << area.x << "," << area.y
//             << "), rightdownpos(" << area.x + area.width << ","
//             << area.y + area.height << ")" << endl;
    }

    return exclusionAreas;
}

//检查轮廓是否在排除区域内
bool isContourInExclusionArea(const vector<Point>& contour,
                             const vector<cv::Rect>& exclusionAreas,
                             double threshold = 0.75) {
    int pointsInArea = 0;
    int totalPoints = contour.size();

    //std::cout << __FUNCTION__ << ": Checking contour with " << totalPoints << " points" << std::endl;

    // 打印所有排除区域
//    for (size_t i = 0; i < exclusionAreas.size(); i++) {
//        const auto& area = exclusionAreas[i];
//        std::cout << "Exclusion area " << i << ": (" << area.x << ", " << area.y
//                  << ", " << area.width << ", " << area.height << ")" << std::endl;
//    }

    // 手动检查每个点是否在排除区域内
    for (const auto& point : contour) {
        bool inArea = false;

        // 手动检查点是否在任何一个排除区域内
        for (size_t i = 0; i < exclusionAreas.size(); i++) {
            const auto& area = exclusionAreas[i];

            // 手动实现点是否在矩形内的检查
            if (point.x >= area.x && point.x < area.x + area.width &&
                point.y >= area.y && point.y < area.y + area.height) {
                pointsInArea++;
                inArea = true;
//                std::cout << "Point " << point << " is in exclusion area " << i
//                          << ": (" << area.x << ", " << area.y
//                          << ", " << area.width << ", " << area.height << ")" << std::endl;
                break;
            }
        }

//        if (!inArea) {
//            std::cout << "Point " << point << " is NOT in any exclusion area" << std::endl;
//        }
    }

    double ratio = (double)pointsInArea / totalPoints;
//    std::cout << "Points in exclusion areas: " << pointsInArea << "/" << totalPoints
//              << " = " << ratio << " (threshold: " << threshold << ")" << std::endl;

    // 如果超过阈值比例的点在排除区域内,则返回true
    return ratio > threshold;
}

void getallContours(const cv::Mat& inputImage, vector<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);
    cout << "find " << contours.size() << " Contours" << endl;
}

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

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

    // 转换为整数点坐标
    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++) {
        cout << "top" << i+1 << ": (" << rectPoints[i].x << ", " << rectPoints[i].y << ")" << 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);
    cout << "area: " << area << endl;
}

int filterContours(const vector<vector<cv::Point>>& contours,
                   vector<vector<cv::Point>>& filteredContours,
                   const vector<cv::Rect>& exclusionAreas) {
    int excludedCount = 0;

    for (const auto& contour : contours) {
        if (isContourInExclusionArea(contour, exclusionAreas, 0.75)) {
            excludedCount++;
            continue; // 跳过这个轮廓
        }
        filteredContours.push_back(contour);
    }

    cout << "filter " << excludedCount << " edge Contours" << endl;
    return excludedCount;
}

void detectContour(cv::Mat& inputImage) {
    cv::Mat getImagecontours = inputImage.clone();
    //1.在原始图像上绘制gridsizexgridsize网格并获取排除区域
    int gridsize = 20;
    // 控制是否绘制网格线--用于调试
    bool drawGridLines = true;
    vector<cv::Rect> exclusionAreas;
    //drawGrid的行和列的索引
    {//四条边界
        exclusionAreas=drawGrid(inputImage, gridsize, drawGridLines);
    }
    {//第2行和第3行的第2列网格需要排除
        cv::Rect pos;
        pos=getGridCell(inputImage,gridsize,2,2);
        exclusionAreas.push_back(pos);
        pos=getGridCell(inputImage,gridsize,3,2);
        exclusionAreas.push_back(pos);

        pos=getGridCell(inputImage,gridsize,2,19);
        exclusionAreas.push_back(pos);
    }
    {//2.第5行的第4和第5列网格需要排除
        cv::Rect pos;
        pos=getGridCell(inputImage,gridsize,5,4);
        exclusionAreas.push_back(pos);
        pos=getGridCell(inputImage,gridsize,5,5);
        exclusionAreas.push_back(pos);
    }
    {//第5行的第16和第17列网格需要排除
        cv::Rect pos;
        pos=getGridCell(inputImage,gridsize,5,16);
        exclusionAreas.push_back(pos);
        pos=getGridCell(inputImage,gridsize,5,17);
        exclusionAreas.push_back(pos);
    }

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

    //4.过滤待排除轮廓
    vector<vector<cv::Point>> filteredContours;
    filterContours(contours, filteredContours, exclusionAreas);
    cout << "filteredContours " << filteredContours.size() << " Contours" << endl;

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

    makerectContours(contours, inputImage);
    // 显示结果
    showimg("inputImage", inputImage);
    cv::waitKey(0);
}


int main(int argc, char* argv[]) {
    string imagePath = "myimage.png";

    // 读取图片
    cv::Mat image = cv::imread(imagePath);
    if (image.empty()) {
        cout << "no can open: " << imagePath << endl;
        return -1;
    }
    cout << "size: " << image.cols << "x" << image.rows << endl;
    // 创建副本用于显示
    cv::Mat result = image.clone();
    detectContour(result);
    return 0;
}
相关推荐
程序员与背包客_CoderZ1 小时前
C/C++版LLM推理框架Llama.cpp——入门与编码实战
c语言·开发语言·网络·c++·人工智能·语言模型·llama
喵了几个咪2 小时前
C++ IDE:最适合 C++ 初学者的 IDE 是什么?
开发语言·c++·ide
梅梅绵绵冰2 小时前
springmvc文件上传
java·开发语言
Hat_man_2 小时前
虚拟机Ubuntu22.04交叉编译Qt5.15.2(ARM64)
开发语言·qt
Boop_wu2 小时前
[Java 面试] 多线程1
java·开发语言
专注于大数据技术栈2 小时前
java学习--main方法
java·开发语言·学习
2501_941802482 小时前
C++高性能并发编程实战:从多线程管理到内存优化与任务调度全流程解析
java·开发语言·c++
lqj_本人2 小时前
鸿蒙Qt音频实战:解决QMediaPlayer的高延迟与杂音问题
qt·音视频·harmonyos
Creeper.exe3 小时前
【C语言】函数
c语言·开发语言