【QT常用技术讲解】opencv实现摄像头图像检测并裁剪物体

前言

本篇是上一篇【QT常用技术讲解】opencv实现指定分辨率打开摄像头的延伸,增加了opencv常见的物体检测及裁剪功能。

效果图

源码请查看资源,opencv的window环境搭建请看【QT入门到晋级】window opencv安装及引入qtcreator(包含两种qt编译器:MSVC和MinGW)

功能讲解

本篇只讲增加的物体检测、(拍照)裁剪功能。

物体描边

增加了一个勾选项edgeDetectionCheckBox,方便结合【拍照】功能,截取出裁边/不裁边的图片,物体描边源码如下

复制代码
void MainWindow::detectAndDrawObjects(cv::Mat &frame)
{
    cv::Mat gray, blurred, diff;
    cv::cvtColor(frame, gray, cv::COLOR_BGR2GRAY);
    cv::GaussianBlur(gray, blurred, cv::Size(5, 5), 0);

    // 背景初始化
    if (background.empty()) {
        blurred.copyTo(background);
        return;
    }

    // 背景减除
    cv::absdiff(blurred, background, diff);
    cv::threshold(diff, diff, 30, 255, cv::THRESH_BINARY);//可对255进行调整


    // 形态学操作,去除噪声
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5));
    cv::morphologyEx(diff, diff, cv::MORPH_CLOSE, kernel);
    cv::morphologyEx(diff, diff, cv::MORPH_OPEN, kernel);

    // 边缘检测
    cv::Mat edged;
    cv::Canny(blurred, edged, 50, 150);

    // 结合运动检测和边缘检测
    cv::bitwise_and(edged, edged, edged, diff);

    // 查找轮廓
    std::vector<std::vector<cv::Point>> contours;
    cv::findContours(edged, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

    // 计算轮廓的"圆度"作为置信度指标
    std::vector<std::pair<cv::Rect, double>> detectedObjects;

    for (size_t i = 0; i < contours.size(); i++) {
        double area = cv::contourArea(contours[i]);
        if (area > 500) { // 只处理足够大的轮廓
            cv::Rect rect = cv::boundingRect(contours[i]);

            // 计算轮廓的圆度(周长^2/面积)
            double perimeter = cv::arcLength(contours[i], true);
            double circularity = (perimeter * perimeter) / (4 * CV_PI * area);

            // 圆度接近1表示更接近圆形(更可能是真实物体)
            if (circularity < 2.0) { // 只保留形状较简单的物体
                detectedObjects.push_back(std::make_pair(rect, circularity));
            }
        }
    }

    // 按面积排序,选择最大的物体
    cv::Rect currentRect(0, 0, 0, 0);
    if (!detectedObjects.empty()) {
        std::sort(detectedObjects.begin(), detectedObjects.end(),
            [](const std::pair<cv::Rect, double>& a, const std::pair<cv::Rect, double>& b) {
                return (a.first.width * a.first.height) > (b.first.width * b.first.height);
            });

        currentRect = detectedObjects[0].first;
    }

    // 添加到缓冲区
    recentDetections.push_back(currentRect);
    if (recentDetections.size() > BUFFER_SIZE) {
        recentDetections.erase(recentDetections.begin());
    }

    // 计算平均位置和大小
    int avgX = 0, avgY = 0, avgWidth = 0, avgHeight = 0;
    int validCount = 0;

    for (const auto& rect : recentDetections) {
        if (rect.width > 0 && rect.height > 0) { // 只考虑有效检测
            avgX += rect.x;
            avgY += rect.y;
            avgWidth += rect.width;
            avgHeight += rect.height;
            validCount++;
        }
    }

    // 如果有有效检测,绘制平滑后的矩形
    if (validCount > 0) {
        avgX /= validCount;
        avgY /= validCount;
        avgWidth /= validCount;
        avgHeight /= validCount;

        cv::Rect smoothedRect(avgX, avgY, avgWidth, avgHeight);
        cv::rectangle(frame, smoothedRect, cv::Scalar(0, 255, 0), contourThickness);

        // 显示轮廓面积
        std::string label = cv::format("Area: %d", avgWidth * avgHeight);
        cv::putText(frame, label, cv::Point(avgX, avgY - 10),
                   cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 255, 0), 2);
    }

    // 缓慢更新背景(每5帧更新一次)
    static int frameCount = 0;
    frameCount++;
    if (frameCount % 5 == 0) {
        cv::addWeighted(background, 0.95, blurred, 0.05, 0, background);
    }
}
函数名 功能描述 参数说明 返回值/作用
cv::cvtColor() 颜色空间转换 frame: 输入图像 gray: 输出图像 cv::COLOR_BGR2GRAY: 转换类型 将BGR图像转换为灰度图像
cv::GaussianBlur() 高斯模糊滤波 gray: 输入图像 blurred: 输出图像 cv::Size(5, 5): 核大小 0: 标准差 减少图像噪声和细节
cv::absdiff() 计算绝对差值 blurred: 输入图像1 background: 输入图像2 diff: 输出图像 计算两幅图像的绝对差异
cv::threshold() 图像阈值化 diff: 输入图像 diff: 输出图像 30: 阈值 255: 最大值 cv::THRESH_BINARY: 阈值类型 将图像二值化
cv::getStructuringElement() 创建结构元素 cv::MORPH_ELLIPSE: 形状 cv::Size(5, 5): 大小 返回椭圆形的结构元素
cv::morphologyEx() 形态学操作 diff: 输入图像 diff: 输出图像 cv::MORPH_CLOSE/OPEN: 操作类型 kernel: 结构元素 执行闭运算和开运算
cv::Canny() Canny边缘检测 blurred: 输入图像 edged: 输出图像 50: 低阈值 150: 高阈值 检测图像中的边缘
cv::bitwise_and() 按位与操作 edged: 输入图像1 edged: 输入图像2 edged: 输出图像 diff: 掩码 结合边缘检测和运动检测结果
cv::findContours() 查找轮廓 edged: 输入图像 contours: 输出轮廓 cv::RETR_EXTERNAL: 检索模式 cv::CHAIN_APPROX_SIMPLE: 近似方法 查找图像中的轮廓
cv::contourArea() 计算轮廓面积 contours[i]: 输入轮廓 返回轮廓的面积
cv::boundingRect() 计算边界矩形 contours[i]: 输入轮廓 返回包含轮廓的最小矩形
cv::arcLength() 计算轮廓周长 contours[i]: 输入轮廓 true: 轮廓是否闭合 返回轮廓的周长
cv::rectangle() 绘制矩形 frame: 目标图像 smoothedRect: 矩形位置和大小 cv::Scalar(0, 255, 0): 颜色 contourThickness: 线宽 在图像上绘制矩形框
cv::putText() 添加文本 frame: 目标图像 label: 文本内容 cv::Point(avgX, avgY - 10): 位置 cv::FONT_HERSHEY_SIMPLEX: 字体 0.5: 字体大小 cv::Scalar(0, 255, 0): 颜色 2: 线宽 在图像上添加文本标签
cv::addWeighted() 图像加权融合 background: 输入图像1 0.95: 权重1 blurred: 输入图像2 0.05: 权重2 0: 伽马值 background: 输出图像 更新背景图像

