【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);}
}
函数名 | 功能描述 | 参数说明 | 返回值/作用 |
---|---|---|---|
| 颜色空间转换 |
| 将BGR图像转换为灰度图像 |
| 高斯模糊滤波 |
| 减少图像噪声和细节 |
| 计算绝对差值 |
| 计算两幅图像的绝对差异 |
| 图像阈值化 |
| 将图像二值化 |
| 创建结构元素 |
| 返回椭圆形的结构元素 |
| 形态学操作 |
| 执行闭运算和开运算 |
| Canny边缘检测 |
| 检测图像中的边缘 |
| 按位与操作 |
| 结合边缘检测和运动检测结果 |
| 查找轮廓 |
| 查找图像中的轮廓 |
| 计算轮廓面积 |
| 返回轮廓的面积 |
| 计算边界矩形 |
| 返回包含轮廓的最小矩形 |
| 计算轮廓周长 |
| 返回轮廓的周长 |
| 绘制矩形 |
| 在图像上绘制矩形框 |
| 添加文本 |
| 在图像上添加文本标签 |
| 图像加权融合 |
| 更新背景图像 |
以上代码做了优化:每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裁剪图像中的特定区域。
篇尾
以上是在黑色背景版下试验效果比较好。