前言
本篇是上一篇【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裁剪图像中的特定区域。
篇尾
以上是在黑色背景版下试验效果比较好。