OpenCV颜色直方图与区域分割实战:从色彩特征到图像语义分析

📅 2026/7/22 9:06:27 👁️ 阅读次数 📝 编程学习
OpenCV颜色直方图与区域分割实战:从色彩特征到图像语义分析

1. 项目概述:从像素到语义的视觉理解

在计算机视觉的日常开发中,我们常常会遇到一些看似简单却暗藏玄机的问题。比如,给你一张色彩斑斓的商品图,如何让程序自动识别出图中不同颜色的区域?或者,处理一张医学影像时,怎样快速将疑似病灶的异常颜色区域分割出来?这些问题背后,都离不开两个核心武器:颜色直方图和区域分割。颜色直方图是图像的“色彩指纹”,它能以一种统计的方式,精炼地描述图像中颜色的分布情况,告诉你这张图里是红色多还是蓝色多,颜色是集中还是分散。而区域分割,则是基于这个“指纹”或其他特征,将图像中具有相似属性的像素聚合成一个个有意义的“块”,比如天空、草地、建筑物。这个“C++ OpenCV颜色直方图与区域分割实战项目”,就是带你亲手搭建一个能够自动分析图像色彩构成并据此分割出不同区域的实用工具。它非常适合有一定C++基础,并希望踏入OpenCV实战大门的开发者。通过这个项目,你将不仅学会调用API,更能理解从颜色特征提取到图像语义分割的完整逻辑链条,掌握一套解决实际图像分析问题的通用方法论。

2. 核心思路与方案选型

2.1 为什么是颜色直方图+区域分割?

在图像处理中,特征提取和基于特征的分割是前后衔接的两个关键步骤。颜色,作为最直观、最稳定的视觉特征之一,常常是分割任务的起点。我选择颜色直方图作为特征,主要基于以下几点考量:

稳定性与计算效率:相比于SIFT、SURF等局部特征,颜色直方图计算速度快,对图像的旋转、缩放具有一定的不变性(特别是使用HSV色彩空间并忽略亮度分量V时)。对于颜色分布差异明显的区域,它能提供非常有效的区分度。

语义关联性强:在自然场景、商品图像中,物体常常与特定的颜色范围相关联(如蓝天、绿草、红色警示牌)。基于颜色的分割,其结果往往更贴近人类对物体的直观认知。

与OpenCV的完美契合:OpenCV提供了calcHist函数用于高效计算直方图,以及compareHist函数用于度量直方图间的相似性。这为我们后续的分割算法(如区域生长、聚类)提供了直接可用的特征相似性判据。

对于分割方法,本项目将重点演示两种经典且互补的思路:基于阈值的分割和基于聚类的分割。阈值分割简单直接,适合前景背景颜色对比度高的场景;而聚类分割(如K-Means)则更通用,能自动将图像颜色归类成若干主题色,进而实现分割。这两种方法覆盖了从规则到自适应的大部分应用场景。

2.2 技术栈与工具准备

  • 核心库OpenCV 4.x。这是计算机视觉的事实标准库。建议使用4.5及以上版本,以获得更好的性能和更统一的C++ API。
  • 开发环境
    • 编译器:支持C++11及以上标准的编译器,如GCC (MinGW-w64)、MSVC (Visual Studio 2022)。
    • IDE/编辑器Visual Studio CodeVisual Studio。VSCode轻量灵活,配合CMake和C/C++插件能获得极佳的开发体验;Visual Studio则提供了开箱即用的强大调试功能。网络热词中频繁出现的“vscode配置c/c++环境”、“visual studio 2022 c++”正反映了这两种主流选择。
    • 构建工具CMake。这是管理跨平台OpenCV项目依赖和构建过程的首选。
  • 依赖管理:确保系统已安装OpenCV。可以通过源码编译安装,或使用包管理器(如vcpkg、apt-get on Linux)。网上大量的“opencv安装教程”、“opencv配置”文章可供参考,核心是配置好头文件路径和库文件链接。

注意:安装OpenCV时,务必确认Python绑定(如opencv-python)和C++库是两回事。本项目使用C++接口,需要的是编译好的C++库(.lib,.dll.so,.a文件)。

3. 颜色直方图:构建图像的色彩DNA

3.1 色彩空间的选择:RGB不是万能的

