目录
[1. 用 cv::polylines 画多边形轮廓(只画线)](#1. 用 cv::polylines 画多边形轮廓(只画线))
[2. 用 cv::fillPoly 画实心多边形(填充颜色)](#2. 用 cv::fillPoly 画实心多边形(填充颜色))
[3. 用 cv::drawContours 既可以画线也可以填充](#3. 用 cv::drawContours 既可以画线也可以填充)
一.图像像素值统计
什么是像素值统计?
一张数字图像由成千上万个像素组成,每个像素都有一个或几个数值(灰度图是一个 0~255 的数,彩色图是三个通道的数)。像素值统计就是对这些数值进行数学计算,得到一些概括性的指标,帮助我们了解整张图像或某个区域的"亮度、颜色分布"等特征。
常见的统计量包括:
-
最小值、最大值:图像中最暗和最亮的点。
-
均值(平均值):整体明暗程度。
-
标准差(方差):像素值分布的离散程度,即对比度高低。
-
直方图:统计每个亮度值(或每个颜色区间)出现的频数,能直观反映亮度分布。
最大值和最小值
cv::minMaxLoc 是 OpenCV 中一个非常实用的函数,用于找出单通道图像 (或任意二维数组)中的全局最小值和最大值,以及它们所在的位置(坐标)。
函数的作用
给定一张灰度图或一个单通道的矩阵,它会在所有像素中找出:
- 最小值(minVal)
- 最大值(maxVal)
- 最小值所在的像素坐标(minLoc)
- 最大值所在的像素坐标(maxLoc)
如果图像中有多个相同的最小值或最大值,默认返回第一个遇到的位置(扫描顺序通常是从左到右、从上到下)。
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 1. 以灰度模式读取图像(直接得到单通道)
cv::Mat gray = cv::imread("test.jpeg", cv::IMREAD_GRAYSCALE);
if (gray.empty()) {
std::cout << "无法读取图像,请检查路径!" << std::endl;
return -1;
}
// 2. 定义存储结果的变量
double minVal, maxVal; // 最小值和最大值
cv::Point minLoc, maxLoc; // 最小值点和最大值点的坐标
// 3. 调用 minMaxLoc
cv::minMaxLoc(gray, &minVal, &maxVal, &minLoc, &maxLoc);
// 4. 打印结果到控制台
std::cout << "最小值: " << minVal << " 位于 " << minLoc << std::endl;
std::cout << "最大值: " << maxVal << " 位于 " << maxLoc << std::endl;
return 0;
}
注意:cv::Point 是 OpenCV 中表示 2D 点(或向量) 的基本数据结构,cv::Point 有两个公开成员:x 和 y(顺序是列、行,即 (x, y) 对应图像坐标中的 (width, height))。

均值:cv::mean()
用于计算图像(或指定区域)的平均像素值。
函数原型:cv::Scalar cv::mean(InputArray src, InputArray mask = noArray())。
参数说明:
- src:输入图像,可以是多通道。
- mask:可选,指定计算的感兴趣区域。
返回值:一个 cv::Scalar 对象,包含每个通道的平均值。
我们看看
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 创建一个 3×3 的 BGR 图像,手动赋值(便于观察)
cv::Mat img(3, 3, CV_8UC3);
img.at<cv::Vec3b>(0, 0) = cv::Vec3b(10, 20, 30); // B, G, R
img.at<cv::Vec3b>(0, 1) = cv::Vec3b(20, 30, 40);
img.at<cv::Vec3b>(0, 2) = cv::Vec3b(30, 40, 50);
img.at<cv::Vec3b>(1, 0) = cv::Vec3b(40, 50, 60);
img.at<cv::Vec3b>(1, 1) = cv::Vec3b(50, 60, 70);
img.at<cv::Vec3b>(1, 2) = cv::Vec3b(60, 70, 80);
img.at<cv::Vec3b>(2, 0) = cv::Vec3b(70, 80, 90);
img.at<cv::Vec3b>(2, 1) = cv::Vec3b(80, 90, 100);
img.at<cv::Vec3b>(2, 2) = cv::Vec3b(90, 100, 110);
// 计算均值(每个通道独立)
cv::Scalar mean_val = cv::mean(img);
std::cout << "图像尺寸: " << img.size() << std::endl;
std::cout << "均值 (B, G, R): " << mean_val[0] << ", " << mean_val[1] << ", " << mean_val[2] << std::endl;
return 0;
}

标准差与方差:cv::meanStdDev()
计算图像每个通道的均值和标准差,方差可通过标准差计算得到。
参数说明:
- src:输入图像。
- mean:输出类型参数,计算得到的均值。
- stddev:输出类型参数,计算得到的标准差。
- mask:可选,指定计算的感兴趣区域。
返回值:输出参数 mean 和 stddev。
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 创建一个简单的灰度图:3×3,值从 0 到 8
cv::Mat gray(3, 3, CV_8UC1);
for (int i = 0; i < gray.rows; i++) {
for (int j = 0; j < gray.cols; j++) {
gray.at<uchar>(i, j) = i * gray.cols + j; // 0,1,2,3,4,5,6,7,8
}
}
cv::Scalar mean, stddev;
cv::meanStdDev(gray, mean, stddev);
std::cout << "灰度图像素值: \n" << gray << std::endl;
std::cout << "均值: " << mean.val[0] << std::endl;
std::cout << "标准差: " << stddev.val[0] << std::endl;
std::cout << "方差: " << stddev.val[0] * stddev.val[0] << std::endl;
return 0;
}

