目录
- 一、算法原理
- 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,p4
std::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)*O
if (value >= (1 + m_beta) * windOtsu)
{
m_dst.at<uchar>(y, x) = 0;
}
// img(x,y)<(1-B)*O
else if (value < (1 - m_beta) * windOtsu)
{
m_dst.at<uchar>(y, x) = 255;
}
else
{
// img(x,y)< Mean + 0.5*delta
if (value < windMean + 0.5 * delta)
{
m_dst.at<uchar>(y, x) = 255;
}
// img(x,y)>= Mean + 0.5*delta
else
{
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);
}