C++视觉开发 二.OpenCV基础

news2024/11/19 19:44:28

目录

本章记录OpenCV开发中的基本操作语法

一.基础

1.读取图像

2.显示图像

3.保存图像

二.图像

1.像素处理

2.彩色图像

三.滤波

1.高斯滤波(Gaussian Blur)

功能: 高斯滤波是一种常用的线性平滑滤波器,用于降低图像噪声和细节。

2.中值滤波(Median Blur)

功能: 中值滤波是一种非线性滤波方法,用于去除图像中的椒盐噪声或者斑点噪声。

四.形态学

1.cv::getStructuringElement 核函数

2.膨胀(Dilation)

3.腐蚀(Erosion)

4.开运算(Opening)

5.闭运算(Closing)

6.通用形态学操作

五. 二值化、轮廓、文本绘制

1.cv::cvtColor 颜色转换

2.cv::threshold 二值化

3.cv::findContours 查找轮廓

4. cv::drawContours 绘制轮廓

5.cv::moments 计算矩

6.cv::putText 绘制文本

7.cv::contourArea() 函数 轮廓面积

六.实战-物体计数 



本章记录OpenCV开发中的基本操作语法

首先导入库

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

一.基础

1.读取图像

cv::Mat img = cv::imread("lenacolor.png",flags);

语法:cv::Mat 图像名 = cv::imread(“图像名称”,flags);

flag标记值如下表:

cv::IMREAD_UNCHANGED保持原格式不变
cv::IMREAD_GRAYSCALE单通道灰度图像
cv::IMREAD_COLOR三通道BGR格式

2.显示图像

cv::imshow("one", img);

语法:cv::imshow("窗口名称", 图像名);

默认加入两个函数:

cv::waitKey(0); //暂停运行
cv::destroyAllWindows(); //按任意键释放窗口

方便查看图像和释放。

3.保存图像

cv::imwrite("result",img)

语法:cv::imwrite("目标文件完成路径", 图像名);

二.图像

1.像素处理

例子:

cv::Mat img = cv::imread("lena.bmp", cv::IMREAD_GRAYSCALE);
    for (int i = 10; i < 100; ++i) {
        for (int j = 80; j < 100; ++j) {
            img.at<uchar>(i, j) = 255;
        }
    }
    cv::imshow("after", img);
    cv::waitKey(0);
    cv::destroyAllWindows();

读取灰度图像,并处理其中的像素点,案例中为改为白色。

2.彩色图像

例子:

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

int main() {
    // 读取图像
    cv::Mat img = cv::imread("lenacolor.png");

    // 检查图像是否加载成功
    if (img.empty()) {
        std::cerr << "Error: Could not open or find the image 'lenacolor.png'" << std::endl;
        return -1;
    }

    // 显示原始图像
    cv::imshow("before", img);

    // 访问并打印指定像素值
    //cv::Vec3b 类型用于表示一个包含3个无符号字符(即一个像素的BGR值)的向量。
    std::cout << "访问img[0,0] = " << img.at<cv::Vec3b>(0, 0) << std::endl;
    std::cout << "访问img[0,0,0] = " << (int)img.at<cv::Vec3b>(0, 0)[0] << std::endl;
    std::cout << "访问img[0,0,1] = " << (int)img.at<cv::Vec3b>(0, 0)[1] << std::endl;
    std::cout << "访问img[0,0,2] = " << (int)img.at<cv::Vec3b>(0, 0)[2] << std::endl;
    std::cout << "访问img[50,0] = " << img.at<cv::Vec3b>(50, 0) << std::endl;
    std::cout << "访问img[100,0] = " << img.at<cv::Vec3b>(100, 0) << std::endl;

    // 区域1:白色
    for (int i = 0; i < 50; ++i) {
        for (int j = 0; j < 100; ++j) {
            img.at<cv::Vec3b>(i, j) = cv::Vec3b(255, 255, 255);
        }
    }

    // 区域2:灰色
    for (int i = 50; i < 100; ++i) {
        for (int j = 0; j < 100; ++j) {
            img.at<cv::Vec3b>(i, j) = cv::Vec3b(128, 128, 128);
        }
    }

    // 区域3:黑色
    for (int i = 100; i < 150; ++i) {
        for (int j = 0; j < 100; ++j) {
            img.at<cv::Vec3b>(i, j) = cv::Vec3b(0, 0, 0);
        }
    }

    // 区域4:红色
    for (int i = 150; i < 200; ++i) {
        for (int j = 0; j < 100; ++j) {
            img.at<cv::Vec3b>(i, j) = cv::Vec3b(0, 0, 255);
        }
    }

    // 显示修改后的图像
    cv::imshow("after", img);

    // 打印修改后的像素值
    std::cout << "修改后img[0,0] = " << img.at<cv::Vec3b>(0, 0) << std::endl;
    std::cout << "修改后img[0,0,0] = " << (int)img.at<cv::Vec3b>(0, 0)[0] << std::endl;
    std::cout << "修改后img[0,0,1] = " << (int)img.at<cv::Vec3b>(0, 0)[1] << std::endl;
    std::cout << "修改后img[0,0,2] = " << (int)img.at<cv::Vec3b>(0, 0)[2] << std::endl;
    std::cout << "修改后img[50,0] = " << img.at<cv::Vec3b>(50, 0) << std::endl;
    std::cout << "修改后img[100,0] = " << img.at<cv::Vec3b>(100, 0) << std::endl;

    // 等待按键
    cv::waitKey(0);

    // 销毁所有窗口
    cv::destroyAllWindows();

    return 0;
}

