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

基于opencv-4.11.0实现

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

关键代码如下

cpp 复制代码
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;
        }
    }
}
cpp 复制代码
// 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;      // 避免除以 0

		wF = 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 for
		for (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; // 前景
				}
			}
		}
	}
相关推荐
新知图书12 分钟前
使用OpenCV的VideoCapture播放视频文件示例
opencv·视频
萧霍之20 分钟前
基于onnxruntime结合PyQt快速搭建视觉原型Demo
pytorch·python·yolo·计算机视觉
nenchoumi31191 小时前
LLM 论文精读(四)LLM Post-Training: A Deep Dive into Reasoning Large Language Models
人工智能·语言模型·自然语言处理
鸿蒙布道师1 小时前
英伟达开源Llama-Nemotron系列模型:14万H100小时训练细节全解析
深度学习·神经网络·opencv·机器学习·自然语言处理·数据挖掘·llama
Qdgr_2 小时前
电厂数据库未来趋势:时序数据库 + AI 驱动的自优化系统
数据库·人工智能·时序数据库
知舟不叙2 小时前
基于OpenCV的人脸识别:LBPH算法
人工智能·opencv·人脸检测·lbph算法
乌恩大侠2 小时前
【东枫科技】使用LabVIEW进行NVIDIA CUDA GPU 开发
人工智能·科技·labview·nvidia·usrp
Silence4Allen3 小时前
RagFlow 完全指南(一):从零搭建开源大模型应用平台(Ollama、VLLM本地模型接入实战)
人工智能·大模型·rag·ragflow
music&movie3 小时前
手写系列——transformer网络完成加法和字符转译任务
网络·人工智能·transformer
白熊1884 小时前
【计算机视觉】基于Python的相机标定项目Camera-Calibration深度解析
python·数码相机·计算机视觉