以上代码做了优化:每5帧更新一次。另外,为了图像稳定,设置了10fps更新一次。(假设图像不是一直在变化,比如高拍仪拍照的场景)

拍照裁剪

复制代码
void MainWindow::on_captureBtn_clicked()
{
    if(!capture || !capture->isOpened()) return;

    cv::Mat frame;
    *capture >> frame;
    if(!frame.empty()) {//检查图像矩阵是否为空
        if(enableObjectDetection) {
            // 找到主要物体并裁剪
            cv::Rect mainObject = findMainObject(frame);//查找图像中的主要物体
            cv::Mat cropped = segmentObject(frame, mainObject);//裁剪图像中的特定区域
            saveCapturedImage(cropped);//保存图像到文件
        } else {
            // 未开启检测时保存整图
            saveCapturedImage(frame);//保存图像到文件
        }
    }
}


void MainWindow::saveCapturedImage(const cv::Mat& image)
{
    // 获取桌面路径
    QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
    QString timestr = QDateTime::currentDateTime().toString("yyyy_MM_dd_hh_mm_ss");
    QString fileName = QString("%1/%2.png").arg(desktopPath).arg(timestr);
    cv::imwrite(fileName.toStdString(), image);
    QMessageBox::information(this, "提示", "图片保存成功: " + fileName);
}

enableObjectDetection是勾选项edgeDetectionCheckBox的值,通过segmentObject裁剪图像中的特定区域。

篇尾

以上是在黑色背景版下试验效果比较好。

相关推荐
偷光4 小时前
浏览器中的隐藏IDE: Elements (元素) 面板
开发语言·前端·ide·php
DKPT4 小时前
JVM栈溢出和堆溢出哪个先满?
java·开发语言·jvm·笔记·学习
gopyer7 小时前
180课时吃透Go语言游戏后端开发6:Go语言的循环语句
开发语言·游戏·golang·循环语句
ajassi20009 小时前
开源 C++ QT QML 开发(二)工程结构
linux·qt·qml
楼田莉子10 小时前
Qt开发学习——QtCreator深度介绍/程序运行/开发规范/对象树
开发语言·前端·c++·qt·学习
韩立学长11 小时前
【开题答辩实录分享】以《基于python的奶茶店分布数据分析与可视化》为例进行答辩实录分享
开发语言·python·数据分析
天若有情67311 小时前
C++空值初始化利器:empty.h使用指南
开发语言·c++
远远远远子11 小时前
类与对象 --1
开发语言·c++·算法
无敌最俊朗@12 小时前
C/C++ 关键关键字面试指南 (const, static, volatile, explicit)
c语言·开发语言·c++·面试