这段代码主要展示了通道问题,指的是图像的颜色通道处理。在OpenCV中,图像以BGR格式存储,即每个像素由三个无符号字符(8位)组成,分别表示蓝色(B)、绿色(G)和红色(R)的强度。通过cv::Vec3b类型的向量可以访问和修改每个像素的BGR值,例如使用img.at<cv::Vec3b>(i, j)[0]访问蓝色通道的值,img.at<cv::Vec3b>(i, j)[1]访问绿色通道的值,img.at<cv::Vec3b>(i, j)[2]访问红色通道的值。在这段代码中,通过操作这些通道的数值,实现了对图像不同区域颜色的修改,从而展示了如何处理和操作图像的颜色通道信息。

三.滤波

1.高斯滤波(Gaussian Blur)

功能: 高斯滤波是一种常用的线性平滑滤波器,用于降低图像噪声和细节。

函数语法:

cv::GaussianBlur(src, dst, ksize, sigmaX, sigmaY, borderType);
  • src: 输入图像,通常是cv::Mat类型。
  • dst: 输出图像,与输入图像有相同的尺寸和类型。
  • ksize: 高斯内核的大小。可以指定一个正奇数,例如cv::Size(3, 3),表示3x3的内核。宽度和高度可以不同,但它们都必须是正的和奇数。
  • sigmaX: X方向的高斯核标准差。0
  • sigmaY: Y方向的高斯核标准差,如果为0,则默认与sigmaX相同。
  • borderType: 边界模式,默认为cv::BORDER_DEFAULT

2.中值滤波(Median Blur)

功能: 中值滤波是一种非线性滤波方法,用于去除图像中的椒盐噪声或者斑点噪声。

函数语法:

cv::medianBlur(src, dst, ksize);
  • src: 输入图像,通常是cv::Mat类型。
  • dst: 输出图像,与输入图像有相同的尺寸和类型。
  • ksize: 中值滤波核的大小,必须是大于1的奇数,例如3、5、7等。

四.形态学

1.cv::getStructuringElement 核函数

功能:用于生成形态学操作中使用的结构元素(或称为内核、核)。形态学操作主要应用于二值图像处理,如腐蚀、膨胀、开运算、闭运算等。结构元素是形态学操作的核心,它定义了操作的形状和大小。

语法:

cv::getStructuringElement(int shape, cv::Size ksize);
参数含义
shape

结构元素的形状,可以是以下值之一:

cv::MORPH_RECT:矩形

cv::MORPH_ELLIPSE:椭圆

cv::MORPH_CROSS:十字形

ksize

结构元素的大小,类型为 cv::Size,表示宽度和高度。

例如: cv::Size(5, 5)

 返回值为cv::Mat 类型。

示例:生成不同形状的结构元素并应用于形态学操作

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

