1. 从零开始创建OpenCV画布
在计算机视觉项目中,画布是我们进行图像处理和图形绘制的基础工作区。OpenCV提供了简单而强大的画布创建功能,让我们能够像在真实画布上一样进行各种图形操作。
1.1 创建空白画布的基本方法
创建空白画布是OpenCV图形操作的起点。最常用的方法是使用cv::Mat类的构造函数:
cpp复制#include <opencv2/opencv.hpp>
int main() {
// 创建一个800x600的白色画布(3通道BGR格式)
cv::Mat canvas(600, 800, CV_8UC3, cv::Scalar(255, 255, 255));
// 显示画布
cv::imshow("My Canvas", canvas);
cv::waitKey(0);
return 0;
}
这段代码创建了一个800像素宽、600像素高的白色画布。关键参数说明:
600:画布高度(行数)800:画布宽度(列数)CV_8UC3:图像类型,8位无符号整数,3通道(BGR)cv::Scalar(255, 255, 255):初始化颜色(白色)
提示:在OpenCV中,图像矩阵的第一个维度是高度(行数),第二个维度是宽度(列数),这与我们通常的"宽×高"习惯相反,初学者容易混淆。
1.2 画布的色彩空间选择
根据不同的应用场景,我们可以选择不同的色彩空间和数据类型:
-
灰度画布:单通道,适合简单的图像处理
cpp复制cv::Mat grayCanvas(600, 800, CV_8UC1, cv::Scalar(128)); // 中灰色 -
彩色画布:三通道BGR,最常用
cpp复制cv::Mat colorCanvas(600, 800, CV_8UC3, cv::Scalar(0, 0, 255)); // 纯红色 -
带透明通道的画布:四通道BGRA
cpp复制cv::Mat transparentCanvas(600, 800, CV_8UC4, cv::Scalar(0, 255, 0, 128)); // 半透明绿色 -
高精度画布:16位或浮点类型,适合专业图像处理
cpp复制cv::Mat highPrecisionCanvas(600, 800, CV_32FC3, cv::Scalar(1.0f, 0.5f, 0.0f)); // 浮点橙色
1.3 画布初始化技巧
在实际项目中,我们经常需要特定的初始化方式:
cpp复制// 创建渐变背景的画布
cv::Mat gradientCanvas(600, 800, CV_8UC3);
for (int y = 0; y < gradientCanvas.rows; ++y) {
for (int x = 0; x < gradientCanvas.cols; ++x) {
// 从左到右的蓝色渐变
gradientCanvas.at<cv::Vec3b>(y, x) = cv::Vec3b(
255 * x / gradientCanvas.cols, // Blue
128, // Green
255 - 255 * x / gradientCanvas.cols // Red
);
}
}
// 创建带网格线的画布
cv::Mat gridCanvas(600, 800, CV_8UC3, cv::Scalar(240, 240, 240));
const int gridSize = 20;
for (int x = 0; x < gridCanvas.cols; x += gridSize) {
cv::line(gridCanvas, cv::Point(x, 0), cv::Point(x, gridCanvas.rows), cv::Scalar(200, 200, 200));
}
for (int y = 0; y < gridCanvas.rows; y += gridSize) {
cv::line(gridCanvas, cv::Point(0, y), cv::Point(gridCanvas.cols, y), cv::Scalar(200, 200, 200));
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 静态图形绘制技术
OpenCV提供了丰富的2D绘图函数,可以绘制各种几何图形和文字。这些功能在数据可视化、界面设计和计算机视觉结果标注中非常有用。
2.1 基本几何图形绘制
2.1.1 线条绘制
线条是最基础的图形元素,OpenCV提供了灵活的线条绘制函数:
cpp复制// 绘制一条从(100,100)到(700,500)的红色直线,线宽3像素
cv::line(canvas,
cv::Point(100, 100),
cv::Point(700, 500),
cv::Scalar(0, 0, 255), // BGR颜色值
3); // 线宽
// 绘制带抗锯齿的线条(需要先转换为浮点再绘制)
cv::Mat tempCanvas;
canvas.convertTo(tempCanvas, CV_32FC3, 1.0/255);
cv::line(tempCanvas,
cv::Point2f(150.5f, 150.5f),
cv::Point2f(750.5f, 550.5f),
cv::Scalar(0.0f, 0.0f, 1.0f),
3,
cv::LINE_AA); // 抗锯齿标志
tempCanvas.convertTo(canvas, CV_8UC3, 255);
2.1.2 矩形绘制
矩形绘制有两种常用方式:实心矩形和边框矩形:
cpp复制// 绘制边框矩形(绿色,线宽2)
cv::rectangle(canvas,
cv::Point(200, 150),
cv::Point(600, 450),
cv::Scalar(0, 255, 0),
2);
// 绘制实心矩形(半透明蓝色)
cv::Mat overlay = canvas.clone();
cv::rectangle(overlay,
cv::Point(250, 200),
cv::Point(550, 400),
cv::Scalar(255, 0, 0),
cv::FILLED); // 填充模式
cv::addWeighted(overlay, 0.3, canvas, 0.7, 0, canvas); // 透明度混合
2.1.3 圆形和椭圆绘制
圆形和椭圆是常用的几何图形:
cpp复制// 绘制实心圆(黄色)
cv::circle(canvas,
cv::Point(400, 300), // 圆心
100, // 半径
cv::Scalar(0, 255, 255),
cv::FILLED);
// 绘制椭圆(旋转45度的红色椭圆)
cv::ellipse(canvas,
cv::Point(400, 300), // 中心点
cv::Size(200, 100), // 长轴和短轴长度
45, // 旋转角度
0, 360, // 起始和结束角度
cv::Scalar(0, 0, 255),
2);
2.2 多边形和复杂图形绘制
对于更复杂的图形,OpenCV提供了多边形绘制功能:
cpp复制// 定义多边形顶点
std::vector<cv::Point> polygonPoints;
polygonPoints.push_back(cv::Point(100, 100));
polygonPoints.push_back(cv::Point(200, 50));
polygonPoints.push_back(cv::Point(300, 100));
polygonPoints.push_back(cv::Point(250, 200));
polygonPoints.push_back(cv::Point(150, 200));
// 绘制多边形边框
cv::polylines(canvas,
polygonPoints,
true, // 是否闭合
cv::Scalar(255, 0, 255),
2);
// 绘制填充多边形
cv::fillPoly(canvas,
std::vector<std::vector<cv::Point>>{polygonPoints},
cv::Scalar(255, 0, 255, 128));
2.3 文字绘制与字体控制
在画布上添加文字说明是常见需求:
cpp复制// 设置字体参数
int fontFace = cv::FONT_HERSHEY_SIMPLEX;
double fontScale = 1.5;
int thickness = 2;
cv::Scalar color(0, 0, 0); // 黑色
// 获取文本大小以居中显示
std::string text = "OpenCV Drawing";
int baseline = 0;
cv::Size textSize = cv::getTextSize(text, fontFace, fontScale, thickness, &baseline);
// 计算文本位置(居中)
cv::Point textOrg((canvas.cols - textSize.width) / 2,
(canvas.rows + textSize.height) / 2);
// 绘制文本
cv::putText(canvas,
text,
textOrg,
fontFace,
fontScale,
color,
thickness,
cv::LINE_AA);
// 绘制多行文本
std::vector<std::string> lines = {"Line 1", "Line 2", "Line 3"};
int lineHeight = textSize.height + 10;
for (size_t i = 0; i < lines.size(); i++) {
cv::putText(canvas,
lines[i],
cv::Point(50, 100 + i * lineHeight),
fontFace,
fontScale * 0.8,
cv::Scalar(0, 0, 255),
thickness);
}
3. 动态图形与动画效果
静态图形虽然有用,但动态图形能更好地展示数据变化和交互效果。OpenCV结合简单的动画循环可以创建各种动态视觉效果。
3.1 基于帧更新的动画原理
OpenCV动态图形的核心原理是在循环中不断更新画布内容并显示:
cpp复制cv::Mat animationCanvas(600, 800, CV_8UC3);
int ballRadius = 30;
cv::Point ballCenter(ballRadius, animationCanvas.rows / 2);
cv::Point ballVelocity(5, 3);
while (true) {
// 清空画布(用浅灰色填充)
animationCanvas.setTo(cv::Scalar(220, 220, 220));
// 更新小球位置
ballCenter += ballVelocity;
// 边界检测(反弹)
if (ballCenter.x - ballRadius < 0 || ballCenter.x + ballRadius > animationCanvas.cols) {
ballVelocity.x *= -1;
}
if (ballCenter.y - ballRadius < 0 || ballCenter.y + ballRadius > animationCanvas.rows) {
ballVelocity.y *= -1;
}
// 绘制小球
cv::circle(animationCanvas, ballCenter, ballRadius, cv::Scalar(0, 0, 255), cv::FILLED);
// 显示动画
cv::imshow("Animation", animationCanvas);
// 按ESC退出
if (cv::waitKey(30) == 27) {
break;
}
}
3.2 粒子系统实现
粒子系统是动态图形的强大工具,可以模拟自然现象:
cpp复制#include <vector>
#include <random>
struct Particle {
cv::Point2f position;
cv::Point2f velocity;
cv::Scalar color;
float lifetime;
float size;
};
int main() {
cv::Mat particleCanvas(600, 800, CV_8UC3);
std::vector<Particle> particles;
std::default_random_engine generator;
std::uniform_real_distribution<float> dist(-1.0f, 1.0f);
while (true) {
particleCanvas.setTo(cv::Scalar(0, 0, 0)); // 黑色背景
// 添加新粒子
if (particles.size() < 500) {
Particle p;
p.position = cv::Point2f(particleCanvas.cols / 2, particleCanvas.rows / 2);
p.velocity = cv::Point2f(dist(generator) * 5, dist(generator) * 5);
p.color = cv::Scalar(0, 255, 255); // 青色
p.lifetime = 100 + dist(generator) * 50;
p.size = 3 + dist(generator) * 2;
particles.push_back(p);
}
// 更新并绘制所有粒子
for (auto it = particles.begin(); it != particles.end(); ) {
it->position += it->velocity;
it->lifetime -= 1.0f;
it->size *= 0.99f;
if (it->lifetime <= 0 || it->size <= 0.5f) {
it = particles.erase(it);
} else {
// 根据生命周期改变颜色
cv::Scalar drawColor = it->color * (it->lifetime / 100.0f);
cv::circle(particleCanvas, it->position, it->size, drawColor, cv::FILLED);
++it;
}
}
cv::imshow("Particle System", particleCanvas);
if (cv::waitKey(30) == 27) break;
}
return 0;
}
3.3 交互式动态图形
结合鼠标和键盘事件,可以创建交互式图形应用:
cpp复制#include <iostream>
cv::Mat interactiveCanvas(600, 800, CV_8UC3);
std::vector<cv::Point> drawingPoints;
bool isDrawing = false;
// 鼠标回调函数
void onMouse(int event, int x, int y, int flags, void* userdata) {
switch (event) {
case cv::EVENT_LBUTTONDOWN:
isDrawing = true;
drawingPoints.clear();
drawingPoints.push_back(cv::Point(x, y));
break;
case cv::EVENT_MOUSEMOVE:
if (isDrawing) {
drawingPoints.push_back(cv::Point(x, y));
// 实时绘制
interactiveCanvas.setTo(cv::Scalar(255, 255, 255));
if (drawingPoints.size() > 1) {
for (size_t i = 1; i < drawingPoints.size(); i++) {
cv::line(interactiveCanvas,
drawingPoints[i-1],
drawingPoints[i],
cv::Scalar(0, 0, 0),
2);
}
}
cv::imshow("Interactive Drawing", interactiveCanvas);
}
break;
case cv::EVENT_LBUTTONUP:
isDrawing = false;
if (drawingPoints.size() > 2) {
// 闭合多边形
std::vector<std::vector<cv::Point>> contours = {drawingPoints};
cv::fillPoly(interactiveCanvas, contours, cv::Scalar(rand()%256, rand()%256, rand()%256));
cv::imshow("Interactive Drawing", interactiveCanvas);
}
break;
}
}
int main() {
interactiveCanvas.setTo(cv::Scalar(255, 255, 255));
cv::namedWindow("Interactive Drawing");
cv::setMouseCallback("Interactive Drawing", onMouse);
std::cout << "Instructions:\n";
std::cout << "- Click and drag to draw a shape\n";
std::cout << "- Release to fill the shape with random color\n";
std::cout << "- Press ESC to exit\n";
while (true) {
cv::imshow("Interactive Drawing", interactiveCanvas);
if (cv::waitKey(30) == 27) break;
}
return 0;
}
4. 高级图形技术与实战应用
掌握了基础图形绘制后,我们可以探索一些更高级的技术和实际应用场景。
4.1 图形变换与仿射变换
仿射变换包括平移、旋转、缩放和剪切等操作:
cpp复制// 创建一个包含三角形的画布
cv::Mat srcCanvas(600, 800, CV_8UC3, cv::Scalar(255, 255, 255));
std::vector<cv::Point> triangle = {cv::Point(400, 100), cv::Point(300, 300), cv::Point(500, 300)};
cv::fillPoly(srcCanvas, std::vector<std::vector<cv::Point>>{triangle}, cv::Scalar(0, 0, 255));
// 定义变换前的三个点和变换后的三个点
cv::Point2f srcTri[3] = {
cv::Point2f(400, 100),
cv::Point2f(300, 300),
cv::Point2f(500, 300)
};
cv::Point2f dstTri[3] = {
cv::Point2f(400, 100),
cv::Point2f(350, 350),
cv::Point2f(450, 300)
};
// 计算仿射变换矩阵
cv::Mat warpMat = cv::getAffineTransform(srcTri, dstTri);
cv::Mat dstCanvas;
cv::warpAffine(srcCanvas, dstCanvas, warpMat, srcCanvas.size());
// 显示结果
cv::imshow("Source", srcCanvas);
cv::imshow("Affine Transform", dstCanvas);
cv::waitKey(0);
4.2 图形混合与透明度控制
图形混合可以实现各种视觉效果:
cpp复制// 创建两个不同的图形
cv::Mat circleCanvas(600, 800, CV_8UC3, cv::Scalar(255, 255, 255));
cv::circle(circleCanvas, cv::Point(400, 300), 200, cv::Scalar(0, 0, 255), cv::FILLED);
cv::Mat squareCanvas(600, 800, CV_8UC3, cv::Scalar(255, 255, 255));
cv::rectangle(squareCanvas, cv::Point(300, 200), cv::Point(500, 400), cv::Scalar(0, 255, 0), cv::FILLED);
// 简单叠加
cv::Mat simpleBlend;
cv::addWeighted(circleCanvas, 0.5, squareCanvas, 0.5, 0, simpleBlend);
// 带蒙版的混合
cv::Mat mask(600, 800, CV_8UC1, cv::Scalar(0));
cv::circle(mask, cv::Point(400, 300), 150, cv::Scalar(255), cv::FILLED);
cv::Mat maskedBlend(600, 800, CV_8UC3);
squareCanvas.copyTo(maskedBlend);
circleCanvas.copyTo(maskedBlend, mask);
// 显示混合结果
cv::imshow("Simple Blend", simpleBlend);
cv::imshow("Masked Blend", maskedBlend);
cv::waitKey(0);
4.3 图形识别与交互反馈
结合图形识别技术,可以创建更智能的交互应用:
cpp复制// 创建一个带按钮的界面
cv::Mat uiCanvas(600, 800, CV_8UC3, cv::Scalar(240, 240, 240));
cv::rectangle(uiCanvas, cv::Rect(300, 250, 200, 80), cv::Scalar(0, 150, 255), cv::FILLED);
cv::putText(uiCanvas, "Click Me!", cv::Point(320, 300),
cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 255, 255), 2);
cv::imshow("UI Demo", uiCanvas);
// 鼠标回调检测按钮点击
void onMouse(int event, int x, int y, int flags, void* userdata) {
if (event == cv::EVENT_LBUTTONDOWN) {
cv::Mat& canvas = *((cv::Mat*)userdata);
// 检查是否点击了按钮区域
if (x >= 300 && x <= 500 && y >= 250 && y <= 330) {
// 按钮按下效果
cv::rectangle(canvas, cv::Rect(300, 250, 200, 80), cv::Scalar(0, 100, 200), cv::FILLED);
cv::putText(canvas, "Clicked!", cv::Point(320, 300),
cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 255, 255), 2);
cv::imshow("UI Demo", canvas);
// 显示反馈信息
cv::Mat feedbackCanvas = canvas.clone();
cv::putText(feedbackCanvas, "Button Clicked!", cv::Point(250, 400),
cv::FONT_HERSHEY_SIMPLEX, 1.5, cv::Scalar(0, 0, 255), 3);
cv::imshow("Feedback", feedbackCanvas);
}
}
}
cv::setMouseCallback("UI Demo", onMouse, &uiCanvas);
cv::waitKey(0);
4.4 性能优化技巧
当处理复杂图形或高帧率动画时,性能优化很重要:
-
预计算和缓存:对于静态元素,预先计算并缓存结果
cpp复制cv::Mat cachedBackground; void initBackground() { cachedBackground.create(600, 800, CV_8UC3); // 复杂的背景绘制代码... } -
局部更新:只重绘发生变化的部分
cpp复制cv::Mat canvas(600, 800, CV_8UC3); cv::Rect dirtyRegion(0, 0, 0, 0); // 需要更新的区域 // 当有图形变化时 dirtyRegion |= cv::Rect(x, y, width, height); // 在渲染循环中 if (!dirtyRegion.empty()) { // 只更新脏区域 updateCanvasRegion(canvas, dirtyRegion); dirtyRegion = cv::Rect(0, 0, 0, 0); } -
使用GPU加速:对于复杂运算,考虑使用OpenCV的CUDA模块
cpp复制#include <opencv2/cudaimgproc.hpp> cv::cuda::GpuMat gpuCanvas; cv::Mat cpuCanvas(600, 800, CV_8UC3); // 上传到GPU gpuCanvas.upload(cpuCanvas); // 在GPU上绘制(示例:转换为灰度) cv::cuda::cvtColor(gpuCanvas, gpuCanvas, cv::COLOR_BGR2GRAY); // 下载回CPU gpuCanvas.download(cpuCanvas); -
减少内存分配:重用矩阵对象避免频繁内存分配
cpp复制cv::Mat temp; // 在循环外部声明 for (...) { // 重用temp矩阵 someOperation(input, temp); // ... }
