【07】OpenCV C++实战篇——鼠标在图片上绘制矩形,计算矩形区域内灰度值的累加值显示在图片上,支持连续多次框选,快速计算结果,快速刷新画面不卡顿

OpenCV C++ ,鼠标在图片上绘制矩形,计算矩形区域内灰度值的累加值显示在图片上。

如果图片是多通道,则先转换为灰度图。

图像上显示每次框选的矩形区域,并显示每次矩形区域内的灰度值累加和、

支持连续多次框选,快速计算结果,快速刷新画面不卡顿。每次框选后页面都会快速刷新新的结果。

支持快速切换图像,当你打开新的图像画面无缝切换。本代码是有实际项目剥离的

1 “imgPro.h”

#ifndef IMGPRO_H
#define IMGPRO_H

#include <opencv2/opencv.hpp>
#include <string>

class imgPro
{
public:
    // 加载图像并显示图像
    void loadImage(const std::string& imagePath);
    void displayImage();

    // 计算指定矩形区域内的灰度值累加和
    void calculateGraySum(const cv::Rect& rect);

    // 静态鼠标回调函数
    static void onMouse(int event, int x, int y, int flags, void* userdata);

private:
    cv::Mat image;  // 原始图像
    cv::Mat displayImageWithSum;  // 用于显示的带有累加和覆盖的图像
    std::string imagePath;  // 图像文件路径
    bool isLoaded;  // 图像是否已加载的标志

    bool drawing;  // 是否正在绘制的标志
    cv::Point startPoint;  // 矩形的起始点
    cv::Rect selectedRect;  // 选定的矩形区域
};

#endif // IMGPRO_H

2 imgPro.cpp

#include "imgPro.h"
#include <opencv2/imgproc.hpp>
#include <iostream>

// 加载图像
void imgPro::loadImage(const std::string& imagePath)
{
    this->imagePath = imagePath;  // 设置图像路径
    image = cv::imread(imagePath);  // 读取图像
    if (image.empty())
    {
        std::cerr << "加载图像失败: " << imagePath << std::endl;  // 如果图像加载失败,输出错误信息
        isLoaded = false;  // 设置加载标志为false
        return;
    }
    isLoaded = true;  // 设置加载标志为true
    displayImageWithSum = image.clone();  // 克隆图像用于显示
    cv::namedWindow("Image", cv::WINDOW_NORMAL);  // 创建一个可调整大小的窗口
    cv::setMouseCallback("Image", onMouse, this);  // 设置鼠标回调函数
}

// 显示图像
void imgPro::displayImage()
{
    if (isLoaded)
    {
        cv::imshow("Image", displayImageWithSum);  // 使用OpenCV显示图像
        cv::waitKey(0);  // 等待按键
    }
    else
    {
        std::cerr << "图像未加载." << std::endl;  // 如果图像未加载,输出错误信息
    }
}