int main() {
    // 读取图像
    cv::Mat image = cv::imread("binary_image.png", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cerr << "Error: Could not open or find the image." << std::endl;
        return -1;
    }

    // 生成矩形结构元素
    cv::Mat rectElement = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));

    // 生成椭圆形结构元素
    cv::Mat ellipseElement = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5));

    // 生成十字形结构元素
    cv::Mat crossElement = cv::getStructuringElement(cv::MORPH_CROSS, cv::Size(5, 5));

    // 应用膨胀操作
    cv::Mat dilatedRect, dilatedEllipse, dilatedCross;
    cv::dilate(image, dilatedRect, rectElement);
    cv::dilate(image, dilatedEllipse, ellipseElement);
    cv::dilate(image, dilatedCross, crossElement);

    // 显示结果
    cv::imshow("Original Image", image);
    cv::imshow("Dilated with Rect", dilatedRect);
    cv::imshow("Dilated with Ellipse", dilatedEllipse);
    cv::imshow("Dilated with Cross", dilatedCross);

    // 等待按键
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

2.膨胀(Dilation)

膨胀操作将图像中的物体边界膨胀或扩展。它会将前景物体的像素值设为周围区域内的最大像素值。在图像中,膨胀可以填充物体内的空洞或连接物体,增加物体的大小。

示例代码语法:

cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
cv::dilate(src, dst, element);

3.腐蚀(Erosion)

腐蚀操作与膨胀相反,它会将前景物体的边界向内侵蚀。它会将前景物体的像素值设为周围区域内的最小像素值。在图像中,腐蚀可以去除物体边界附近的像素,减小物体的大小或分离物体。

示例代码语法:

cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
cv::erode(src, dst, element);

 注意:如果需要用到膨胀或者腐蚀的迭代次数,需要用到第五个参数

示例:腐蚀4次膨胀3次

cv::Mat erosion;
cv::erode(binary, erosion, kernel, cv::Point(-1, -1), 4);
cv::Mat dilation;
cv::dilate(erosion, dilation, kernel, cv::Point(-1, -1), 3);

4.开运算(Opening)

开运算是先进行腐蚀操作,然后进行膨胀操作的组合操作。它被用来去除图像中的噪声。开运算能够平滑物体的边界,消除细小的噪声。

示例代码语法:

cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
cv::morphologyEx(src, dst, cv::MORPH_OPEN, element);

5.闭运算(Closing)

闭运算是先进行膨胀操作,然后进行腐蚀操作的组合操作。它能够填充物体内部的小洞,连接物体。闭运算可以平滑物体的轮廓,连接相邻的物体。

示例代码语法:

cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));
cv::morphologyEx(src, dst, cv::MORPH_CLOSE, element);

6.通用形态学操作

在OpenCV中,形态学操作通常结合使用cv::Mat类型的图像和cv::morphologyEx函数来实现。

函数语法:

cv::morphologyEx(src, dst, op, kernel, anchor, iterations, borderType, borderValue);
  • src: 输入图像,通常是cv::Mat类型。
  • dst: 输出图像,与输入图像有相同的尺寸和类型。
  • op: 形态学操作类型,可以是以下几种:
    • cv::MORPH_ERODE: 腐蚀
    • cv::MORPH_DILATE: 膨胀
    • cv::MORPH_OPEN: 开运算
    • cv::MORPH_CLOSE: 闭运算
    • 其他自定义的形态学操作类型。
  • kernel: 结构元素(内核)大小和形状,通常是通过cv::getStructuringElement函数创建的,例如:
    cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5))
    创建一个5x5的矩形结构元素。

常用上面几个就够了,下面几个可以了解一下:

  • anchor: 结构元素的锚点位置,默认为(-1, -1)表示结构元素的中心。
  • iterations: 形态学操作的重复次数,默认为1。
  • borderType: 边界模式,默认为cv::BORDER_CONSTANT
  • borderValue: 边界值,默认为cv::morphologyDefaultBorderValue()

示例:

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

int main() {
    cv::Mat img = cv::imread("shapes.jpg", cv::IMREAD_GRAYSCALE);
    if (img.empty()) {
        std::cerr << "Error: Could not open or find the image 'shapes.jpg'" << std::endl;
        return -1;
    }

    // 创建结构元素(内核)
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5));

    // 进行膨胀操作
    cv::Mat dilated_img;
    cv::morphologyEx(img, dilated_img, cv::MORPH_DILATE, kernel);

    // 进行开运算(先腐蚀后膨胀)
    cv::Mat opened_img;
    cv::morphologyEx(img, opened_img, cv::MORPH_OPEN, kernel);

    // 显示原始图像和处理后的图像
    cv::imshow("Original Image", img);
    cv::imshow("Dilated Image", dilated_img);
    cv::imshow("Opened Image", opened_img);

    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

