OpenCV——图像分块局部阈值二值化

目录

  • 一、算法原理
    • 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);}

三、结果展示

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/2520.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

from_pretrained明明以及下载好模型,却突然不能加载了报错

本人报错&#xff1a;OSError: Error no file named model_index.json found in directory /home/xxx/我的python学习/textToImage/sdxl-turbo. 原因&#xff1a;路径错误导致无法加载模型的配置文件 pipe AutoPipelineForText2Image.from_pretrained("stabilityai/sdx…

HORROR SYSTEM

HORROR SYSTEM是一个创新的工具包,允许开发者在Unity3D中创建独特的原创恐怖游戏。 HORROR SYSTEM是一款强大而灵活的工具,旨在基于Unity3D引擎创建沉浸式第三人称恐怖游戏。 这项资产易于使用且直观,可以让任何经验水平的开发人员将他们的想法付诸实践,创造出高质量、充满…

文献速递:深度学习胶质瘤诊断---空间细胞结构预测胶质母细胞瘤的预后

Title 题目 Spatial cellular architecture predicts prognosis in glioblastoma 空间细胞结构预测胶质母细胞瘤的预后 01文献速递介绍 胶质母细胞瘤的治疗耐药性的关键驱动因素是肿瘤内的异质性和细胞状态的可塑性。在这里&#xff0c;我们调查了空间细胞组织与胶质母细胞瘤…

python爬虫 - 爬取html中的script数据(zum.com新闻信息 )

文章目录 1. 分析页面内容数据格式2. 使用re.findall方法&#xff0c;编写爬虫代码3. 使用re.search 方法&#xff0c;编写爬虫代码 1. 分析页面内容数据格式 &#xff08;1&#xff09;打开 https://zum.com/ &#xff08;2&#xff09;按F12&#xff08;或 在网页上右键 --…

C++中的五种高级初始化技术:从reserve到piecewise_construct等

C高级初始化技术&#xff1a;reserve、emplace_back、constinit、Lambda表达式、piecewise_construct 一、简介二、reserve 结合 emplace_back三、C 20的constinit四、Lambda表达式和初始化五、make_unique_for_overwrite六、piecewise_construct 和 forward_as_tuple七、总结 …

SpringBoot xxl-job 任务调度

首先官网下载xxl-job的源代码&#xff0c;然后切换到jdk8&#xff0c;等Maven下载依赖 执行mysql的脚本&#xff0c;修改连接配置&#xff0c;启动admin站点 默认地址 http://localhost:8080/xxl-job-admin/ 先新增一个任务执行器&#xff0c;指向未来任务代码的站点 然后在…

Vue.js应用中的多元化通信策略:10+种方法深度解析

Vue.js应用中的多元化通信策略&#xff1a;10种方法深度解析 在构建复杂且交互丰富的Vue 2.x应用程序时&#xff0c;有效的组件间通信是确保数据流通、状态同步与逻辑协调的关键。本文将深入探讨超过10种适用于Vue 2.x的应用内通信方法&#xff0c;覆盖父子组件、兄弟组件、跨…

探索亚马逊云科技「生成式 AI 精英速成计划」

目录 前言「生成式 AI 精英速成计划」技术开发课程学习课程学习 总结 前言 亚马逊云科技&#xff08;Amazon Web Services&#xff0c;简称AWS&#xff09;作为全球领先的云计算服务提供商&#xff0c;一直以来在推动人工智能&#xff08;AI&#xff09;领域的发展中扮演着重要…

04_c/c++开源库 json解析jsoncpp库

1.说明与安装 说明: c json字符解析 安装: sudo apt-get install libjsoncpp-dev 编译依赖 pkg-config --cflags --libs jsoncpp -I/usr/include/jsoncpp -ljsoncpp 编译选项: -I/usr/include/jsoncpp 连接选项: -ljsoncpp 2.实例 1.代码 1_jsonCpp_解析字符串_增.删.改…

IContentService

目录 1、 IContentService 1.1、 // * 保存文章 1.2、 * 发布文章 1.3、 *查询文章返回多条数据 1.4、 * 根据id或slug获取文章 1.5、 * 查询分类/标签下的文章归档 2、 IRelationshipService