// 计算指定矩形区域内的灰度值累加和
void imgPro::calculateGraySum(const cv::Rect& rect)
{
    if (!isLoaded)
    {
        std::cerr << "图像未加载." << std::endl;  // 如果图像未加载,输出错误信息
        return;
    }

    // 确保矩形在图像边界内
    cv::Rect boundedRect = rect & cv::Rect(0, 0, image.cols, image.rows);

    // 克隆矩形区域的图像
    cv::Mat roi = image(boundedRect).clone();
    cv::Mat grayRoi;

    // 如果是彩色图像,转换为灰度图像
    if (roi.channels() == 3)
    {
        cv::cvtColor(roi, grayRoi, cv::COLOR_BGR2GRAY);
    }
    else
    {
        grayRoi = roi;
    }

    // 计算灰度值累加和
    long long sum = 0;
    for (int y = 0; y < grayRoi.rows; y++)
    {
        for (int x = 0; x < grayRoi.cols; x++)
        {
            sum += grayRoi.at<uchar>(y, x);  // 累加灰度值
        }
    }

    // 在图像上显示累加和
    displayImageWithSum = image.clone();
    cv::rectangle(displayImageWithSum, boundedRect, cv::Scalar(0, 0, 255), 2);  // 绘制矩形框

    std::string text = "Sum: " + std::to_string(sum);  // 准备显示的文本内容
    int baseLine;  // 基线,用于计算文本尺寸
    // 获取文本尺寸,以便在绘制时调整位置
    cv::Size textSize = cv::getTextSize(text, cv::FONT_HERSHEY_SIMPLEX, 1, 2, &baseLine);
    cv::Point textOrg(boundedRect.x, boundedRect.y - 10);  // 文本的起始位置

    // 调整文本位置,避免超出图像边界
    if (textOrg.y - textSize.height < 0) {
        textOrg.y = boundedRect.y + textSize.height + 10;  // 如果文本高度超出图像顶部,则向下移动
    }
    if (textOrg.x + textSize.width > displayImageWithSum.cols) {
        textOrg.x = boundedRect.x - textSize.width - 10;  // 如果文本宽度超出图像右边,则向左移动
    }

    cv::putText(displayImageWithSum, text, textOrg, cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 2);  // 在图像上显示累加和

    cv::imshow("Image", displayImageWithSum);  // 使用OpenCV显示带有累加和的图像
}


// 鼠标回调函数
void imgPro::onMouse(int event, int x, int y, int flags, void* userdata)
{
    imgPro* self = static_cast<imgPro*>(userdata);

    if (!self->isLoaded)
        return;

    switch (event)
    {
    case cv::EVENT_LBUTTONDOWN:  // 左键按下事件
        self->drawing = true;
        self->startPoint = cv::Point(x, y);
        break;
    case cv::EVENT_MOUSEMOVE:  // 鼠标移动事件
        if (self->drawing)
        {
            self->displayImageWithSum = self->image.clone();
            cv::rectangle(self->displayImageWithSum, self->startPoint, cv::Point(x, y), cv::Scalar(0, 0, 255), 2);
            cv::imshow("Image", self->displayImageWithSum);
        }
        break;
    case cv::EVENT_LBUTTONUP:  // 左键释放事件
        self->drawing = false;
        self->selectedRect = cv::Rect(self->startPoint, cv::Point(x, y));
        self->calculateGraySum(self->selectedRect);
        break;
    }
}

3. 调用示例

#include "imgPro.h"
#include <opencv2/opencv.hpp>
#include <iostream>

