feat: init OpenCV C++ training course with 8 experiments and detailed tutorial

This commit is contained in:
TuxMonkey
2026-07-27 14:23:06 +08:00
commit 5fdfd9791a
18 changed files with 3843 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
# ============================================================
# 实验 1:图像读取与保存 - CMake 构建配置文件
# ============================================================
# cmake_minimum_required: 指定构建此项目所需的最低 CMake 版本
# VERSION 3.10 是一个相对较老但足够稳定的版本(WSL Ubuntu 默认通常满足)
cmake_minimum_required(VERSION 3.10)
# project: 给你的项目起一个名字
# 这个名字会出现在生成的解决方案中,也方便你辨认
# 命名规则:exp + 实验编号 + 下划线 + 实验英文名
project(exp1_read_write)
# set(CMAKE_CXX_STANDARD 11): 指定使用 C++11 标准
# C++11 提供了 auto、范围 for 等现代特性,代码写起来更舒服
set(CMAKE_CXX_STANDARD 11)
# CMAKE_CXX_STANDARD_REQUIRED: 设为 ON 表示"必须"使用 C++11
# 如果编译器不支持 C++11,CMake 会直接报错,而不是偷偷降级
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# find_package: 寻找外部库,这里是寻找 OpenCV
# REQUIRED 表示 OpenCV 是必须的,找不到就报错并停止配置
# CMake 会自动查找 OpenCV 的头文件路径和库文件路径
find_package(OpenCV REQUIRED)
# add_executable: 告诉 CMake 我们要生成一个可执行文件
# 第一个参数 "exp1" 是可执行文件的名字(编译后运行 ./exp1)
# 第二个参数 "main.cpp" 是参与编译的源文件(可以有多个)
add_executable(exp1 main.cpp)
# target_link_libraries: 将 OpenCV 的库文件链接到我们的可执行文件上
# ${OpenCV_LIBS} 是 find_package(OpenCV) 自动填充的变量
# 它包含了 OpenCV 所有需要的 .so 库文件路径
# 不写这一行的话,编译时会出现"未定义引用"的链接错误
target_link_libraries(exp1 ${OpenCV_LIBS})
# include_directories: 将 OpenCV 的头文件目录添加到编译器的搜索路径中
# ${OpenCV_INCLUDE_DIRS} 也是 find_package 自动填充的
# 这样编译器才能找到 #include <opencv2/opencv.hpp> 这样的头文件
include_directories(${OpenCV_INCLUDE_DIRS})

162
exp1_read_write/main.cpp Normal file
View File