二.图像几何形状绘制
2.1.绘制矩形
在 OpenCV 中,绘制矩形的函数是 cv::rectangle。它可以在图像上绘制一个矩形框(空心或填充)。
- 使用左上角和右下角坐标
cpp
void cv::rectangle(
InputOutputArray img, // 要绘制矩形的图像(会被修改)
Point pt1, // 矩形的一个角点(例如左上角)
Point pt2, // 矩形的对角点(例如右下角)
const Scalar& color, // 矩形颜色(BGR顺序)
int thickness = 1, // 线条粗细(正数;若为 cv::FILLED 或 -1 则填充)
int lineType = cv::LINE_8,// 线条类型(如 cv::LINE_AA 抗锯齿)
int shift = 0 // 坐标点的小数位数(一般0)
);
- 使用矩形对象 cv::Rect
cpp
void cv::rectangle(
InputOutputArray img,
const Rect& rect, // 矩形区域(包含 x, y, width, height)
const Scalar& color,
int thickness = 1,
int lineType = cv::LINE_8,
int shift = 0
);
这两种方式都可以画出矩形来
我们直接看例子
cpp
#include <opencv2/opencv.hpp>
int main() {
// 创建一个 400x400 的黑色背景图像
cv::Mat img(400, 400, CV_8UC3, cv::Scalar(0, 0, 0));
// 1. 用两个点绘制一个空心红色矩形
cv::rectangle(img, cv::Point(50, 50), cv::Point(150, 150), cv::Scalar(0, 0, 255), 2);
// 2. 用 Rect 绘制一个填充蓝色矩形
cv::Rect rect(100, 200, 100, 80);
//cv::Rect rect(100, 200, 100, 80); 的含义是:创建一个矩形对象,
// 其左上角位于图像坐标 (100, 200) 处,宽度为 100 像素,高度为 80 像素。
cv::rectangle(img, rect, cv::Scalar(255, 0, 0), cv::FILLED);
// 3. 绘制一个绿色抗锯齿矩形
cv::rectangle(img, cv::Point(300, 50), cv::Point(380, 120), cv::Scalar(0, 255, 0), 3, cv::LINE_AA);
cv::imshow("矩形绘制", img);
cv::waitKey(0);
return 0;
}
抗锯齿 (Anti-aliasing,简称 AA)是一种用于消除计算机图形中锯齿状边缘的技术。
简单来说,当你用像素点阵显示一条斜线或曲线时,由于像素是方形的小格子,边缘会呈现出明显的阶梯状,就像"楼梯"一样,这种现象称为走样(Aliasing)。抗锯齿的作用就是让这些阶梯变得平滑:
-
原理:在物体边缘与背景之间,插入半透明的过渡像素,或者对边缘像素进行颜色的加权混合。这样,人眼看起来会感觉边缘是柔和、连续、模糊过渡的,而不是生硬的锯齿。
-
生活中的类比:想象用马赛克拼一条斜线,如果不做处理,每个小方块边界清晰,台阶感明显;如果允许使用更小、颜色渐变的马赛克,从远处看就显得光滑了。抗锯齿就是那个"渐变过渡"的过程。
-
常见应用:3D 游戏(如 MSAA、TAA)、矢量图形(如字体渲染)、图像编辑(如 PS 里的"平滑"选择边缘)。
-
效果对比:
-
无抗锯齿:阶梯明显,边缘生硬。
-
有抗锯齿:边缘柔和,视觉平滑(略微变模糊)。
-