计算直方图的第一步是选择色彩空间。虽然图像通常以RGB格式存储,但直接对RGB三个通道分别计算直方图并进行比较,效果往往不佳。因为RGB空间对亮度非常敏感,同一颜色在明暗不同时,其RGB值差异巨大。

因此,我们更倾向于使用HSV色彩空间

  • H(色调):表示颜色类型,如红、绿、蓝。这是颜色最本质的属性。
  • S(饱和度):表示颜色的纯度,从灰色到纯色。
  • V(亮度):表示颜色的明暗程度。

在分割任务中,我们通常更关心“是什么颜色”(H)和“有多鲜艳”(S),而对“有多亮”(V)相对不敏感。因此,一个常见的做法是:将图像从BGR(OpenCV默认读取格式)转换到HSV,然后仅使用H和S通道来计算2D直方图。这样可以有效减少光照变化对颜色特征的影响。

#include <opencv2/opencv.hpp> #include <vector> int main() { // 读取图像 cv::Mat image_bgr = cv::imread("your_image.jpg"); if(image_bgr.empty()) { std::cerr << "Could not open or find the image!" << std::endl; return -1; } // 转换到HSV色彩空间 cv::Mat image_hsv; cv::cvtColor(image_bgr, image_hsv, cv::COLOR_BGR2HSV); // 分离HSV通道 std::vector<cv::Mat> hsv_planes; cv::split(image_hsv, hsv_planes); cv::Mat hue = hsv_planes[0]; // H通道 cv::Mat saturation = hsv_planes[1]; // S通道 // 准备用于计算2D直方图的通道数组 int channels[] = {0, 1}; // 使用第0(H)和第1(S)通道 int histSize[] = {50, 60}; // H维度分为50个bin,S维度分为60个bin float h_ranges[] = {0, 180}; // OpenCV中H范围是0-179 float s_ranges[] = {0, 256}; // S范围是0-255 const float* ranges[] = {h_ranges, s_ranges}; // 计算2D直方图 cv::Mat hist_hs; cv::calcHist(&image_hsv, 1, channels, cv::Mat(), // 不使用掩码 hist_hs, 2, histSize, ranges, true, // 直方图是均匀的 false // 不累加 ); // 可选:归一化直方图,便于后续比较和可视化 cv::normalize(hist_hs, hist_hs, 0, 1, cv::NORM_MINMAX, -1, cv::Mat()); // 此时,hist_hs就是一个50x60的二维矩阵,代表了图像在H-S平面上的颜色分布 // 后续可以用它来比较不同图像或图像区域的相似性 return 0; }

参数选择的考量

  • histSize[]: 直方图“箱子”(bin)的数量。这决定了颜色特征的粒度。数量太少(如10),会丢失细节,不同颜色可能被混在一起;数量太多(如256),则直方图过于稀疏,对噪声敏感,且计算量增大。{50, 60}是一个经验性的折中值,在实际项目中可能需要根据具体图像调整。
  • ranges[]: H通道的范围是[0, 180),这是因为在OpenCV中,为了用一个字节(0-255)表示H,将0-360度的色环压缩了一半。S和V通道范围是[0, 256)

3.2 直方图比较:量化颜色相似度

计算出直方图后,如何判断两幅图像或两个区域的颜色是否相似?这就需要直方图比较方法。OpenCV的compareHist函数提供了多种度量方式:

  1. 相关性比较 (cv::HISTCMP_CORREL):值域[-1, 1],越接近1表示相关性越强,颜色分布越相似。这是最常用的方法之一,对直方图的整体形状匹配较好。
  2. 卡方比较 (cv::HISTCMP_CHISQR):值域[0, inf),越接近0表示越相似。它对直方图bin值的差异比较敏感。
  3. 相交法 (cv::HISTCMP_INTERSECT):计算两个直方图在每个bin上的最小值之和。对于归一化后的直方图,结果越接近1越相似。计算简单快速。
  4. 巴氏距离 (cv::HISTCMP_BHATTACHARYYA):值域[0, 1],0表示完全匹配。它测量两个概率分布之间的相似性,在统计学上意义明确。