五. 二值化、轮廓、文本绘制

先展示完整案例:

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

int main() {
    // 读取图像
    cv::Mat o = cv::imread("cat3.jpg", 1);

    // 检查图像是否加载成功
    if (o.empty()) {
        std::cerr << "Error: Could not open or find the image 'cat3.jpg'" << std::endl;
        return -1;
    }

    // 显示原始图像
    cv::imshow("original", o);

    // 转换为灰度图像
    cv::Mat gray;
    cv::cvtColor(o, gray, cv::COLOR_BGR2GRAY);

    // 二值化
    cv::Mat binary;
    cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY);

    // 查找轮廓
    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

    // 绘制第一个轮廓
    cv::drawContours(o, contours, 0, cv::Scalar(0, 0, 255), 3);

    // 计算矩
    cv::Moments m = cv::moments(contours[0]);
    double m00 = m.m00; // 非0像素值的和
    double m10 = m.m10; // 非0像素值*x轴坐标值的和
    double m01 = m.m01; // 非0像素值*y轴坐标值的和

    // 计算质心
    int cx = static_cast<int>(m10 / m00);
    int cy = static_cast<int>(m01 / m00);

    // 在质心位置绘制文本
    cv::putText(o, "cat", cv::Point(cx, cy), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 3);

    // 显示结果图像
    cv::imshow("result", o);

    // 等待按键
    cv::waitKey(0);

    // 销毁所有窗口
    cv::destroyAllWindows();

    return 0;
}

 结果如图所示:

里面的重要函数:

1.cv::cvtColor 颜色转换

功能:将图像从一种颜色空间转换为另一种颜色空间。

语法:

cv::cvtColor(const cv::Mat& src, cv::Mat& dst, int code, int dstCn = 0)
参数含义
src输入图像
dst输出图像
code

颜色转换代码(常用 cv::COLOR_BGR2GRAY)

例如 cv::COLOR_BGR2GRAY 表示从BGR转换为灰度

dstCn目标图像的通道数(可选参数)

示例: 将彩色图像 o 转化为灰度图像 gray

cv::Mat gray;
cv::cvtColor(o, gray, cv::COLOR_BGR2GRAY);

2.cv::threshold 二值化

!!!!!!!重要!!!!!!!

功能:将灰度图像转换为二值图像。

语法

cv::threshold(const cv::Mat& src, cv::Mat& dst, double thresh, double maxval, int type)
参数含义
src输入图像(单通道,通常是灰度图像)
dst输出图像(与输入图像大小相同)
thresh阈值,用于比较像素值的基准值
maxval满足条件时分配给像素的最大值
type阈值类型,例如 cv::THRESH_BINARY

常用的type:     0纯黑,255纯白

  • cv::THRESH_BINARY:如果像素值大于阈值,则将其设置为 maxval,否则设置为0。
  • cv::THRESH_BINARY_INV:如果像素值大于阈值,则将其设置为0,否则设置为 maxval
  • cv::THRESH_TRUNC:如果像素值大于阈值,则将其设置为阈值,否则保持不变。
  • cv::THRESH_TOZERO:如果像素值大于阈值,则保持不变,否则设置为0。
  • cv::THRESH_TOZERO_INV:如果像素值大于阈值,则设置为0,否则保持不变。
  • cv::THRESH_BINARY_INV + cv::THRESH_OTSU:增加THRESH_OTSU方法,以自动确定最佳阈值

示例:将灰度图像 gray 转换为二值图像 binary,阈值为127,超过阈值的像素值设为255

cv::Mat binary;
cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY);
//自动最优阈值
cv::threshold(gray, binary,  0, 255, cv::THRESH_BINARY_INV+cv::THRESH_OTSU);

3.cv::findContours 查找轮廓

功能:查找图像中的轮廓。

语法:

cv::findContours(
    cv::Mat& image,
    std::vector<std::vector<cv::Point>>& contours,
    std::vector<cv::Vec4i>& hierarchy,
    int mode,
    int method
)
参数含义
image输入的单通道二值图像。此函数会修改输入图像,因此需要传递图像的副本。
contours输出的轮廓向量,每个轮廓是一个点的向量。
hierarchy输出的层次结构信息向量。
mode轮廓检索模式
method轮廓逼近方法