就很简单。在 OpenCV 中,图像坐标系的原点位于窗口的左上角,X 轴水平向右增大 (即从左往右),Y 轴垂直向下增大 (即从上往下)。
cv::Rect rect(100, 200, 100, 80); 的含义是:创建一个矩形,其左上角位于图像坐标 (100, 200) 处,宽度为 100 像素,高度为 80 像素。
具体解释:
- x = 100:矩形左上角的列坐标(水平方向,从图像左边缘向右 100 像素)。
- y = 200:矩形左上角的行坐标(垂直方向,从图像上边缘向下 200 像素)。
- width = 100:矩形宽度为 100 像素(列方向覆盖范围 100 ~ 199)。
- height = 80:矩形高度为 80 像素(行方向覆盖范围 200 ~ 279)。
cpp
cv::Rect rect(100, 200, 100, 80);
等价于
cv::Rect rect;
rect.x = 100;
rect.y = 200;
rect.width = 100;
rect.height = 80;
2.2.绘制圆
在 OpenCV 中,绘制圆的函数是 cv::circle。它可以在指定图像上绘制一个圆形(空心圆或实心圆)。
cpp
void cv::circle(
InputOutputArray img, // 输入/输出图像(会被修改)
Point center, // 圆心坐标
int radius, // 半径(像素)
const Scalar& color, // 颜色(BGR顺序)
int thickness = 1, // 线条粗细(正数;若为 cv::FILLED 或 -1 则填充)
int lineType = cv::LINE_8, // 线条类型(如 cv::LINE_AA 抗锯齿)
int shift = 0 // 圆心坐标和半径的小数位数(通常0)
);
这个函数还是很简单的,我们直接看例子
cpp
#include <opencv2/opencv.hpp>
int main() {
// 创建一张 400x400 的黑色背景图像
cv::Mat img(400, 400, CV_8UC3, cv::Scalar(0, 0, 0));
// 1. 绘制一个空心红色圆,圆心 (200,200),半径 80,线宽 2
cv::circle(img, cv::Point(200, 200), 80, cv::Scalar(0, 0, 255), 2);
// 2. 绘制一个填充蓝色圆,圆心 (100,100),半径 30
cv::circle(img, cv::Point(100, 100), 30, cv::Scalar(255, 0, 0), cv::FILLED);
// 3. 绘制一个绿色抗锯齿空心圆,圆心 (300,300),半径 50,线宽 3
cv::circle(img, cv::Point(300, 300), 50, cv::Scalar(0, 255, 0), 3, cv::LINE_AA);
cv::imshow("绘制圆形示例", img);
cv::waitKey(0);
return 0;
}

2.3.绘制线段
在 OpenCV 中,绘制线段的函数是 cv::line。它可以在图像上连接两个点绘制一条直线段。
我们直接看例子
cpp
#include <opencv2/opencv.hpp>
int main() {
cv::Mat img(400, 400, CV_8UC3, cv::Scalar(0,0,0)); // 黑色背景
// 1. 红色线段,从 (50,50) 到 (200,50),线宽 2
cv::line(img, cv::Point(50,50), cv::Point(200,50), cv::Scalar(0,0,255), 2);
// 2. 绿色抗锯齿线段,从 (50,100) 到 (200,300),线宽 3
cv::line(img, cv::Point(50,100), cv::Point(200,300), cv::Scalar(0,255,0), 3, cv::LINE_AA);
// 3. 蓝色粗线段,线宽 5
cv::line(img, cv::Point(250,50), cv::Point(350,150), cv::Scalar(255,0,0), 5);
cv::imshow("绘制线段", img);
cv::waitKey(0);
return 0;
}

