目录
- 一、算法原理
- 1、算法概述
- 2、参考文献
- 二、代码实现
- 三、结果展示
OpenCV——图像分块局部阈值二值化由CSDN点云侠原创,爬虫自重。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。
一、算法原理
1、算法概述
针对目前局部阈值二值化结果存在目标虚假或断裂的缺陷,提出了一种基于图像分块的局部阈值二值化方法。首先,将图像分成若干子块并分析每个子块像素灰度变化情况; 接着,取一定大小的局部窗口在图像中移动,比较该局部窗口内与包含窗口自身且比窗口更大区域内的像素灰度变化情况,更大区域由窗口模板当前覆盖的所有子块组成,以此判断窗口内是否为灰度变化平坦( 或剧烈) 区域; 最后,根据不同的区域,给出具体的二值化方案。
2、参考文献
[1] 张洁玉. 基于图像分块的局部阈值二值化方法 [J]. 计算机应用, 2017, 37 (03): 827-831.
二、代码实现
ImageBinarization.h
#pragma once
#include <vector>
#include <opencv2/opencv.hpp>class ImageBinarization
{
private:// 参数cv::Mat m_src; // 输入数据cv::Mat m_dst; // 输出数据cv::Size m_blockSize; // 分块图像的尺寸cv::Size m_moveWndSize; // 移动窗口的尺寸int m_segRow = 0; // 图像分块的行数int m_segCol = 0; // 图像分块的列数double m_alpha = 0.5; // 平坦区域与剧烈区域的区分阈值double m_beta = 0.5; // 剧烈区域,像素中心点与阈值O的距离std::vector<std::tuple<cv::Rect, double, double>> m_imgProperty;// 内部函数double getMatMAD(const cv::Mat& wndImg); // 计算平均绝对偏差std::vector<int>getGridIndices(const int windCenterInSegRow, const int windCenterInSegCol);void imageSegment();// 图像分块void segBlockBin(); // 分块二值化
public:ImageBinarization() {}~ImageBinarization() {}void setInputImage(const cv::Mat& src);void setBlockSize(const cv::Size& blockSize); // 设置分块图像的尺寸void setMoveWindSize(const cv::Size& moveWndSize); // 设置移动窗口的尺寸void setAlphaValue(double alpha); // 设置区分阈值void setBetaValue(double beta); // 设置像素中心点与阈值的距离void blockBinResult(cv::Mat& dst); // 分块二值化结果输出
};
ImageBinarization.cpp
#include<cstdlib>#include"ImageBinarization.h"// 参数设置
// 输入图像
void ImageBinarization::setInputImage(const cv::Mat& src)
{m_src = src;
}
// 分块尺寸
void ImageBinarization::setBlockSize(const cv::Size& blockSize)
{m_blockSize = blockSize;
}
// 滑动窗口尺寸
void ImageBinarization::setMoveWindSize(const cv::Size& moveWndSize)
{m_moveWndSize = moveWndSize;
}
// 平滑与剧烈区域分割阈值
void ImageBinarization::setAlphaValue(double alpha)
{m_alpha = alpha;
}
// 剧烈区域距离阈值
void ImageBinarization::setBetaValue(double beta)
{m_beta = beta;
}// 计算平均绝对偏差
double ImageBinarization::getMatMAD(const cv::Mat& wndImg)
{CV_Assert(wndImg.type() == CV_8UC1);// 计算均值cv::Scalar myMean = cv::mean(wndImg);double sum = 0.0;for (int y = 0; y < wndImg.rows; ++y){for (int x = 0; x < wndImg.cols; ++x){// 计算每一个像素灰度减去平均值的绝对值int diffAbs = cv::abs(wndImg.at<uchar>(y, x) - myMean[0]);sum += diffAbs; // 计算绝对值之和}}return sum / (wndImg.rows * wndImg.cols); // 平均绝对偏差
}
// 中心点八邻域格网号计算
std::vector<int> ImageBinarization::getGridIndices(const int windCenterInSegRow, const int windCenterInSegCol)
{// p8,p7,p6// p1,p0,p5// p2,p3,p4std::vector<int>nGridIndices;// 邻域格网索引号容器nGridIndices.resize(9);// 中心点p0所在格网int p0Row = windCenterInSegRow;int p0Col = windCenterInSegCol;int p0Grid = -1;if (p0Row < m_segRow && p0Col < m_segCol){p0Grid = p0Row * m_segCol + p0Col;}// 中心点越界则计算结束else{std::cerr << "中心点越界" << std::endl;abort();}nGridIndices[0] = p0Grid;// p0左侧p1所在格网int p1Row = p0Row;int p1Col = p0Col - 1;int p1Grid = -1;if (p1Col >= 0){p1Grid = p1Row * m_segCol + p1Col;}nGridIndices[1] = p1Grid;// p0左下方p2所在格网int p2Row = p0Row + 1;int p2Col = p0Col - 1;int p2Grid = -1;if (p2Row < m_segRow && p2Col >= 0){p2Grid = p2Row * m_segCol + p2Col;}nGridIndices[2] = p2Grid;// p0正下方p3所在格网int p3Row = p0Row + 1;int p3Col = p0Col;int p3Grid = -1;if (p3Row < m_segRow){p3Grid = p3Row * m_segCol + p3Col;}nGridIndices[3] = p3Grid;// p0右下方p4所在格网int p4Row = p0Row + 1;int p4Col = p0Col + 1;int p4Grid = -1;if (p4Row < m_segRow && p4Col < m_segCol){p4Grid = p4Row * m_segCol + p4Col;}nGridIndices[4] = p4Grid;// p0右侧p5所在格网int p5Row = p0Row;int p5Col = p0Col + 1;int p5Grid = -1;if (p5Col < m_segCol){p5Grid = p5Row * m_segCol + p5Col;}nGridIndices[5] = p5Grid;// p0右上方p6所在格网int p6Row = p0Row - 1;int p6Col = p0Col + 1;int p6Grid = -1;if (p6Row >= 0 && p6Col < m_segCol){p6Grid = p6Row * m_segCol + p6Col;}nGridIndices[6] = p6Grid;// p0正上方p7所在格网int p7Row = p0Row - 1;int p7Col = p0Col;int p7Grid = -1;if (p7Row >= 0){p7Grid = p7Row * m_segCol + p7Col;}nGridIndices[7] = p7Grid;// p0左上方p8所在格网int p8Row = p0Row - 1;int p8Col = p0Col - 1;int p8Grid = -1;if (p8Row >= 0 && p8Col >= 0){p8Grid = p8Row * m_segCol + p8Col;}nGridIndices[8] = p8Grid;return nGridIndices;
}
// 图像分块
void ImageBinarization::imageSegment()
{// OpenCV异常检测CV_Assert((m_blockSize.width <= m_src.cols) && (m_blockSize.height <= m_src.rows));// 1、获取分块图像的尺寸const int blockHeight = m_blockSize.height; // 图像分块的高度const int blockWidth = m_blockSize.width; // 图像分块的宽度// 2、根据分块尺寸计算分块的个数m_segRow = cvCeil(m_src.rows / static_cast<double>(blockHeight)); // 图像分块的行数m_segCol = cvCeil(m_src.cols / static_cast<double>(blockWidth)); // 图像分块的列数// 3、使用裁剪函数进行分块cv::Mat roiImg;for (int i = 0; i < m_segRow; ++i){int rectHeight = 0;// 裁剪矩形框的高度int rectWidth = 0; // 裁剪矩形框的宽度// 如果剩余像素的高度小于分块的高度,则裁剪矩形框的高度为剩余像素的高度rectHeight = m_src.rows - i * blockHeight < blockHeight ? m_src.rows - i * blockHeight : blockHeight;for (int j = 0; j < m_segCol; ++j){// 如果剩余像素的宽度小于分块的宽度,则裁剪矩形框的宽度为剩余像素的宽度rectWidth = m_src.cols - j * blockWidth < blockWidth ? m_src.cols - j * blockWidth : blockWidth;// 获取分块图像cv::Rect rect(j * blockWidth, i * blockHeight, rectWidth, rectHeight);m_src(rect).copyTo(roiImg);// 计算每个分块的平均绝对偏差double madValue = getMatMAD(roiImg);// 计算每个分块的otsu阈值cv::Mat imgOtsu;double otsuValue = cv::threshold(roiImg, imgOtsu, 0, 255, cv::THRESH_BINARY | cv::THRESH_OTSU);// 分块信息、平均绝对偏差和otsu阈值存储到vector容器m_imgProperty.push_back(std::make_tuple(rect, madValue, otsuValue));}}
}
// 分块二值化
void ImageBinarization::segBlockBin()
{CV_Assert(m_src.type() == CV_8UC1);CV_Assert((m_moveWndSize.width % 2 == 1) && (m_moveWndSize.height % 2 == 1));CV_Assert((m_moveWndSize.width <= m_src.cols) && (m_moveWndSize.height <= m_src.rows));// 1、计算每个窗口的阈值m_dst = cv::Mat::zeros(m_src.rows, m_src.cols, CV_8UC1);for (int y = m_moveWndSize.height / 2; y <= m_src.rows - m_moveWndSize.height / 2 - 1; ++y){for (int x = m_moveWndSize.width / 2; x <= m_src.cols - m_moveWndSize.width / 2 - 1; ++x){// 获取以(x,y)为中心的滑动窗口范围内的灰度值cv::Point topLeftPoint = cv::Point(x - m_moveWndSize.width / 2, y - m_moveWndSize.height / 2);cv::Rect moveWindRect = cv::Rect(topLeftPoint.x, topLeftPoint.y, m_moveWndSize.width, m_moveWndSize.height);cv::Mat moveWindMat = m_src(moveWindRect);double delta = getMatMAD(moveWindMat); // 局部窗口像素灰度平均绝对偏差double windMean = cv::mean(moveWindMat)[0];// 局部窗口像素灰度平均值double T2 = 0.0; // 区分平坦区域和非平坦区域的阈值double windOtsu = 0.0; // 平坦区域局部窗口阈值// -----------------------计算滑动窗口中心点所在的分块格网号---------------------const int windCenterInSegRow = cvFloor(y / m_blockSize.height); // 行号const int windCenterInSegCol = cvFloor(x / m_blockSize.width); // 列号// ----------------------------中心点八邻域格网号计算----------------------------std::vector<int>nGridIndices = getGridIndices(windCenterInSegRow, windCenterInSegCol);// -----------------------------根据子块计算阈值--------------------------------for (size_t gridIdx = 0; gridIdx < nGridIndices.size(); ++gridIdx){cv::Rect Intersection; // 重叠区域 int n = nGridIndices[gridIdx];if (n != -1){Intersection = moveWindRect & std::get<0>(m_imgProperty[n]);}if (Intersection.area() > 0){double ki = 1.0 * Intersection.area() / moveWindRect.area();T2 += ki * std::get<1>(m_imgProperty[n]);windOtsu += ki * std::get<2>(m_imgProperty[n]);}}int value = m_src.at<uchar>(y, x);// 平坦区域if (delta < m_alpha * T2){// 大于阈值的部分为黑色if (value > windOtsu){m_dst.at<uchar>(y, x) = 0;}// 小于阈值的部分为白色else{m_dst.at<uchar>(y, x) = 255;}}// 剧烈区域else{// img(x,y)>=(1-B)*Oif (value >= (1 + m_beta) * windOtsu){m_dst.at<uchar>(y, x) = 0;}// img(x,y)<(1-B)*Oelse if (value < (1 - m_beta) * windOtsu){m_dst.at<uchar>(y, x) = 255;}else{// img(x,y)< Mean + 0.5*deltaif (value < windMean + 0.5 * delta){m_dst.at<uchar>(y, x) = 255;}// img(x,y)>= Mean + 0.5*deltaelse{m_dst.at<uchar>(y, x) = 0;}}}}}
}
// 分块二值化结果输出
void ImageBinarization::blockBinResult(cv::Mat& dst)
{imageSegment();segBlockBin();// 结果输出dst = m_dst;
}
main.cpp
#include<string>
#include<iostream>
#include"ImageBinarization.h"int main()
{cv::Mat img = cv::imread("CG.jpg");// 转灰度图cv::Mat gray;cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);cv::Size windSize(3, 3); // 分块窗口大小cv::Size moveWind(5, 5); // 移动窗口大小cv::Mat img_Thr_O; // 二值化结果ImageBinarization ib;ib.setInputImage(gray);ib.setBlockSize(windSize);ib.setMoveWindSize(moveWind);ib.setAlphaValue(0.5);ib.setBetaValue(0.7);ib.blockBinResult(img_Thr_O);cv::imshow("origion_pic", img);cv::imshow("img_Thr_O", img_Thr_O);cv::waitKey(0);}