其中的mode和method:

  • mode(轮廓检索模式):

    • cv::RETR_EXTERNAL:只检索最外层的轮廓。
    • cv::RETR_LIST:检索所有的轮廓,不建立层次结构。
    • cv::RETR_CCOMP:检索所有的轮廓,并将它们组织成两层的层次结构。
    • cv::RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的完整层次结构。
  • method(轮廓逼近方法):

    • cv::CHAIN_APPROX_NONE:存储所有的轮廓点。
    • cv::CHAIN_APPROX_SIMPLE:压缩水平、垂直和对角线段,仅保留它们的端点。
    • cv::CHAIN_APPROX_TC89_L1cv::CHAIN_APPROX_TC89_KCOS:使用Teh-Chin链逼近算法。

示例: 在二值图像中查找轮廓,并将结果存储在 contourshierarchy 中。

//二值化图像
cv::Mat binary;
cv::threshold(src, binary, 127, 255, cv::THRESH_BINARY);

//查找轮廓
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

4. cv::drawContours 绘制轮廓

功能:在图像上绘制轮廓。

语法

cv::drawContours(
    cv::Mat& image,
    const std::vector<std::vector<cv::Point>>& contours,
    int contourIdx,
    const cv::Scalar& color,
    int thickness,
    int lineType,
    const std::vector<cv::Vec4i>& hierarchy = std::vector<cv::Vec4i>(),
    int maxLevel = INT_MAX,
    cv::Point offset = cv::Point()
)
参数含义
image目标图像,在该图像上绘制轮廓
contours

包含所有轮廓的向量,每个轮廓是一个点的向量。

使用 cv::findContours 函数从二值图像中提取出来,并存储在这个向量中。

contourIdx

轮廓的索引。

例如: -1表示绘制所有轮廓;0表示绘制第一个轮廓。

color

轮廓的颜色,用 cv::Scalar 定义。

例如: cv::Scalar(0, 0, 255) 表示红色。

thickness轮廓线的粗细,默认为 1。如果为负值,则填充轮廓。(以像素为单位)

以上为常用的参数,后面的几个为可选参数,通常不使用。

示例:(在上面的基础上)

cv::drawContours(o, contours, 0, cv::Scalar(0, 0, 255), 3);

5.cv::moments 计算矩

功能:计算给定轮廓或二值图像的空间矩。矩的计算是基于图像或轮廓中的像素值,通过数学公式来提取图像中的几何特征。

矩的具体定义和计算如下

1.零阶矩(m00): 表示图像中非零像素的总和,即图像中物体的面积。

其中,I(x,y)I(x,y)I(x,y)是图像中点(x,y)(x,y)(x,y)的像素值。

2.一阶矩(m10 和 m01): 分别表示图像中非零像素的x坐标和y坐标的加权和,用于计算质心。

3.质心(Centroid): 质心是图像中物体的重心位置,通过零阶矩和一阶矩计算得到。

语法:

cv::Moments m = cv::moments(const InputArray &array, bool binaryImage = false);
参数含义
array输入的二值图像或轮廓(可以是cv::Mat或者std::vector<cv::Point>
binaryImage

如果是二值图像,设为true;否则设为false

这个参数在处理二值图像时有助于提高计算效率。

示例:从cv::Moments对象中提取零阶矩和一阶矩,并计算质心坐标。

cv::Moments m = cv::moments(contours[0]);
double m00 = m.m00; // 非0像素值的和
double m10 = m.m10; // 非0像素值*x轴坐标值的和
double m01 = m.m01; // 非0像素值*y轴坐标值的和

// 计算质心
int cx = static_cast<int>(m10 / m00);
int cy = static_cast<int>(m01 / m00);

6.cv::putText 绘制文本

功能:用于在图像上绘制指定的文本。

语法:

cv::putText(
  cv::Mat &img, 
  const std::string &text, 
  cv::Point org, int fontFace, 
  double fontScale, cv::Scalar color, 
  int thickness = 1, 
  int lineType = 8, 
  bool bottomLeftOrigin = false
);
img输入的图像,文本将绘制在这张图像上。
text要绘制的文本字符串
org文本起始点的坐标,用cv::Point表示,指定文本左下角的位置。
fontFace

字体类型,OpenCV支持多种字体类型。

例如cv::FONT_HERSHEY_SIMPLEXcv::FONT_HERSHEY_PLAIN等。

fontScale字体缩放系数,控制文本的大小。(设为1就行)
color

文本颜色,使用cv::Scalar表示,可以定义BGR颜色值。

例如cv::Scalar(0, 0, 255)表示红色。

thickness(可选参数)文本的线条粗细(可选),默认为1。
lineType

(可选参数)线条类型,默认为8,表示8连接线。

  其他选项包括4和cv::LINE_AA(抗锯齿线条)。

bottomLeftOrigin

(可选参数)布尔值。

如果为true:文本的原点将是图像的左下角。

否则:否则是左上角。默认为false

 示例:在图像的质心位置绘制文本"cat"

// 计算质心
int cx = static_cast<int>(m10 / m00);
int cy = static_cast<int>(m01 / m00);

// 在质心位置绘制文本
cv::putText(o, "cat", cv::Point(cx, cy), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255), 3);