// 假设我们有两个区域的直方图 hist1 和 hist2 double correlation = cv::compareHist(hist1, hist2, cv::HISTCMP_CORREL); double chisqr = cv::compareHist(hist1, hist2, cv::HISTCMP_CHISQR); double intersection = cv::compareHist(hist1, hist2, cv::HISTCMP_INTERSECT); double bhattacharyya = cv::compareHist(hist1, hist2, cv::HISTCMP_BHATTACHARYYA); std::cout << "Correlation: " << correlation << std::endl; std::cout << "Chi-Square: " << chisqr << std::endl; // 对于相交法,如果直方图未归一化,结果可能很大,通常也需要先归一化。 std::cout << "Intersection: " << intersection << std::endl; std::cout << "Bhattacharyya: " << bhattacharyya << std::endl;

选择建议:对于基于颜色的区域相似性判断,相关性比较 (HISTCMP_CORREL)通常是首选,因为它对整体分布匹配好,且结果在固定范围内,易于设定阈值。例如,可以设定当相关性大于0.8时,认为两个区域颜色相似。

4. 基于颜色直方图的区域分割实战

有了颜色特征和相似性度量,我们就可以进行分割了。这里介绍两种实战方法。

4.1 方法一:阈值分割与颜色范围提取

这种方法适用于目标颜色范围相对明确、集中的情况。其核心思想是:在HSV空间定义你想要提取的颜色的上下阈值,然后利用cv::inRange函数生成一个二值掩码。

// 继续使用上面转换得到的 image_hsv // 定义想提取的颜色的HSV范围,例如:提取绿色区域 cv::Scalar lower_green = cv::Scalar(35, 43, 46); // HSV下限: H=35, S=43, V=46 cv::Scalar upper_green = cv::Scalar(77, 255, 255); // HSV上限: H=77, S=255, V=255 cv::Mat mask_green; cv::inRange(image_hsv, lower_green, upper_green, mask_green); // mask_green是一个单通道二值图,白色(255)区域表示落在该颜色范围内的像素 // 使用掩码提取原图中的绿色区域 cv::Mat green_part; image_bgr.copyTo(green_part, mask_green); // 仅复制mask中为白色的像素位置 // 显示结果 cv::imshow("Original", image_bgr); cv::imshow("Green Mask", mask_green); cv::imshow("Green Part", green_part); cv::waitKey(0);

如何确定HSV阈值?这是该方法的关键和难点。一个非常实用的技巧是使用OpenCV的GUI功能动态调整:

  1. 创建一个带轨迹条的窗口。
  2. 读取图像并转换到HSV。
  3. 为H、S、V的上下限分别创建轨迹条。
  4. 在回调函数中,根据当前轨迹条的值实时调用cv::inRange并显示掩码。
  5. 手动拖动轨迹条,直到掩码能较好地覆盖目标颜色区域。此时轨迹条的值就是理想的阈值。

实操心得:cv::inRange处理的是每个通道独立的范围,定义阈值时一定要考虑HSV三个通道。对于光照不均的图像,固定阈值可能效果不好,可能需要结合自适应阈值或更复杂的方法。另外,OpenCV中H通道的范围是0-179,但在很多颜色选取工具(如Photoshop)中是0-360,使用时需要除以2进行转换,这是一个常见的坑。

4.2 方法二:K-Means聚类分割

当图像颜色复杂,没有明确阈值时,聚类方法是更好的选择。K-Means可以将图像中所有像素的颜色(例如在HSV或Lab空间)聚类成K个主要的颜色类别。