@@ -0,0 +1,162 @@
/*
* ============================================================
* 实验 1:图像读取与保存
* 知识点:cv::Mat、cv::imread、cv::imwrite
* ============================================================
*
* 【实验目标】
* 1. 理解 cv::Mat —— OpenCV 中存储图像的核心数据类型
* 2. 学会用 cv::imread 从文件中读取图像
* 3. 学会用 cv::imwrite 将图像保存到文件
* 4. 养成检查图像是否读取成功的习惯
*
* 【生活化比喻】
* 把 cv::Mat 想象成一张"棋盘格纸":
* - 每个格子是一个"像素"(就是图像上的一个小点)
* - 格子里填的数字代表颜色(比如 0=黑, 255=白)
* - 整张纸就是一幅图像,OpenCV 把它存在计算机内存里
*
* cv::imread 就像是"用扫描仪把一张打印好的照片扫进电脑"
* cv::imwrite 就像是"用打印机把电脑里的图打印出来"
*/
// 包含 OpenCV 核心头文件
// opencv.hpp 是一个"总头文件",包含了 OpenCV 所有常用模块
// 这是最方便的写法,学习阶段推荐使用
#include <opencv2/opencv.hpp>
// 包含标准输入输出流,用于打印信息到终端
#include <iostream>
// main 函数 —— 所有 C++ 程序的入口点
// argc 是命令行参数个数,argv 是参数内容数组
// 在本实验中我们不使用命令行参数,所以参数名可以省略
int main()
{
// ==========================================
// 第一部分:生成一张测试图片
// ==========================================
// 为什么先生成一张图片而不是直接读取?
// 因为我们要确保每个同学都能运行成功,不依赖任何外部图片文件
// 等学会读取之后,你可以把自己的照片放到这个文件夹里来测试!
// cv::Mat(行数, 列数, 类型, 颜色值) —— 创建一个矩阵/图像
// - 480:图像高度 480 像素(行数),也就是垂直方向有 480 个点
// - 640:图像宽度 640 像素(列数),也就是水平方向有 640 个点
// - CV_8UC3:每个像素用 8 位无符号整数 × 3 个通道表示
// - 8U = 8-bit Unsigned(取值范围 0~255)
// - C3 = 3 个通道(Channel),也就是 B(蓝)、G(绿)、R(红)
// - 所以每个像素需要 3 个字节来存储颜色信息
// - cv::Scalar(255, 255, 255):Scalar 是一个四元组 (B, G, R, Alpha)
// - 这里 (255,255,255) 就是纯白色(B=255, G=255, R=255)
// 注意 OpenCV 的颜色顺序是 BGR,不是我们常见的 RGB!
// 这是历史原因造成的,记住就好:OpenCV 里永远是 BGR 顺序
std::cout << "[步骤 1] 正在生成一张 640x480 的彩色测试图片..." << std::endl;
cv::Mat canvas(480, 640, CV_8UC3, cv::Scalar(50, 50, 50)); // 深灰色背景
// 在画布上画一些彩色图形,让图片看起来有趣
// cv::circle(图像, 圆心, 半径, 颜色, 线宽) —— 画圆
// 圆心用 cv::Point(x, y) 表示,注意 x 是水平方向(列),y 是垂直方向(行)
// 线宽 = -1 表示填充整个圆(实心圆)
cv::circle(canvas, cv::Point(150, 120), 80, cv::Scalar(255, 0, 0), -1); // 蓝色实心圆(B=255)
cv::circle(canvas, cv::Point(320, 120), 80, cv::Scalar(0, 255, 0), -1); // 绿色实心圆(G=255)
cv::circle(canvas, cv::Point(490, 120), 80, cv::Scalar(0, 0, 255), -1); // 红色实心圆(R=255)
// cv::rectangle(图像, 左上角, 右下角, 颜色, 线宽) —— 画矩形
// 线宽 = 3 表示空心矩形,边框宽度为 3 像素
cv::rectangle(canvas, cv::Point(100, 280), cv::Point(540, 440),
cv::Scalar(0, 255, 255), 3); // 黄色空心矩形(B=0, G=255, R=255)
// cv::putText(图像, 文字, 起始位置, 字体, 字号, 颜色, 线宽) —— 写文字
cv::putText(canvas, "OpenCV Experiment 1", cv::Point(130, 350),
cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(255, 255, 255), 2); // 白色文字
// 将生成的图片保存为文件
// cv::imwrite(文件路径, 图像矩阵) —— 把内存中的图像写入磁盘
// 返回值是 bool 类型:true 表示保存成功,false 表示失败
// 文件格式由扩展名决定,这里 .jpg 表示用 JPEG 格式压缩保存
bool write_ok = cv::imwrite("test_image.jpg", canvas);
if (write_ok)
{
std::cout << " -> 测试图片已保存为 test_image.jpg" << std::endl;
}
else
{
std::cerr << " [错误] 保存测试图片失败!" << std::endl;
return -1; // 返回 -1 表示程序异常退出
}
// ==========================================
// 第二部分:读取图片
// ==========================================
std::cout << "[步骤 2] 正在读取 test_image.jpg..." << std::endl;
// cv::imread(文件路径, 读取模式) —— 从磁盘读取图像到内存
// 返回值是一个 cv::Mat 对象,包含了图像的像素数据
//
// 读取模式(第二个参数):
// cv::IMREAD_COLOR = 1 :始终读取为 3 通道彩色图(默认值)
// cv::IMREAD_GRAYSCALE = 0 :始终读取为单通道灰度图
// cv::IMREAD_UNCHANGED = -1 :保持原样(包括 Alpha 透明通道)
//
// 这里使用 cv::IMREAD_COLOR 确保读进来是彩色图像
cv::Mat image = cv::imread("test_image.jpg", cv::IMREAD_COLOR);
// 【极其重要】检查图像是否读取成功!
// image.empty() 返回 true 表示 Mat 对象中没有数据
// 常见失败原因:
// 1. 文件名写错了(大小写、拼写错误)
// 2. 文件不在程序运行的当前目录下
// 3. 文件损坏或格式不支持
// 养成好习惯:每次 imread 之后都要检查 empty()!
if (image.empty())
{
std::cerr << "[错误] 无法读取图像!请检查:" << std::endl;
std::cerr << " 1. test_image.jpg 是否在当前目录下?" << std::endl;
std::cerr << " 2. 文件名是否正确(注意大小写)?" << std::endl;
return -1; // 返回 -1 表示程序因错误退出
}
// 打印图像的基本信息
// image.rows —— 图像高度(行数,垂直像素数)
// image.cols —— 图像宽度(列数,水平像素数)
// image.channels() —— 通道数(彩色=3,灰度=1)
// image.type() —— 像素数据类型编号
std::cout << " -> 读取成功!图像信息:" << std::endl;
std::cout << " 宽度 (cols): " << image.cols << " 像素" << std::endl;
std::cout << " 高度 (rows): " << image.rows << " 像素" << std::endl;
std::cout << " 通道数: " << image.channels() << " (3=彩色, 1=灰度)" << std::endl;
// ==========================================
// 第三部分:保存读到的图片(验证读取无误)
// ==========================================
std::cout << "[步骤 3] 正在保存为 output.jpg..." << std::endl;
// 把读到的图像原封不动地保存为新文件
// 这样可以验证"读取 → 保存"整个流程是否正常
bool save_ok = cv::imwrite("output.jpg", image);
if (save_ok)
{
std::cout << " -> 图片已成功保存为 output.jpg" << std::endl;
std::cout << " -> 请在 Windows 资源管理器中打开该文件查看结果!" << std::endl;
}
else
{
std::cerr << " [错误] 保存图片失败!请检查磁盘空间和写入权限。" << std::endl;
return -1;
}
// ==========================================
// 总结
// ==========================================
std::cout << std::endl;
std::cout << "===== 实验 1 完成 =====" << std::endl;
std::cout << "你学会了:" << std::endl;
std::cout << " 1. cv::Mat 是 OpenCV 存储图像的数据类型" << std::endl;
std::cout << " 2. cv::imread() 从文件读取图像" << std::endl;
std::cout << " 3. cv::imwrite() 将图像写入文件" << std::endl;
std::cout << " 4. 读取后一定要检查 empty()" << std::endl;
// return 0 表示程序正常结束
return 0;
}