当前位置: 首页 > backend >正文

OpenCV提取图像中的暗斑/亮斑

基于opencv-4.11.0实现

包括滑动窗口动态阈值 + 阈值范围 + 开操作 + 闭操作 + 连通域提取和分析

关键代码如下

cv::Mat mask;
cv::Mat cvimage(image->Height, image->Width, CV_8UC1, image->Data);
Boundingbox2D box = RegionToMat(region, mask);
cv::Rect roi_rect = cv::Rect(box.MinX, box.MinY, box.Width(), box.Height());
cv::Mat src = cvimage(roi_rect).clone();
mask = mask(roi_rect).clone();cv::Mat mask_low_high;
cv::Mat mask_adj_thresh;
adaptiveThreshold(src, mask_adj_thresh, windX, windY, (minThreshold < 0));
if (minThreshold < 0)
{inRange(src, cv::Scalar(255 + minThreshold), cv::Scalar(255 + maxThreshold), mask_low_high);//inRange(src, cv::Scalar(-maxThreshold), cv::Scalar(-minThreshold), mask_low_high);
}
else
{inRange(src, cv::Scalar(minThreshold), cv::Scalar(maxThreshold), mask_low_high);
}
cv::Mat intersection;
cv::bitwise_and(mask_low_high, mask_adj_thresh, intersection);
cv::bitwise_and(mask, intersection, mask);// 定义结构元素(核)
cv::Mat open_kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(2 * openX - 1, 2 * openY - 1));
cv::Mat close_kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(2 * closeX - 1, 2 * closeY - 1));// 开运算(先腐蚀后膨胀)
cv::Mat opened;
cv::morphologyEx(mask, opened, cv::MORPH_OPEN, open_kernel);// 闭运算(先膨胀后腐蚀)
cv::Mat closed;
cv::morphologyEx(opened, closed, cv::MORPH_CLOSE, close_kernel);// 连通域分析
cv::Mat labels, stats, centroids;
int num_objects = connectedComponentsWithStats(closed, labels, stats, centroids);// 使用map存储每个连通域的所有像素位置
std::map<int, std::vector<cv::Point>> component_pixels;// 遍历labels矩阵,收集每个连通域的像素位置
for (int y = 0; y < labels.rows; y++) {for (int x = 0; x < labels.cols; x++) {int label = labels.at<int>(y, x);if (label > 0) {  // 忽略背景component_pixels[label].emplace_back(x, y);}}
}speckleRegionsCount = 0;
cv::Mat final = cv::Mat::zeros(closed.size(), CV_8UC1);
for (auto component : component_pixels)
{if (component.second.size() > minArea){speckleRegionsCount++;for (int i = 0;i < component.second.size();i++){final.at<uchar>(component.second[i]) = 255;}}
}
// Otsu 阈值算法实现
int otsuThreshold(const cv::Mat& image) 
{// 计算灰度直方图int hist[256] = { 0 };for (int y = 0; y < image.rows; y++) {for (int x = 0; x < image.cols; x++) {hist[image.at<uchar>(y, x)]++;}}int totalPixels = image.rows * image.cols;float sum = 0;for (int i = 0; i < 256; i++) {sum += i * hist[i];}float sumB = 0;      // 背景像素的加权和int wB = 0;          // 背景像素数量int wF = 0;          // 前景像素数量float maxVariance = 0;int threshold = 0;for (int t = 0; t < 256; t++) {wB += hist[t];              // 背景像素数量累积if (wB == 0) continue;      // 避免除以 0wF = totalPixels - wB;       // 前景像素数量if (wF == 0) break;          // 所有像素已分配sumB += t * hist[t];         // 背景像素的加权和float meanB = sumB / wB;     // 背景均值float meanF = (sum - sumB) / wF; // 前景均值// 计算类间方差float variance = (float)wB * (float)wF * (meanB - meanF) * (meanB - meanF);// 更新最佳阈值if (variance > maxVariance) {maxVariance = variance;threshold = t;}}return threshold;
}// 动态阈值二值化实现
void adaptiveThreshold(const cv::Mat& inputImage,cv::Mat& outputImage,int windX, int windY,bool detectDark = false) 
{int rows = inputImage.rows;int cols = inputImage.cols;// 确保输出图像大小一致outputImage = cv::Mat::zeros(inputImage.size(), CV_8UC1);// 遍历每个像素
#pragma omp parallel forfor (int y = 0; y < rows; ++y) {const uchar* prow_data = inputImage.ptr<uchar>(y);uchar* pout = outputImage.ptr<uchar>(y);for (int x = 0; x < cols; ++x){// 计算局部区域的边界int halfSizeX = windX / 2;int halfSizeY = windY / 2;int startX = (std::max)(0, x - halfSizeX);int endX = (std::min)(cols - 1, x + halfSizeX);int startY = (std::max)(0, y - halfSizeY);int endY = (std::min)(rows - 1, y + halfSizeY);auto thresh = otsuThreshold(inputImage(cv::Rect(startX, startY, endX - startX + 1, endY - startY + 1)));// 根据动态阈值进行二值化if ((prow_data[x] > thresh && !detectDark) || (prow_data[x] < static_cast<uchar>(thresh * 0.8) && detectDark)){pout[x] = 255; // 前景}}}}

http://www.xdnf.cn/news/4457.html

相关文章:

  • IvorySQL 再次走进北京大学研究生开源公选课
  • onenet连接微信小程序(mqtt协议)
  • 【国产化】在银河麒麟ARM环境下离线安装docker
  • Spring 如何解决循环依赖问题?
  • JavaScript性能优化:从青铜到王者的进阶之路
  • 从人体姿态到机械臂轨迹:基于深度学习的Kinova远程操控系统架构解析
  • Kubernetes(k8s)学习笔记(九)--搭建多租户系统
  • QMK键盘固件配置详解
  • 2025.05.07-华为机考第三题300分
  • DIFY教程第四弹:通过工作流来创建一个SQL语句的执行器
  • 【计算机基础】任意进制转换方法详解
  • 资产管理系统对比评测:从传统模式到 AI 驱动的变革
  • 引用的使用
  • [Es_1] 介绍 | 特点 | 图算法 | Trie | FST
  • 【C/C++】errno/strerror 和 GetLastError()/FormatMessage 的区别
  • 模拟设计中如何减小失配
  • 4.系统定时器基本定时器
  • 操作系统——第四章(文件的物理结构以及与逻辑结构的对比)
  • Redis相关命令详解与原理
  • 【Agent】使用 Python 结合 OpenAI 的 API 实现一个支持 Function Call 的程序,修改本机的 txt 文件
  • 如何检查 Watchtower 是否正常工作及更新未生效的排查方法【日常排错】
  • 探寻程序开发的个人密码
  • excel 批量导出图片并指定命名
  • Excel点击单元格内容消失
  • 龙虎榜——20250507
  • LVGL -meter的应用
  • phpstudy升级新版apache
  • 如何在金仓数据库KingbaseES中新建一个数据库?新建一个表?给表添加一个字段?
  • 【PostgreSQL数据分析实战:从数据清洗到可视化全流程】8.1 基础图表绘制(折线图/柱状图/散点图)
  • 把本地的文件拷贝到wsl的文件夹下或者 wsl读取本地的文件