Files
vision-training-2026-opencvcpp/exp5_threshold/main.cpp

152 lines
7.3 KiB
C++
Raw Normal View History

/*
* ============================================================
* 实验 5:图像阈值化 (Thresholding)
* 知识点:cv::threshold
* ============================================================
*
* 【实验目标】
* 1. 理解"阈值化"是什么——把灰度图变成黑白图
* 2. 掌握 cv::threshold 函数的各种阈值类型
* 3. 理解阈值化在图像分割中的基础地位
*
* 【生活化比喻】
* 阈值化就像是"考试及格线":
* - 考试成绩 0~100 分
* - 学校规定 60 分及格:≥ 60 算"通过",< 60 算"不通过"
* - 这里的"60"就是阈值 (threshold)
*
* 应用到图像上:
* - 每个像素的灰度值 0~255(相当于 0~100 分)
* - 设定阈值 128(相当于 60 分)
* - 灰度 ≥ 128 的像素 → 变白色(255,相当于"通过")
* - 灰度 < 128 的像素 → 变黑色(0,相当于"不通过")
* - 整张图就只剩黑白两色了!
*
* 为什么需要这个操作?
* - 把"感兴趣的物体"从"背景"中分离出来
* - 为后续的形态学操作(实验 6)做准备
* - 很多算法(如轮廓检测)需要二值图像作为输入
*
* 【函数详解】
* cv::threshold(src, dst, thresh, maxval, type)
*
* src :输入图像(必须是单通道灰度图)
* dst :输出图像(和输入同样大小和类型)
* thresh :阈值(就是那个"及格线")
* maxval :最大值(当像素满足条件时,赋予它的新值,通常是 255)
* type :阈值类型(决定"怎么比较"和"比较后怎么赋值")
*
* OpenCV 支持的阈值类型:
* cv::THRESH_BINARY :>阈值→maxval,否则→0 (标准二值化)
* cv::THRESH_BINARY_INV :>阈值→0,否则→maxval (反二值化)
* cv::THRESH_TRUNC :>阈值→阈值,否则→原值不变 (截断)
* cv::THRESH_TOZERO :>阈值→原值不变,否则→0 (置零)
* cv::THRESH_TOZERO_INV :>阈值→0,否则→原值不变 (反置零)
*/
#include <opencv2/opencv.hpp>
#include <iostream>
int main()
{
// ==========================================
// 第一部分:生成一张灰度渐变图
// ==========================================
std::cout << "[步骤 1] 正在生成灰度渐变测试图..." << std::endl;
// 创建一张 300(高)× 512(宽)的灰度图
// CV_8UC1 表示:8位无符号整数、1通道(灰度)
cv::Mat gradient(300, 512, CV_8UC1);
// 用手工方式生成从左到右的灰度渐变
// 左边的像素灰度值为 0(全黑),右边为 255(全白),逐渐过渡
for (int row = 0; row < gradient.rows; row++)
{
for (int col = 0; col < gradient.cols; col++)
{
// 灰度值 = 列索引 × 255 / 511(比例映射)
// 这样 col=0 对应灰度 = 0,col=511 对应灰度 = 255
// gradient.at<uchar>(row, col) —— 访问/修改单个像素值
// .at<uchar> 中的 uchar = unsigned char = 8比特无符号整数
// 因为 CV_8UC1 的每个像素就是 uchar 类型
gradient.at<uchar>(row, col) = static_cast<uchar>(col * 255 / 511);
}
}
// 在渐变图上画几个标记,方便观察阈值效果
// 画一条垂直线标记阈值 128 的位置
cv::line(gradient, cv::Point(256, 0), cv::Point(256, 299),
cv::Scalar(255), 1); // 在 x=256 处画白线(灰度 128 的位置)
// 放一些文字
cv::putText(gradient, "0", cv::Point(5, 150),
cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(255));
cv::putText(gradient, "128", cv::Point(240, 150),
cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(255));
cv::putText(gradient, "255", cv::Point(470, 150),
cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0));
cv::imwrite("gradient.jpg", gradient);
std::cout << " -> 原始渐变图已保存为 gradient.jpg" << std::endl;
std::cout << " (从左到右灰度从 0 渐变到 255)" << std::endl;
// ==========================================
// 第二部分:演示各种阈值类型
// ==========================================
// 统一使用阈值 128(中间值),maxval = 255(纯白)
// --- 类型 1:THRESH_BINARY(标准二值化)---
// 逻辑:如果 像素值 > 128,则赋值为 255;否则赋值为 0
// 效果:左边一半(<128)全黑,右边一半(>128)全白
std::cout << "[步骤 2] THRESH_BINARY(标准二值化)..." << std::endl;
cv::Mat thresh_binary;
cv::threshold(gradient, thresh_binary, 128, 255, cv::THRESH_BINARY);
cv::imwrite("thresh_binary.jpg", thresh_binary);
std::cout << " -> 已保存为 thresh_binary.jpg" << std::endl;
// --- 类型 2:THRESH_BINARY_INV(反二值化)---
// 逻辑:如果 像素值 > 128,则赋值为 0;否则赋值为 255
// 效果:和上面完全相反——左边白,右边黑
std::cout << "[步骤 3] THRESH_BINARY_INV(反二值化)..." << std::endl;
cv::Mat thresh_binary_inv;
cv::threshold(gradient, thresh_binary_inv, 128, 255, cv::THRESH_BINARY_INV);
cv::imwrite("thresh_binary_inv.jpg", thresh_binary_inv);
std::cout << " -> 已保存为 thresh_binary_inv.jpg" << std::endl;
// --- 类型 3:THRESH_TRUNC(截断)---
// 逻辑:如果 像素值 > 128,则赋值为 128(被截断!);否则保持原值不变
// 效果:左边(<128)保持渐变,右边(>128)全部变成 128(一片灰)
std::cout << "[步骤 4] THRESH_TRUNC(截断)..." << std::endl;
cv::Mat thresh_trunc;
cv::threshold(gradient, thresh_trunc, 128, 255, cv::THRESH_TRUNC);
cv::imwrite("thresh_trunc.jpg", thresh_trunc);
std::cout << " -> 已保存为 thresh_trunc.jpg" << std::endl;
// --- 类型 4:THRESH_TOZERO(置零)---
// 逻辑:如果 像素值 > 128,则保持原值不变;否则赋值为 0(变黑)
// 效果:左边(<128)全黑,右边(>128)保持渐变
std::cout << "[步骤 5] THRESH_TOZERO(置零)..." << std::endl;
cv::Mat thresh_tozero;
cv::threshold(gradient, thresh_tozero, 128, 255, cv::THRESH_TOZERO);
cv::imwrite("thresh_tozero.jpg", thresh_tozero);
std::cout << " -> 已保存为 thresh_tozero.jpg" << std::endl;
// --- 类型 5:THRESH_TOZERO_INV(反置零)---
// 逻辑:如果 像素值 > 128,则赋值为 0(变黑);否则保持原值不变
// 效果:左边(<128)保持渐变,右边(>128)全黑
std::cout << "[步骤 6] THRESH_TOZERO_INV(反置零)..." << std::endl;
cv::Mat thresh_tozero_inv;
cv::threshold(gradient, thresh_tozero_inv, 128, 255, cv::THRESH_TOZERO_INV);
cv::imwrite("thresh_tozero_inv.jpg", thresh_tozero_inv);
std::cout << " -> 已保存为 thresh_tozero_inv.jpg" << std::endl;
// 总结
std::cout << std::endl;
std::cout << "===== 实验 5 完成 =====" << std::endl;
std::cout << "你学会了:" << std::endl;
std::cout << " 1. 阈值化就是把灰度图按某个界限分成两部分" << std::endl;
std::cout << " 2. 有 5 种阈值类型,最常用的是 THRESH_BINARY" << std::endl;
std::cout << " 3. 阈值化是图像分割的基础操作" << std::endl;
return 0;
}