int main(int argc, char** argv)
{
    imgPro imgpro;

    std::string imagePath = "E:\\projects\\grayVal\\3.png";  // 设置图像路径

    imgpro.loadImage(imagePath);  // 加载图像
    imgpro.displayImage();  // 显示图像

    return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

<think>我们已知用户想使用C++OpenCV检测直线物体并计算其角度。根据提供的引用,我们可以使用轮廓检测或霍夫变换来检测直线,然后计算角度。引用[1]提到了轮廓检测,可以用于检测物体的轮廓,进而可能用于计算物体的旋转角度。引用[2]提到了霍夫圆检测,但同时也暗示了霍夫变换可以用于直线检测(因为霍夫变换既可以检测直线也可以检测圆)。通常,检测直线并计算角度有以下两种常见方法:1.使用霍夫变换检测直线,然后计算每条直线的角度。2.先检测物体的轮廓,然后拟合轮廓的最小外接矩形,再根据外接矩形的倾斜角度得到物体的旋转角度。这里,用户明确要求检测“直线物体”,这可以理解为细长的、类似直线状的物体(如棍子、笔等),或者是图像中的直线段。因此,我们可以提供两种方法,并给出步骤和示例代码。方法一:使用霍夫变换检测直线步骤:1.读取图像并转换为灰度图。2.进行边缘检测(如Canny)。3.使用霍夫变换检测直线(HoughLines或HoughLinesP)。4.对于检测到的每条直线,计算其角度(弧度),然后转换为角度(度)。5.计算平均角度或选择主要直线的角度。方法二:使用轮廓检测并拟合外接矩形步骤:1.读取图像并转换为灰度图。2.二值化图像。3.检测轮廓(findContours)。4.遍历轮廓,对每个轮廓求最小外接矩形(minAreaRect)。5.从最小外接矩形中获取旋转角度。由于用户提到的是“直线物体”,如果是连续的物体,方法二可能更合适,因为它可以得到物体的整体倾斜角度。如果是多条直线,则方法一更合适。我们分别提供两种方法的代码示例:方法一:霍夫变换(以HoughLinesP为例,因为它检测线段)```cpp#include<opencv2/opencv.hpp>#include<iostream>#include<vector>usingnamespacecv;usingnamespacestd;intmain(){//读取图像Matsrc=imread("object.jpg");if(src.empty()){cout<< "Couldnotopenorfindtheimage!"<< endl;return-1;}Matgray;cvtColor(src,gray,COLOR_BGR2GRAY);//边缘检测Matedges;Canny(gray,edges,50,200,3);//霍夫变换检测直线vector<Vec4i>lines;HoughLinesP(edges,lines,1,CV_PI/180,50,50,10);//计算每条直线的角度并求平均(或者取主要直线)doubleangleSum=0;intcount=0;//在图像上绘制直线for(size_ti=0;i< lines.size();i++){Vec4il=lines[i];line(src,Point(l[0],l[1]),Point(l[2],l[3]),Scalar(0,0,255),1,LINE_AA);//计算这条直线的角度(弧度)doubleangle=atan2(l[3]-l[1],l[2]-l[0]);doubleangleDeg=angle*180/CV_PI;//这里我们只考虑锐角(比如在-85到85度之间),避免垂直直线的90度干扰if(fabs(angleDeg)<85){angleSum+=angleDeg;count++;}}doubleaverageAngle=(count>0)?(angleSum/count):0;cout<< "Averageangle(fromlines)="<< averageAngle<< "degrees."<< endl;imshow("DetectedLines",src);waitKey(0);return0;}```注意:使用霍夫变换时,我们检测的是直线段,然后计算每条直线段的角度。如果物体是直线状的,可能会有多条线段组成,因此我们计算平均角度。但这种方法可能受背景中的直线干扰,所以需要根据实际情况调整参数。方法二:轮廓检测并拟合最小外接矩形这种方法适合单个物体,而且物体是细长的,这样最小外接矩形可以反映物体的方向。```cpp#include<opencv2/opencv.hpp>#include<iostream>usingnamespacecv;usingnamespacestd;intmain(){Matsrc=imread("object.jpg");if(src.empty()){cout<< "Couldnotopenorfindtheimage!"<< endl;return-1;}Matgray;cvtColor(src,gray,COLOR_BGR2GRAY);//二值化Matthresh;threshold(gray,thresh,200,255,THRESH_BINARY);//查找轮廓vector<vector<Point>>contours;findContours(thresh,contours,RETR_EXTERNAL,CHAIN_APPROX_SIMPLE);//按面积排序,假设最大的轮廓是我们要的物体sort(contours.begin(),contours.end(),[](constvector<Point>&c1,constvector<Point>&c2){returncontourArea(c1)>contourArea(c2);});//取最大的轮廓(如果有的话)if(!contours.empty()){//计算最小外接矩形RotatedRectminRect=minAreaRect(contours[0]);//最小外接矩形的角度(范围在[0,90]度,需要调整)floatangle=minRect.angle;if(minRect.size.width< minRect.size.height){//注意:OpenCV中,minAreaRect返回的angle可能是-90到0度,当物体垂直时宽度会小于高度,所以我们调整角度angle+=90;}cout<< "Objectrotationangle="<< angle<< "degrees."<< endl;//绘制外接矩形Point2fvertices[4];minRect.points(vertices);for(inti=0;i<4;i++)line(src,vertices[i],vertices[(i+1)%4],Scalar(0,255,0),2);}imshow("ContourDetection",src);waitKey(0);return0;}```注意:这种方法中,最小外接矩形的角度angle的取值在0到90度之间。但有时候,当物体垂直时,矩形的宽度会小于高度,此时角度要加上90度。具体的调整方法可能需要根据实际物体的长宽比来判断。此外,这种方法只适用于单个物体。根据用户具体的物体情况,选择合适的方法。如果图像中有多条直线,且没有明显的物体轮廓,则使用霍夫变换。如果有一个明显的直线状物体,则使用轮廓检测。最后,我们提供几个相关问题:§§相关问题§§1.如何提高霍夫变换检测直线的准确性?2.使用最小外接矩形计算物体角度时,如果物体有多个怎么办?3.在轮廓检测方法中,如何排除背景中的其他轮廓干扰?</think> ### 使用 OpenCV 检测直线物体并计算角度的方法 在 C++ 中使用 OpenCV 检测直线物体并计算角度,主要通过以下步骤实现: #### 方法步骤 1. **图像预处理** - 转为灰度图:`cvtColor()` - 高斯模糊:`GaussianBlur()` 减少噪声 - Canny 边缘检测:`Canny()` 提取边缘轮廓 [^1] 2. **直线检测方法** **方法一:霍夫变换 (推荐)** ```cpp std::vector<cv::Vec4i> lines; cv::HoughLinesP( edgeImage, // 边缘检测后的图像 lines, // 存储检测结果的向量 1, // 距离分辨率(像素) CV_PI/180, // 角度分辨率(弧度) 50, // 阈值(加器) 30, // 最小线段长度 10 // 最大线段间距 ); ``` **方法二:轮廓检测法** ```cpp std::vector<std::vector<cv::Point>> contours; cv::findContours( binaryImage, // 二值化图像 contours, // 存储轮廓的向量 cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE ); ``` 3. **角度计算** - 霍夫变换法:计算线段斜率 ```cpp for(const auto& line : lines) { float angle = atan2(line[3] - line[1], line[2] - line[0]) * 180 / CV_PI; } ``` - 轮廓法:获取最小外接矩形 ```cpp cv::RotatedRect rect = cv::minAreaRect(contours[0]); float angle = rect.angle; // 直接获取旋转角度 ``` #### 完整示例代码 ```cpp #include <opencv2/opencv.hpp> int main() { cv::Mat src = cv::imread("object.jpg"); cv::Mat gray, blurred, edges; // 预处理 cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::GaussianBlur(gray, blurred, cv::Size(5,5), 1.5); cv::Canny(blurred, edges, 50, 150); // 直线检测 std::vector<cv::Vec4i> lines; cv::HoughLinesP(edges, lines, 1, CV_PI/180, 50, 30, 10); // 计算和标注角度 for(const auto& line : lines) { // 绘制线段 cv::line(src, cv::Point(line[0], line[1]), cv::Point(line[2], line[3]), cv::Scalar(0,0,255), 2); // 计算角度 float angle = atan2(line[3]-line[1], line[2]-line[0]) * 180 / CV_PI; if(angle < 0) angle += 180; // 转换为0-180度范围 // 显示角度 cv::putText(src, std::to_string(angle), cv::Point(line[0], line[1]-10), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255,0,0)); } cv::imshow("Result", src); cv::waitKey(); return 0; } ``` #### 关键参数说明 1. **霍夫变换参数** - `距离分辨率`:建议1像素 - `角度分辨率`:建议1度 ($1^\circ=\pi/180$弧度) - `阈值`:检测直线的最小加值(根据图像调整) 2. **角度范围** - OpenCV 返回角度范围:$-90^\circ$ 到 $+90^\circ$ - 通常转换为 $0^\circ$ 到 $180^\circ$ 范围更直观 #### 注意事项 1. 光照均匀的图像效果更好 2. 长直线物体推荐霍夫变换 3. 复杂形状物体建议使用轮廓法 4. 结果需考虑OpenCV坐标系:原点在左上角
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

R-G-B

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值