三.随机数与随机颜色
OpenCV 在 C++ 中提供了 cv::RNG 类(Random Number Generator),用于生成伪随机数。它基于乘性递推算法(类似 C++11 的 minstd_rand),速度很快且易于使用。
- 创建与初始化
cpp
cv::RNG rng; // 使用默认种子(固定值,每次运行序列相同)
cv::RNG rng(12345); // 指定种子,相同种子产生相同随机序列(可复现)
cv::RNG rng(getTickCount()); // 使用系统时间作为种子,每次运行不同
- 生成均匀分布的随机数
使用 .uniform(a, b) 方法,返回类型根据模板参数推导:
cpp
int val1 = rng.uniform(0, 255); // 整数范围 [0, 255)
float val2 = rng.uniform(0.0f, 1.0f); // 浮点数范围 [0, 1)
double val3 = rng.uniform(100.0, 200.0); // 双精度范围 [100, 200)
- 生成高斯(正态)分布的随机数
使用 .gaussian(sigma),返回 double:
cpp
double noise = rng.gaussian(5.0); // 均值 0,标准差 5.0
- 填充数组(Mat)的随机数
cv::RNG 提供了 fill 方法,可以快速用随机数填充整个 cv::Mat:
cpp
cv::Mat noise(100, 100, CV_8UC1);
rng.fill(noise, cv::RNG::UNIFORM, 0, 255); // 均匀分布
// 或者正态分布:rng.fill(noise, cv::RNG::NORMAL, 128, 30);
那么借助这个随机数,我们就可以来生成随机颜色
在 OpenCV 中,颜色通常表示为 cv::Scalar(b, g, r)(BGR 顺序)。随机颜色就是为三个通道分别生成 0~255 的随机数。
用法非常像下面这样子
cpp
cv::Scalar randomColor(int seed) {
cv::RNG rng(seed);
return cv::Scalar(rng.uniform(0, 255), // B
rng.uniform(0, 255), // G
rng.uniform(0, 255)); // R
}
实际应用:随机颜色绘制多个圆
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
cv::Mat img(600, 800, CV_8UC3, cv::Scalar(255, 255, 255)); // 白背景
cv::RNG rng(cv::getTickCount());
for (int i = 0; i < 50; ++i) {
int x = rng.uniform(0, img.cols);
int y = rng.uniform(0, img.rows);
int radius = rng.uniform(5, 50);
cv::Scalar color(rng.uniform(0, 255),
rng.uniform(0, 255),
rng.uniform(0, 255));
cv::circle(img, cv::Point(x, y), radius, color, -1); // 实心圆
}
cv::imshow("Random Circles", img);
cv::waitKey(0);
return 0;
}