// 准备工作:将图像数据转换为适合K-Means的格式 cv::Mat image_lab; cv::cvtColor(image_bgr, image_lab, cv::COLOR_BGR2Lab); // Lab空间比RGB更符合人眼感知,聚类效果常更好 // 将图像矩阵从3通道的 (rows, cols, 3) 重塑为2维的 (rows*cols, 3) // 每一行代表一个像素的3个颜色通道值 cv::Mat pixel_data = image_lab.reshape(1, image_lab.total()); // 通道数变为1,行数为像素总数 pixel_data.convertTo(pixel_data, CV_32F); // K-Means需要浮点数据 // 定义K-Means参数 int k = 4; // 假设我们希望将图像颜色分为4类 cv::Mat labels; // 每个像素所属的类别标签 (0, 1, 2, 3...) cv::Mat centers; // 每个类别的中心颜色值 int attempts = 5; // 尝试不同初始中心的次数,取最好结果 cv::TermCriteria criteria(cv::TermCriteria::EPS + cv::TermCriteria::MAX_ITER, 100, 0.01); // 执行K-Means聚类 cv::kmeans(pixel_data, k, labels, criteria, attempts, cv::KMEANS_PP_CENTERS, centers); // 将聚类结果可视化:用聚类中心颜色替换每个像素的颜色 cv::Mat segmented_image(image_lab.size(), image_lab.type()); for (int i = 0; i < pixel_data.rows; ++i) { int cluster_idx = labels.at<int>(i); // 获取该像素在原始图像中的位置 int row = i / image_lab.cols; int col = i % image_lab.cols; // 将该像素的颜色设置为它所属簇的中心颜色 // centers是CV_32F类型,需要转换 cv::Vec3f center_color = centers.at<cv::Vec3f>(cluster_idx); segmented_image.at<cv::Vec3b>(row, col) = cv::Vec3b(center_color[0], center_color[1], center_color[2]); } // 转换回BGR空间显示 cv::Mat result_bgr; cv::cvtColor(segmented_image, result_bgr, cv::COLOR_Lab2BGR); cv::imshow("Original", image_bgr); cv::imshow("K-Means Segmented (K=4)", result_bgr); cv::waitKey(0);

K值的选择: 这是K-Means的核心超参数。K太小,分割过于粗糙,不同颜色的物体会被合并;K太大,会产生过度分割,同一物体被分成多块。没有绝对的最优值,需要根据图像内容和应用目标来定。可以尝试使用“肘部法则”(绘制不同K值下的误差平方和曲线,选择拐点)或基于先验知识(如已知图像中有几种主要颜色)来确定。

聚类后的处理: 直接可视化聚类中心颜色得到的是“颜色量化”效果。要得到真正的分割掩码(每个区域一个独立的二值图),需要遍历labels矩阵,为每个簇生成一个掩码。

std::vector<cv::Mat> cluster_masks(k); for (int i = 0; i < k; ++i) { cluster_masks[i] = cv::Mat::zeros(image_bgr.size(), CV_8UC1); } // 再次遍历所有像素,根据label设置对应掩码 for (int i = 0; i < pixel_data.rows; ++i) { int cluster_idx = labels.at<int>(i); int row = i / image_lab.cols; int col = i % image_lab.cols; cluster_masks[cluster_idx].at<uchar>(row, col) = 255; } // 现在 cluster_masks[0], cluster_masks[1]... 就是各个颜色簇的分割掩码 // 可以对这些掩码进行后续操作,如寻找轮廓、计算面积等 for (int i = 0; i < k; ++i) { cv::imshow("Cluster Mask " + std::to_string(i), cluster_masks[i]); } cv::waitKey(0);

5. 项目集成与性能优化实战

将上述模块组合起来,我们可以构建一个完整的颜色分析与分割流水线。假设我们的目标是:输入一张图像,自动识别出其中占主导地位的几种颜色,并将这些颜色对应的区域分割出来。

5.1 完整流程设计

  1. 图像预处理:读取图像,统一缩放至固定大小(如800x600)以加速处理,进行高斯模糊减少噪声。
  2. 颜色空间转换与量化:转换到Lab色彩空间,使用K-Means进行颜色量化,得到K个主色和每个像素的标签。
  3. 主色分析与筛选:计算每个颜色簇的像素数量,排序,可以筛选出像素占比超过一定阈值(如5%)的主要颜色。
  4. 生成分割掩码与后处理:对筛选出的每个主色,根据其标签生成二值掩码。对这些掩码进行形态学操作(如开运算去除小噪点,闭运算填充小孔洞)。
  5. 提取与标注区域:对后处理后的掩码寻找轮廓,在原图上绘制轮廓或填充颜色进行可视化。
