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

目录

本章记录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;
}

结果如图: 

  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值