前言

        本篇是上一篇【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> contours;
    cv::findContours(edged, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
    // 计算轮廓的"圆度"作为置信度指标
    std::vector> 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& a, const std::pair& 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裁剪图像中的特定区域。

篇尾

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