7.cv::contourArea() 函数 轮廓面积

功能:用于计算各轮廓的面积。

语法:

cv::contourArea(const InputArray &contour, bool oriented = false);
参数含义
contour输入的轮廓,是一个点的向量(std::vector<cv::Point>
oriented(可选参数)布尔值,如果为true,则返回有符号的面积,表示轮廓的方向(顺时针或逆时针)。默认值为false,返回绝对面积。

返回值:返回轮廓的面积(double类型)

完整示例:

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

using namespace cv;

int main() {
    // 读取图像
    cv::Mat o = cv::imread("opencv.png", 1);
    if (o.empty()) {
        std::cerr << "Error: Could not open or find the image 'opencv.png'" << std::endl;
        return -1;
    }

    // 转换为灰度图像
    cv::Mat gray;
    cv::cvtColor(o, gray, cv::COLOR_BGR2GRAY);

    // 二值化
    cv::Mat binary;
    cv::threshold(gray, binary, 127, 255, cv::THRESH_BINARY);

    // 查找轮廓
    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    cv::findContours(binary, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

    int n = contours.size();

    for (int i = 0; i < n; ++i) {
        double area = cv::contourArea(contours[i]);
        if (area > 1000) {
            // 绘制轮廓
            cv::drawContours(o, contours, i, cv::Scalar(0, 0, 255), 1);

            // 计算质心
            cv::Moments m = cv::moments(contours[i]);
            if (m.m00 != 0) { // 确保 m00 不为零
                int cx = static_cast<int>(m.m10 / m.m00);
                int cy = static_cast<int>(m.m01 / m.m00);

                // 在质心位置绘制文本
                std::string ii = std::to_string(i);
                cv::putText(o, ii, cv::Point(cx, cy), cv::FONT_HERSHEY_SCRIPT_SIMPLEX, 1, cv::Scalar(0, 0, 255));

                // 打印面积
                std::cout << "轮廓" << i << "的面积为" << area << std::endl;
            }
        }
    }

    // 显示结果图像
    cv::imshow("result", o);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

六.实战-物体计数 

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

using namespace std;

int main() {
    // 读取图像
    cv::Mat o = cv::imread("count.jpg", 1);
    if (o.empty()) {
        cerr << "Error: Could not open or find the image." << endl;
        return -1;
    }

    // 转换为灰度图像
    cv::Mat gray;
    cv::cvtColor(o, gray, cv::COLOR_BGR2GRAY);

    // 二值化,使用 OTSU 方法自动确定阈值
    cv::Mat binary;
    cv::threshold(gray, binary, 0, 255, cv::THRESH_BINARY_INV + cv::THRESH_OTSU);

    // 生成结构元素
    cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5));

    // 腐蚀和膨胀操作
    cv::Mat erosion;
    cv::erode(binary, erosion, kernel, cv::Point(-1, -1), 4);
    cv::Mat dilation;
    cv::dilate(erosion, dilation, kernel, cv::Point(-1, -1), 3);

    // 高斯模糊
    cv::Mat gaussian;
    cv::GaussianBlur(dilation, gaussian, cv::Size(3, 3), 1);

    // 查找轮廓
    vector<vector<cv::Point>> contours;
    vector<cv::Vec4i> hierarchy;
    cv::findContours(gaussian, contours, hierarchy, cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);

    // 筛选符合要求的轮廓
    vector<vector<cv::Point>> contoursOK;
    for (size_t i = 0; i < contours.size(); ++i) {
        double area = cv::contourArea(contours[i]);
        if (area > 30) {
            contoursOK.push_back(contours[i]);
        }
    }

    // 绘制筛选后的轮廓
    cv::drawContours(o, contoursOK, -1, cv::Scalar(0, 255, 0), 1);

    // 标记质心
    for (size_t i = 0; i < contoursOK.size(); ++i) {
        cv::Moments m = cv::moments(contoursOK[i]);
        if (m.m00 != 0) {
            int cx = static_cast<int>(m.m10 / m.m00);
            int cy = static_cast<int>(m.m01 / m.m00);
            string ii = to_string(i + 1);
            cv::putText(o, ii, cv::Point(cx, cy), cv::FONT_HERSHEY_PLAIN, 1.5, cv::Scalar(0, 0, 255), 2);
        }
    }

    // 显示结果
    cv::imshow("result", o);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

结果如图: 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1881996.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

案例:MySQL主从复制与读写分离

一、案例分析 1.案例概述 在实际的生产环境中&#xff0c;如果对数据库的读和写都在同一个数据库服务器中操作,无论是在安全性、高可用性还是高并发等各个方面都是完全不能满足实际需求的。因此&#xff0c;一般来说都是通过主从复制(Master-Slave)来同步数据&#xff0c;再通…

【昇思25天学习打卡营打卡指南-第十三天】ShuffleNet图像分类

ShuffleNet图像分类 ShuffleNet网络介绍 ShuffleNetV1是旷视科技提出的一种计算高效的CNN模型&#xff0c;和MobileNet, SqueezeNet等一样主要应用在移动端&#xff0c;所以模型的设计目标就是利用有限的计算资源来达到最好的模型精度。ShuffleNetV1的设计核心是引入了两种操…

stable-diffusion-webui-colab搭建SadTalker由图生成视频人

在这里选择一个stable-diffusion-webui-colab ​​​​​​​​​GitHub - camenduru/stable-diffusion-webui-colab: stable diffusion webui colab 这里我选择是&#xff1a; https://colab.research.google.com/github/camenduru/stable-diffusion-webui-colab/blob/main…

《昇思25天学习打卡营第16天 | 昇思MindSpore基于MobileNetv2的垃圾分类》

16天 本节学习了垃圾分类代码开发的方法。通过读取本地图像数据作为输入&#xff0c;对图像中的垃圾物体进行检测&#xff0c;并且将检测结果图片保存到文件中。 MobileNet网络是由Google团队于2017年提出的专注于移动端、嵌入式或IoT设备的轻量级CNN网络&#xff0c;相比于传…

百元蓝牙耳机推荐2024,百元蓝牙耳机排行榜盘点

在2024年面对琳琅满目的蓝牙耳机选项&#xff0c;消费者往往难以抉择&#xff0c;特别是在预算有限的情况下&#xff0c;如何在众多产品中挑选出既满足质量又符合预算的耳机成为了一个不小的挑战。 为了帮助大家在繁多的选择中找到真正物有所值的百元蓝牙耳机&#xff0c;我们…

Win10临时文件夹Temp无写入权限不能安装怎么解决?

网上很多解决方案&#xff0c;但其实大部分是C盘满了 使用treesize软件&#xff0c;来精准清理C盘的垃圾&#xff0c;释放空间 或者用everything来快速定位Temp的位置&#xff0c;先把里面能删的都删掉 上面两款软件都是非常好用的&#xff0c;建议大家使用

word怎么转换成pdf?分享3种PDF文件转换技巧

word怎么转换成pdf&#xff1f;在日常办公中&#xff0c;将Word转换成PDF可以带来诸多便利。首先&#xff0c;PDF格式具有跨平台的通用性&#xff0c;无论在哪个操作系统或设备上&#xff0c;都能保持文档的原始布局和格式。其次&#xff0c;PDF文件不容易被篡改&#xff0c;可…

对原生textarea加上:当前输入字数/最大输入字数

源码: <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Textarea Character Counter with Dragga…

跨模型知识融合:大模型的知识融合

大模型&#xff08;LLMs&#xff09;在多个领域的应用日益广泛&#xff0c;但确保它们的行为与人类价值观和意图一致却充满挑战。传统对齐方法&#xff0c;例如基于人类反馈的强化学习&#xff08;RLHF&#xff09;&#xff0c;虽取得一定进展&#xff0c;仍面临诸多难题&#…

浅谈区块链

区块链是一种分布式数据库技术&#xff0c;也被称为分布式账本技术。它的本质是一个去中心化的数据库&#xff0c;使用密码学相关联产生的数据块串连而成&#xff0c;用于验证其信息的有效性&#xff08;防伪&#xff09;和生成下一个区块。区块链具有“不可伪造”“全程留痕”…

肆拾玖坊的商业模式,49坊新零售奖金制度体系,众筹众创+会员制

肆拾玖坊之所以能够在短时间内成为白酒行业的“现象级”企业,,不仅是依靠独特商业模式,同时也依靠的是坚持用户为核心,围绕用户需求,让用户与产品直接产生连接理念。 坐标&#xff1a;厦门&#xff0c;我是易创客肖琳 深耕社交新零售行业10年&#xff0c;主要提供新零售系统工…

《昇思25天学习打卡营第21天 | 昇思MindSporePix2Pix实现图像转换》

21天 本节学习了通过Pix2Pix实现图像转换。 Pix2Pix是基于条件生成对抗网络&#xff08;cGAN&#xff09;实现的一种深度学习图像转换模型。可以实现语义/标签到真实图片、灰度图到彩色图、航空图到地图、白天到黑夜、线稿图到实物图的转换。Pix2Pix是将cGAN应用于有监督的图…

【正点原子K210连载】 第十五章 按键中断实验 摘自【正点原子】DNK210使用指南-CanMV版指南

1&#xff09;实验平台&#xff1a;正点原子ATK-DNK210开发板 2&#xff09;平台购买地址https://detail.tmall.com/item.htm?id731866264428 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/docs/boards/xiaoxitongban 第十五章 按键中断实…

浅谈人工智能发展趋势

第三次浪潮 人类科技发展的主线正沿着“能源”和“新型”展开。AI的尽头是光伏和储能。 如今我们正在经历第三次浪潮——信息文明。 社会生产力 劳动对象 劳动工具 劳动者 生产要素 农业文明铜器铁器 材料 人力工具 农民 土地人力 工业文明机车电力 材料动力 动力…

如何在Python中实现一个简单的爬虫程序

如何在Python中实现一个简单的爬虫程序 随着互联网的发展&#xff0c;数据已成为当今社会最宝贵的资源之一。而爬虫程序则成为了获取互联网数据的重要工具之一。本文将介绍如何在Python中实现一个简单的爬虫程序&#xff0c;并提供具体的代码示例。 确定目标网站 在开始编写爬…

单片机软件架构连载(1)-枚举(enum)

今天跟大家讲一下我在产品开发时&#xff0c;用枚举(enum)的一些骚操作&#xff0c;都是实战经验&#xff0c;不难&#xff0c;但开发经验尚浅的话&#xff0c;不一定能把它灵活应用。 为什么要讲枚举呢&#xff1f; 因为我发现它是一个容易被遗忘&#xff0c;同时又非常重要的…

LeetCode刷题之HOT100之二叉树的最近公共祖先

2024 7/1 新的一个月来啦&#xff01;也算是迎来了暑假&#xff0c;可惜我们没有暑假&#xff0c;只能待实验室&#xff0c;中途会有10天小假。Anyway&#xff0c;做题啦 1、题目描述 2、算法分析 又来到了树的部分&#xff0c;要找最近的公共祖先。想到树就会想到DFS和BFS。…

护眼灯哪些牌子好?几款最好的护眼灯品牌排行榜分享

在当代社会&#xff0c;随着工作压力和学业负担的增加&#xff0c;人们的用眼时间越来越长&#xff0c;因此保护眼睛的需求变得愈发迫切。护眼台灯作为一种护眼产品&#xff0c;已经逐渐普及&#xff0c;成为许多人的助手。然而护眼灯哪些牌子好&#xff1f;今天&#xff0c;我…

3D交互可视化编辑器求推荐,最好是针对企业级使用的?

企业级使用的3D交互可视化编辑器&#xff0c;支持编辑和调整2D、3D渲染及交互设置&#xff0c;以下几款可以关注了解一下&#xff1a; 1、Unity&#xff1a;一个广泛使用的跨平台游戏引擎&#xff0c;由Unity Technologies开发。支持开发者创建2D和3D游戏、交互式应用以及虚拟…