基本思路为;
- 转灰度图,计算梯度,并归一化到0~255;
- 使用Otsu方法,对归一化的梯度图像进行二值化处理(处理后,边缘为白色亮线,其余为黑色);
- 获取二值图像非零像素最小包围盒Roi;
- 统计Roi之外区域像素平均RGB值,作为背景RGB值;
- 原始图像转HSV彩色,并抠取Roi区域图像;
- 对抠取的HSV彩色图像进行Kmeans聚类,k=2;
- 对聚类的结果分别统计其RGB均值,判定与背景RGB值相近的为背景区域(第4步结果),否则为前景区域;
- 求前景区域HSV值均值;
前提条件:背景较为单一的场景,没有梯度变化明显的纹理特征。
实现代码如下:
#include <iostream>
#include <filesystem>
#include<fstream>
#include <opencv2/opencv.hpp>
namespace fs = std::filesystem;
cv::Rect findMinBoundingPoly(cv::Mat& binaryImage);
void extractEdge(cv::Mat& image, cv::Mat &edge)
{
cv::Mat tmp = image.clone();
if (image.channels() == 3) {
cv::cvtColor(image, tmp, cv::COLOR_BGR2GRAY);
}
// 计算图像的梯度幅值
cv::Mat gradX, gradY, gradMag;
cv::Sobel(tmp, gradX, CV_32F, 1, 0); // 水平方向梯度
cv::Sobel(tmp, gradY, CV_32F, 0, 1); // 垂直方向梯度
cv::magnitude(gradX, gradY, gradMag); // 梯度幅值
// 归一化梯度幅值到 0-255 范围
cv::Mat normalizedGradMag;
cv::normalize(gradMag, normalizedGradMag, 0, 255, cv::NORM_MINMAX, CV_8U);
// 自适应二值化
//cv::Mat binaryImage;
double otsuThreshold = threshold(
normalizedGradMag, // 输入图像
edge, // 输出图像
0, // 阈值(Otsu 二值化时设