四.多边形填充与绘制
- cv::polylines ------ 只画多边形的"边框"
作用:在图像上画出多边形的轮廓线,内部保持原样(透明/不填充)。
特点:
- 你可以决定是否闭合多边形(最后一个点是否连回第一个点)。
- 线的粗细可以调整。
- 可以一次画多个互不相连的多边形。
典型使用场景:你只想标出多边形的边界,比如在图像上圈出一个区域,但不想遮盖区域内部的内容。
- cv::fillPoly ------ 只填充多边形的"内部"
作用:把多边形的整个内部区域用指定颜色填满,不画边界线。
特点:
- 多边形必须是闭合的**(函数内部会自动把首尾连起来)**。
- 没有线宽参数,因为根本不会画线。
- 同样可以一次填充多个不相邻的多边形。
典型使用场景:你想把某个多边形区域涂成纯色,比如制作掩膜(mask)、涂色块、高亮一个区域等。
- cv::drawContours ------ "全能选手",既能画边框也能填充
作用:既可以像 polylines 一样画轮廓线,也可以像 fillPoly 一样填充内部,甚至可以在同一张图上混合操作(比如画一部分轮廓、填充另一部分)。
特点:
- 灵活性最高:通过一个参数(thickness)来控制是画线(正数)还是填充(特殊值 FILLED 或 -1)。
- 通常与 findContours 配合使用来提取图像的轮廓,但也支持直接输入自己定义的多边形顶点。
- 可以只画轮廓列表中的某一个轮廓(通过索引指定),而不必全部画出。
- 支持轮廓之间的层级关系(比如只画最外层的轮廓,不画内部的孔洞)。
1. 用 cv::polylines 画多边形轮廓(只画线)
这个函数的参数还是很简单的
cpp
void cv::polylines(
InputOutputArray img,
InputArrayOfArrays pts,
bool isClosed,
const Scalar& color,
int thickness = 1,
int lineType = LINE_8,
int shift = 0
);
参数含义:
- img:目标图像(会被修改)。通常是 cv::Mat 类型。
- pts:多边形的顶点集合。类型是 InputArrayOfArrays,你可以传入 std::vector<std::vector<cv::Point>>。外层 vector 表示多个独立的多边形,内层 vector 是一个多边形的所有顶点。
- isClosed:布尔值。true 表示将最后一个顶点与第一个顶点连接起来,形成闭合多边形;false 则不连接(变成折线)。
- color:线条颜色,**cv::Scalar(b, g, r) 格式。**例如 cv::Scalar(0,255,0) 是绿色。
- thickness:**线条宽度,正数。**默认 1。如果为负(如 -1)不会填充,只是无效或导致错误;填充要用 fillPoly 或 drawContours 的 FILLED。
- lineType:线条绘制算法类型,**默认 LINE_8(8 连接线)。**可选 LINE_4、LINE_AA(抗锯齿)等,一般不用改。
- shift:顶点坐标的小数点位数(右移位数)。**默认为 0,表示坐标是整数像素。**一般不用管。
在简单例子中,我们只用了前 5 个参数,后面的使用默认值。
cpp
#include <opencv2/opencv.hpp>
int main() {
// 创建黑色图像
cv::Mat img = cv::Mat::zeros(300, 300, CV_8UC3);
// 定义四个顶点
std::vector<cv::Point> pts = { {50,50}, {250,50}, {200,250}, {30,200} };
// polylines 需要 vector<vector<Point>>
std::vector<std::vector<cv::Point>> poly = { pts };
// 画绿色线框,线宽 2
cv::polylines(img, poly, true, cv::Scalar(0, 255, 0), 2);
cv::imshow("polylines", img);
cv::waitKey(0);
return 0;
}

2. 用 cv::fillPoly 画实心多边形(填充颜色)
函数原型:
cpp
void cv::fillPoly(
InputOutputArray img,
InputArrayOfArrays pts,
const Scalar& color,
int lineType = LINE_8,
int shift = 0,
Point offset = Point()
);
参数含义:
- img:目标图像(会被修改)。
- pts:多边形的顶点集合,格式与 polylines 完全相同(vector<vector<Point>>)。可以同时填充多个不相连的多边形区域。
- color:填充颜色,cv::Scalar(b, g, r)。
- lineType:绘制填充边界时使用的线条算法类型,默认 LINE_8。注意这个参数不是控制轮廓线宽,而是决定多边形边缘如何光栅化。
- shift:顶点坐标的小数位数偏移,默认 0。
- offset:对所有顶点坐标加上的偏移量(Point 类型)。默认为 Point()(即不偏移)。你可以用它来整体移动多边形位置而无需修改顶点数组。
简单例子中只用了前 3 个参数。
cpp
#include <opencv2/opencv.hpp>
int main() {
cv::Mat img = cv::Mat::zeros(300, 300, CV_8UC3);
std::vector<cv::Point> pts = { {50,50}, {250,50}, {200,250}, {30,200} };
std::vector<std::vector<cv::Point>> poly = { pts };
// 填充红色(不画轮廓)
cv::fillPoly(img, poly, cv::Scalar(0, 0, 255));
cv::imshow("fillPoly", img);
cv::waitKey(0);
return 0;
}