Gitea:轻量级、开源的Git仓库管理平台

引言 Gitea是一款开源的、基于Go语言编写的轻量级Git服务器。它提供了类似于GitHub的功能&#xff0c;如代码托管、版本控制、团队协作等&#xff0c;但更加轻便和易于部署。Gitea的设计初衷是为了让团队或个人能够更方便地管理和分享自己的代码&#xff0c;同时不需要花费大量…

MATLAB将多张小图整合到一张大图形成模板图

MATLAB将多张小图整合到一张大图形成模板图 代码如下: clc;close all;clear all;warning off;%清除变量 rand(seed, 100); randn(seed, 100); format long g;foldername字符模板; [datacell,filenamecell,filenameAllcell]readfun_1n(foldername); K2length(filenamecell);% …

常见射频指标的本质和意义(一)

1、Rx Sensitivity&#xff08;接收灵敏度&#xff09; 接收灵敏度&#xff0c;这应该是最基本的概念之一&#xff0c;表征的是接收机能够在不超过一定误码率的情况下识别的最低信号强度。这里说误码率&#xff0c;是沿用CS&#xff08;电路交换&#xff09;时代的定义作一个通…

【YOLO改进】主干插入SKAttention模块(基于MMYOLO)

SKAttention模块 论文链接:https://arxiv.org/pdf/1903.06586.pdf 将SKAttention模块添加到MMYOLO中 将开源代码SK.py文件复制到mmyolo/models/plugins目录下 导入MMYOLO用于注册模块的包: from mmyolo.registry import MODELS 确保 class SKAttention中的输入维度为in_cha…

读天才与算法:人脑与AI的数学思维笔记08_生物的创造力

1. 生物的创造力 1.1. 在进化树中是否有其他的物种已经具有与我们人类相当的创造力水平 1.2. 20世纪50年代中期&#xff0c;动物学家德斯蒙德莫里斯&#xff08;Desmond Morris&#xff09;在伦敦动物园做了这样一个试验 1.2.1. 动物学家给…

Laravel 6 - 第十四章 响应

​ 文章目录 Laravel 6 - 第一章 简介 Laravel 6 - 第二章 项目搭建 Laravel 6 - 第三章 文件夹结构 Laravel 6 - 第四章 生命周期 Laravel 6 - 第五章 控制反转和依赖注入 Laravel 6 - 第六章 服务容器 Laravel 6 - 第七章 服务提供者 Laravel 6 - 第八章 门面 Laravel 6 - …

《ESP8266通信指南》4-以Client进行TCP通信(AT指令)

往期 《ESP8266通信指南》3-常用AT指令详解-8266连WIFI-CSDN博客 《ESP8266通信指南》2-ESP8266 AT测试-CSDN博客 《ESP8266通信指南》1-ESP8266 简介-CSDN博客 1. 小节目标 通过 AT 指令使用 8266 进行 TCP 通信 2. 书接上回 复习以下&#xff0c;上一小节我们讲到了 8…

学习java第五十天

Spring框架中的Bean的生命周期是什么&#xff1f; 在Spring中&#xff0c;Bean的生命周期可以被划分为以下阶段&#xff1a; 实例化&#xff1a;在这个阶段中&#xff0c;Spring容器根据Bean的定义&#xff0c;通过反射或其他方法来创建Bean的实例。这个阶段包括调用构造方法和…

【MongoDB】--MongoDB的组合索引

目录 一、前言二、Query查询条件转换shell输入命令1、常用shell输入命令2、explain()解析计划三、组合索引的说明一、前言 本文章主要介绍Mongodb的组合索引的使用。 二、Query查询条件转换shell输入命令 1、常用shell输入命令 Query: {"tenantsid": {"$num…

hyperf 三十一 极简DB组件

一 安装及配置 composer require hyperf/db php bin/hyperf.php vendor:publish hyperf/db 默认配置 config/autoload/db.php 如下&#xff0c;数据库支持多库配置&#xff0c;默认为 default。 配置项类型默认值备注driverstring无数据库引擎 支持 pdo 和 mysqlhoststringl…