// 步骤1: 预处理 cv::Mat input = cv::imread("complex_scene.jpg"); cv::Mat resized; cv::resize(input, resized, cv::Size(800, 600)); cv::Mat blurred; cv::GaussianBlur(resized, blurred, cv::Size(5,5), 0); // 步骤2: 颜色量化 (K-Means) cv::Mat lab; cv::cvtColor(blurred, lab, cv::COLOR_BGR2Lab); cv::Mat pixels = lab.reshape(1, lab.total()); pixels.convertTo(pixels, CV_32F); int k = 6; // 假设我们找6种主色 cv::Mat labels, centers; cv::kmeans(pixels, k, labels, cv::TermCriteria(cv::TermCriteria::EPS+cv::TermCriteria::MAX_ITER, 10, 1.0), 3, cv::KMEANS_PP_CENTERS, centers); // 步骤3: 分析主色占比 std::vector<int> pixel_count(k, 0); for(int i=0; i<labels.rows; ++i) { int idx = labels.at<int>(i); pixel_count[idx]++; } int total_pixels = labels.rows; std::vector<std::pair<int, int>> cluster_pairs; // (簇索引, 像素数) for(int i=0; i<k; ++i) { cluster_pairs.push_back({i, pixel_count[i]}); } // 按像素数从大到小排序 std::sort(cluster_pairs.begin(), cluster_pairs.end(), [](const std::pair<int,int>& a, const std::pair<int,int>& b){ return a.second > b.second; }); // 步骤4 & 5: 为每个主要簇生成掩码、后处理并绘制 float dominance_threshold = 0.05f; // 占比5%以上才认为是主要区域 cv::Mat visualization = resized.clone(); cv::RNG rng(12345); // 随机数生成器,用于给不同区域上色 for(const auto& cp : cluster_pairs) { int cluster_idx = cp.first; float ratio = static_cast<float>(cp.second) / total_pixels; if(ratio < dominance_threshold) break; // 占比太小,忽略 // 生成该簇的掩码 cv::Mat mask = cv::Mat::zeros(resized.size(), CV_8UC1); for(int i=0; i<labels.rows; ++i) { if(labels.at<int>(i) == cluster_idx) { int row = i / resized.cols; int col = i % resized.cols; mask.at<uchar>(row, col) = 255; } } // 后处理:形态学开闭运算 cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5,5)); cv::morphologyEx(mask, mask, cv::MORPH_OPEN, kernel); // 开运算去噪 cv::morphologyEx(mask, mask, cv::MORPH_CLOSE, kernel); // 闭运算填洞 // 寻找轮廓 std::vector<std::vector<cv::Point>> contours; cv::findContours(mask, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); // 为这个区域生成一个随机颜色用于可视化 cv::Scalar color = cv::Scalar(rng.uniform(0,256), rng.uniform(0,256), rng.uniform(0,256)); // 绘制轮廓和填充(半透明) for(const auto& contour : contours) { if(cv::contourArea(contour) > 100) { // 忽略太小的轮廓 cv::drawContours(visualization, std::vector<std::vector<cv::Point>>{contour}, -1, color, 2); cv::Mat overlay = visualization.clone(); cv::fillPoly(overlay, std::vector<std::vector<cv::Point>>{contour}, color); cv::addWeighted(overlay, 0.3, visualization, 0.7, 0, visualization); // 半透明填充 } } // 在图像上标注该区域的颜色占比 std::string label = "C" + std::to_string(cluster_idx) + ": " + std::to_string(int(ratio*100)) + "%"; cv::putText(visualization, label, cv::Point(10, 30 + cluster_idx*30), cv::FONT_HERSHEY_SIMPLEX, 0.7, color, 2); } cv::imshow("Dominant Color Segmentation", visualization); cv::waitKey(0);

5.2 性能优化与内存管理

当处理高分辨率图像或视频流时,性能至关重要。

  1. 降采样处理:对于分割任务,很多时候不需要原图的全分辨率。可以先将图像缩放至一个较小的尺寸(如宽度固定为640像素)进行处理,得到分割掩码后再上采样回原图尺寸。这能极大减少K-Means和形态学运算的计算量。
  2. 优化K-Meansattempts参数不宜过大,3-5次通常足够。criteria中的最大迭代次数(MAX_ITER)和精度(EPS)也可以根据需求调整,平衡速度与精度。
  3. 使用cv::Mat::reshape和指针访问:如上面代码所示,使用reshape将3D像素矩阵转换为2D特征矩阵是标准做法。在遍历像素填充掩码时,如果图像很大,使用指针(ptr)访问会比at<>方法更快。
  4. 并行化:OpenCV 4.x的许多函数内部已使用并行优化(如Intel TBB, OpenMP)。确保在编译OpenCV时开启了这些选项。对于自己写的循环,如果逻辑简单且独立,可以考虑使用OpenCV的cv::parallel_for_进行并行化。

6. 常见问题、调试技巧与扩展方向