3. 用 cv::drawContours 既可以画线也可以填充
函数原型:
cpp
void cv::drawContours(
InputOutputArray image,
InputArrayOfArrays contours,
int contourIdx,
const Scalar& color,
int thickness = 1,
int lineType = LINE_8,
InputArray hierarchy = noArray(),
int maxLevel = INT_MAX,
Point offset = Point()
);
参数含义:
- image:目标图像。
- contours:轮廓列表,类型是**vector<vector<Point>>。**每个内层 vector 是一个轮廓(通常通过 findContours 获得,但也可以手动构造多边形顶点)。
- contourIdx:要绘制的轮廓索引。-1 表示绘制所有轮廓;0 表示只绘制第一个轮廓;1 只绘制第二个,依此类推。
- color:轮廓颜色。
- thickness:轮廓线的宽度。正数绘制线条;cv::FILLED(或 -1)表示填充整个轮廓内部。
- lineType:线条类型,默认 LINE_8。
- hierarchy:轮廓层次信息,默认为 noArray()。用于控制只绘制某个层级及以下的轮廓,通常与 maxLevel 配合使用。如果不需要层级过滤,可以忽略。
- maxLevel:绘制轮廓的最大层级深度。默认 INT_MAX 表示绘制所有可能的层级。仅在提供 hierarchy 时有意义。
- offset:绘制时对每个轮廓顶点加的偏移量。
在简单例子中,你只用了前 5 个参数(contourIdx=-1,thickness=FILLED),其他使用默认值。
cpp
#include <opencv2/opencv.hpp>
int main() {
cv::Mat img = cv::Mat::zeros(300, 300, CV_8UC3);
std::vector<cv::Point> pts = { {50,50}, {250,50}, {200,250}, {30,200} };
std::vector<std::vector<cv::Point>> contours = { pts };
cv::drawContours(img, contours, -1, cv::Scalar(255, 0, 0), 2); // 蓝色线宽 2
cv::imshow("drawContours fill", img);
cv::waitKey(0);
return 0;
}

cpp
#include <opencv2/opencv.hpp>
int main() {
cv::Mat img = cv::Mat::zeros(300, 300, CV_8UC3);
std::vector<cv::Point> pts = { {50,50}, {250,50}, {200,250}, {30,200} };
std::vector<std::vector<cv::Point>> contours = { pts };
// 填充黄色(第三个参数 -1 表示画所有轮廓)
cv::drawContours(img, contours, -1, cv::Scalar(0, 255, 255), cv::FILLED);
cv::imshow("drawContours fill", img);
cv::waitKey(0);
return 0;
}

看到了吧,这样子就可以画线条,也可以填充。
此外,我们还需要去解释一个参数的作用
- contourIdx:要绘制的轮廓索引。-1 表示绘制所有轮廓;0 表示只绘制第一个轮廓;1 只绘制第二个,依此类推。
这个轮廓到底是啥意思?
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 创建一个 400x400 的黑色图像
cv::Mat img = cv::Mat::zeros(400, 400, CV_8UC3);
// 定义两个轮廓(多边形)
std::vector<cv::Point> triangle;
triangle.push_back(cv::Point(100, 100));
triangle.push_back(cv::Point(200, 50));
triangle.push_back(cv::Point(150, 200));
std::vector<cv::Point> quad;
quad.push_back(cv::Point(250, 100));
quad.push_back(cv::Point(350, 100));
quad.push_back(cv::Point(350, 200));
quad.push_back(cv::Point(250, 200));
// 把两个轮廓放入一个列表(按顺序:索引0是三角形,索引1是四边形)
std::vector<std::vector<cv::Point>> contours;
contours.push_back(triangle); // contours[0]
contours.push_back(quad); // contours[1]
// 1. 演示 contourIdx = -1:绘制所有轮廓(两个都画)
cv::Mat img_all = img.clone(); // 复制一份干净图像
cv::drawContours(img_all, contours, -1, cv::Scalar(0, 255, 0), 2);
cv::imshow("contourIdx = -1 (全部)", img_all);
// 2. 演示 contourIdx = 0:只绘制第一个轮廓(三角形)
cv::Mat img_first = img.clone();
cv::drawContours(img_first, contours, 0, cv::Scalar(0, 0, 255), 2);
cv::imshow("contourIdx = 0 (只画三角形)", img_first);
// 3. 演示 contourIdx = 1:只绘制第二个轮廓(四边形)
cv::Mat img_second = img.clone();
cv::drawContours(img_second, contours, 1, cv::Scalar(255, 0, 0), 2);
cv::imshow("contourIdx = 1 (只画四边形)", img_second);
cv::waitKey(0);
return 0;
}

这个就很明白了吧。