6.1 实战中遇到的典型问题与解决思路

  1. OpenCV imread读取为空

    • 问题:最经典的问题,cv::imread返回空的cv::Mat
    • 排查
      • 检查文件路径是否正确(绝对路径或相对路径)。
      • 检查文件是否存在,以及程序是否有读取权限。
      • 检查文件格式是否被OpenCV支持(jpg, png, bmp等)。
    • 解决:始终在imread后添加空值检查。使用cv::imshowstd::cout << image.size()快速验证。
  2. K-Means分割结果不稳定或很差

    • 问题:每次运行分割区域都不一样,或分割结果毫无道理。
    • 排查
      • 初始中心:K-Means对初始中心敏感。确保使用了cv::KMEANS_PP_CENTERS(K-Means++)来生成更好的初始中心,而不是随机初始化。
      • 数据未转换:确认输入K-Means的像素数据已经转换为CV_32F(浮点)类型。
      • K值不当:K值可能不适合当前图像。尝试不同的K值,或先用直方图观察颜色分布的大致峰数量。
      • 色彩空间:在RGB空间聚类效果可能不如Lab或HSV。尝试转换色彩空间。
    • 解决:固定随机种子(cv::RNG状态)以确保结果可复现,用于调试。系统性地尝试不同参数和色彩空间。
  3. 分割掩码边缘粗糙或有噪声

    • 问题:分割出来的区域边界锯齿严重,或者内部有很多小孔洞、孤立白点。
    • 排查:这是二值图像的常见问题。
    • 解决:形态学操作是标准后处理手段。
      • 开运算 (Opening):先腐蚀后膨胀。能有效消除小的白色噪点(前景噪声)。
      • 闭运算 (Closing):先膨胀后腐蚀。能填充小的黑色孔洞(背景噪声)。
      • 内核大小和形状需要根据噪声大小调整。圆形或椭圆内核通常比矩形内核能更好地保持原有形状。
  4. 处理速度慢

    • 问题:处理一张大图需要好几秒,无法满足实时性要求。
    • 排查
      • 最大的瓶颈通常是K-Means和全像素遍历。
      • 检查是否在循环中频繁创建临时cv::Mat对象。
    • 解决
      • 降采样:如5.2节所述。
      • 减少K值和迭代次数:在可接受的质量损失范围内调整。
      • 使用更快的特征:如果颜色直方图足够,可以不必对每个像素聚类,而是先计算图像子块的直方图进行粗分割。

6.2 调试与可视化技巧

  • 中间结果可视化:不要只盯着最终输出。把每一步的结果(如HSV各个通道图、计算出的掩码、形态学处理前后的对比)都用cv::imshow显示出来。这是定位问题最直接的方法。
  • 使用cv::Mat::channels()cv::Mat::type():当程序崩溃在at<>访问时,很可能是通道数或数据类型不匹配。在关键步骤后打印这些信息。
  • 直方图可视化:虽然OpenCV没有直接绘制2D直方图的函数,但可以通过计算1D直方图或使用第三方库来可视化,帮助理解颜色分布。

6.3 项目扩展方向

这个基础项目可以沿多个方向深化,构建更强大的应用:

  1. 结合纹理特征:仅凭颜色有时无法区分纹理不同但颜色相近的物体(如蓝天和蓝色衬衫)。可以加入LBP(局部二值模式)、Haralick纹理等特征,与颜色直方图结合形成多特征描述子。
  2. 交互式分割工具:实现一个简单的GUI(可以用OpenCV的HighGUI,或Qt),允许用户用画笔标记前景和背景,然后利用直方图比较或GraphCut/GrabCut算法进行精细分割。
  3. 应用于视频流:将处理流程封装成函数,对摄像头或视频文件的每一帧进行处理。需要考虑帧间连续性,可以使用上一帧的分割结果来初始化当前帧的K-Means中心,以提高速度和稳定性。
  4. 集成深度学习:将本项目作为预处理或后处理环节。例如,用深度学习模型进行初步分割,然后用颜色直方图对分割结果进行校验或细化;或者反过来,用颜色分割的结果作为ROI(感兴趣区域),缩小深度学习模型需要处理的区域。

这个项目就像打开计算机视觉大门的一把钥匙,颜色直方图和区域分割是其中最基础也最实用的两块拼图。把它们吃透,再面对复杂的图像分析任务时,你心里就有了一张清晰的路线图。