OpenCV-基于阴影勾勒的图纸清晰度增强算法

news2024/11/16 17:50:32

作者:翟天保Steven
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

实现原理

       大家在工作和学习中,无论是写报告还是论文,经常有截图的需求,比如图表、图纸等,但是截下来的图像往往是失真模糊的,此时如果可以用算法基于某种逻辑处理下,会使图像效果好很多。

       本文基于阴影识别算法写了一个图纸清晰度增强算法:图纸的特征就是背景色和字体色颜色相对单调,将所有字体和图表框用识别算法提取勾勒出来,对其进行提亮或加暗就能有直观的效果。图像分辨率方面按需求用CUBIC插值扩展。

       图像阴影算法不了解的同学可以参考:

OpenCV-图像阴影调整_opencv 添加阴影-CSDN博客

       下方介绍基于阴影勾勒的图纸清晰度增强算法的具体流程。

具体流程

1)读取识别图像的原图,用CUBIC插值算法进行了长宽的4倍扩展。

// 读取图像
cv::Mat src = imread("test.jpg", 0);
// 如果图像无法加载,则输出错误信息并返回
if (src.empty()) 
{
	std::cout << "Could not open or find the image" << std::endl;
	return -1;
}
// 尺寸扩大至4倍,用CUBIC插值算法,更平滑
Mat enlargedImage;
cv::resize(src, enlargedImage, Size(), 4.0, 4.0, INTER_CUBIC);

2)同样是“像”,插值后的像素感没那么重。

3)像素归一化后,通过(1-gray)*(1-gray)得到thresh图像,图像中原本暗的地方则为亮,取平均值当阈值,进行二值化得到掩膜mask。下图分别是thresh和mask。

// 像素归一化
cv::Mat gray;
input.convertTo(gray, CV_32FC1);
gray /= 255.f;
// 确定阴影区
cv::Mat thresh = cv::Mat::zeros(gray.size(), gray.type());
thresh = (1.0f - gray).mul(1.0f - gray);
// 取平均值作为阈值
float t = mean(thresh)[0];
cv::Mat mask = cv::Mat::zeros(gray.size(), CV_8UC1);
mask.setTo(255, thresh >= t);

4)根据midrate和brightrate,进行阴影区调整。假设输入的调整值为-50,对非阴影区而言,midrate都为1,brightrate都为0,即没有变化;对阴影区而言,midrate都为0.5,brightrate都为-0.125,所以色彩数值均有所增加,带来了变暗效果;对边缘地区,midrate和brightrate起到了很好的过渡作用。下图是midrate,brightrate因为看起来都是黑色的就不展示了。

// 参数设置
int max = 4;
float bright = light / 100.0f / max;
float mid = 1.0f + max * bright;
// 边缘平滑过渡
cv::Mat midrate = cv::Mat::zeros(input.size(), CV_32FC1);
cv::Mat brightrate = cv::Mat::zeros(input.size(), CV_32FC1);
for (int i = 0; i < input.rows; ++i)
{
	uchar *m = mask.ptr<uchar>(i);
	float *th = thresh.ptr<float>(i);
	float *mi = midrate.ptr<float>(i);
	float *br = brightrate.ptr<float>(i);
	for (int j = 0; j < input.cols; ++j)
	{
		if (m[j] == 255)
		{
			mi[j] = mid;
			br[j] = bright;
		}
		else 
		{
			mi[j] = (mid - 1.0f) / t * th[j] + 1.0f;
			br[j] = (1.0f / t * th[j])*bright;
		}
	}
}

5)对阴影进行调整。

// 阴影提亮或变暗,获取结果图
cv::Mat result = cv::Mat::zeros(input.size(), input.type());
for (int i = 0; i < input.rows; ++i)
{
	float *mi = midrate.ptr<float>(i);
	float *br = brightrate.ptr<float>(i);
	uchar *in = input.ptr<uchar>(i);
	uchar *r = result.ptr<uchar>(i);
	for (int j = 0; j < input.cols; ++j)
	{
		for (int k = 0; k < 3; ++k)
		{
			float temp = pow(float(in[j]) / 255.f, 1.0f / mi[j])*(1.0 / (1 - br[j]));
			uchar utemp = uchar(255 * temp);
			r[j] = utemp;
		}
	}
}

C++测试代码

// C++常用头文件
#include <algorithm>
#include <chrono>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <direct.h>
#include <functional>
#include <fstream>
#include <filesystem>
#include <iostream>
#include <io.h>
#include <map>
#include <numeric>
#include <omp.h>
#include <random>
#include <regex>
#include <stdio.h>
#include <sstream>
#include <string>
#include <set>
#include <time.h>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include <Windows.h>
// 第三方相关头文件
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/core/fast_math.hpp>

// 引入命名空间
using namespace std;
using namespace cv;

// 图像阴影亮暗调整
cv::Mat Shadow(cv::Mat input, int light)
{
	// 像素归一化
	cv::Mat gray;
	input.convertTo(gray, CV_32FC1);
	gray /= 255.f;

	// 确定阴影区
	cv::Mat thresh = cv::Mat::zeros(gray.size(), gray.type());
	thresh = (1.0f - gray).mul(1.0f - gray);
	// 取平均值作为阈值
	float t = mean(thresh)[0];
	cv::Mat mask = cv::Mat::zeros(gray.size(), CV_8UC1);
	mask.setTo(255, thresh >= t);

	// 参数设置
	int max = 4;
	float bright = light / 100.0f / max;
	float mid = 1.0f + max * bright;

	// 边缘平滑过渡
	cv::Mat midrate = cv::Mat::zeros(input.size(), CV_32FC1);
	cv::Mat brightrate = cv::Mat::zeros(input.size(), CV_32FC1);
	for (int i = 0; i < input.rows; ++i)
	{
		uchar *m = mask.ptr<uchar>(i);
		float *th = thresh.ptr<float>(i);
		float *mi = midrate.ptr<float>(i);
		float *br = brightrate.ptr<float>(i);
		for (int j = 0; j < input.cols; ++j)
		{
			if (m[j] == 255)
			{
				mi[j] = mid;
				br[j] = bright;
			}
			else 
			{
				mi[j] = (mid - 1.0f) / t * th[j] + 1.0f;
				br[j] = (1.0f / t * th[j])*bright;
			}
		}
	}

	// 阴影提亮,获取结果图
	cv::Mat result = cv::Mat::zeros(input.size(), input.type());
	for (int i = 0; i < input.rows; ++i)
	{
		float *mi = midrate.ptr<float>(i);
		float *br = brightrate.ptr<float>(i);
		uchar *in = input.ptr<uchar>(i);
		uchar *r = result.ptr<uchar>(i);
		for (int j = 0; j < input.cols; ++j)
		{
			for (int k = 0; k < 3; ++k)
			{
				float temp = pow(float(in[j]) / 255.f, 1.0f / mi[j])*(1.0 / (1 - br[j]));
				uchar utemp = uchar(255 * temp);
				r[j] = utemp;
			}

		}
	}
	return result;
}

int main()
{
	// 读取图像
	cv::Mat src = imread("test.jpg", 0);

	// 如果图像无法加载,则输出错误信息并返回
	if (src.empty()) 
	{
		std::cout << "Could not open or find the image" << std::endl;
		return -1;
	}

	// 尺寸扩大至4倍,用CUBIC插值算法,更平滑
	Mat enlargedImage;
	cv::resize(src, enlargedImage, Size(), 4.0, 4.0, INTER_CUBIC);

	// 图像阴影变暗:起到黑色字体颜色加深的效果
	cv::Mat shadow = Shadow(enlargedImage, -50);

	cout << "finish." << endl;
	return 0;
}

测试效果

       从测试效果中可以看出,因为尺寸进行了插值,所以像素感没那么明显,同时对阴影进行了变暗,整体感受会好很多,增强了图像的整体清晰度。

       如果函数有什么可以改进完善的地方,非常欢迎大家指出,一同进步何乐而不为呢~

       如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!

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

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

相关文章

医学影像图像去噪:滤波器方法、频域方法、小波变换、非局部均值去噪、深度学习与稀疏表示和字典学习

医学影像图像去噪是指使用各种算法从医学成像数据中去除噪声,以提高图像质量和对疾病的诊断准确性。MRI(磁共振成像)和CT(计算机断层扫描)是两种常见的医学成像技术,它们都会受到不同类型噪声的影响。 在医学影像中,噪声可能来源于多个方面,包括成像设备的电子系统、患…

计算机网络【CN】Ch4 网络层

总结 一台主机可以有多个IP地址&#xff0c;但是必须属于多个逻辑网络【不同的网络号】。 解决IP地址耗尽&#xff1a; IP地址结构&#xff1a; 划分子网&#xff1a;&#x1d43c;&#x1d443;地址<网络号>,<子网号>,<主机号> CIDR&#xff1a;IP地址{&…

C++:特殊成员函数

构造函数、析构函数和拷贝构造函数是C类中的三种特殊成员函数&#xff0c;它们分别用于对象的初始化、清理和拷贝操作。 1.构造函数&#xff08;Constructor&#xff09;&#xff1a;构造函数在对象创建时自动调用&#xff0c;用于初始化对象的成员变量。它的名称与类名相同&a…

Vs Code npm install 报错解决方法

用的人家的前端框架发现是封装过的&#xff0c;要修改人家前端的话还得把前端源码放在Vs Code 上运行&#xff0c;后端放在IDEA上运行&#xff0c;然后前后端并行开发&#xff0c;在配置前端环境时遇到&#xff1a; npm install 这个的原因是我把node下载到D盘了权限不够框框爆…

Linux:服务器硬件及RAID配置

Linux&#xff1a;服务器硬件及RAID配置 服务器 服务器是什么 服务器的英文名称为“ Server”&#xff0c;是指在网络上提供各种服务的高性能计算机。作为网络的节点&#xff0c;存储、处理网络上80&#xff05;的数据、信息&#xff0c;因此也被称为网络的灵魂。 服务器和…

数据挖掘实验(Apriori,fpgrowth)

Apriori&#xff1a;这里做了个小优化&#xff0c;比如abcde和adcef自连接出的新项集abcdef&#xff0c;可以用abcde的位置和f的位置取交集&#xff0c;这样第n项集的计算可以用n-1项集的信息和数字本身的位置信息计算出来&#xff0c;只需要保存第n-1项集的位置信息就可以提速…

怎么通过Javascript脚本实现远程控制一路开关

怎么通过Javascript脚本实现远程控制一路开关呢&#xff1f; 本文描述了使用Javascript脚本调用HTTP接口&#xff0c;实现控制一路开关。一路开关可控制一路照明、排风扇等电器。 可选用产品&#xff1a;可根据实际场景需求&#xff0c;选择对应的规格 序号设备名称1智能WiFi…

信息系统项目管理师0062:需求分析(5信息系统工程—5.1软件工程—5.1.2需求分析)

点击查看专栏目录 文章目录 5.1.2需求分析1.需求的层次2.需求过程3.UML4.面向对象分析记忆要点总结5.1.2需求分析 软件需求是指用户对新系统在功能、行为、性能、设计约束等方面的期望。根据IEEE的软件工程标准词汇表,软件需求是指用户解决问题或达到目标所需的条件或能力,是…

入坑 Node.js 1

原文&#xff1a;https://blog.iyatt.com/?p14717 前言 前面刚刚对 Spring Boot 有了个概念&#xff0c;再来学学 Node.js&#xff0c;顺便当学 JavaScript&#xff0c;为后面入前端做准备。 环境 Node.js 20.12.2 官方 API 文档&#xff1a;https://nodejs.org/docs/lat…

iOS OC项目中引入SwiftUI文件

iOS OC项目中引入SwiftUI文件 1、创建SwiftUI文件 2、第一次创建时&#xff0c;Xcode会提示桥接&#xff0c;选择 Creat Bridging Header即可。 3、创建swift管理类 /**在UIKit中使用SwiftUI&#xff0c;需要使用UIHostingController对SwiftUI进行包装&#xff0c;返回的是U…

小游戏:贪吃蛇

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;贪吃蛇 &#x1f337;追光的人&#xff0c;终会万丈光芒 目录 &#x1f3dd;1.头文件&#xff1a; &#x1f3dd;2.实现文件&#xff1a; &#x1f3dd;3.测试文件 &#xff1a; 前言&#…

STM32自动光控窗帘程序+Proteus仿真图 H桥L298驱动电机

目录 1、前言 2、仿真图 3、源程序 资料下载地址&#xff1a;STM32自动光控窗帘程序Proteus仿真图 H桥L298驱动电机 1、前言 基于STM32F103设计的智能光控窗帘&#xff0c;包含STM32芯片、光敏电阻、LCD1602显示屏、电机驱动控制模块等。 备注&#xff1a;通过ARM内部的…

网络安全数字孪生:一种新颖的汽车软件解决方案

摘要 随着汽车行业转变为数据驱动的业务&#xff0c;软件在车辆的开发和维护中发挥了核心作用。随着软件数量的增加&#xff0c;相应的网络安全风险、责任和监管也随之增加&#xff0c;传统方法变得不再适用于这类任务。相应的结果是整车厂和供应商都在努力应对汽车软件日益增加…

CAS和synchronized原理

synchronized与CAS Synchronized 原理加锁工作过程一些优化 CAS实现原子类 小结 Synchronized 原理 synchronized 既可以是乐观锁, 也可以是悲观锁.synchronized 既可以是轻量级锁, 也可以是重量级锁.synchronized 重量级锁是由系统的互斥锁实现的; 轻量级锁是基于自旋锁实现的…

Windows 安全中心:页面不可用 你的 IT 管理员已限制对此应用的某些区域的访问,并且你尝试访问的项目不可用。有关详细信息,请与 IT 支持人员联系。

问题 1&#xff1a;Windows 安全中心提示&#xff1a;【页面不可用 你的 IT 管理员已限制对此应用的某些区域的访问&#xff0c;并且你尝试访问的项目不可用。有关详细信息&#xff0c;请与 IT 支持人员联系。】 修复 Microsoft.SecHealthUI 方法 1&#xff1a;命令自动重装安…

linq select 和selectMany的区别

Select 和 SelectMany 都是 LINQ 查询方法&#xff0c;但它们之间有一些区别。 Select 方法用于从集合中选择特定的属性或对集合中的元素进行转换&#xff0c;并返回一个新的集合。例如&#xff1a; var numbers new List<int> { 1, 2, 3, 4, 5 }; var squaredNumbers…

sc2024项目consul

1. 什么是consul HashiCorp Consul是一款服务网络解决方案&#xff0c;可让团队管理服务之间以及内部部署和多云环境及运行时的安全网络连接。consul提供服务发现、服务治理、流量管理和对网络基础设施设备的自动更新。(添加链接描述)Consul使用Go语言开发 2. 功能 多数据中…

【PCL】教程global_hypothesis_verification 通过验证模型假设来实现 3D 对象识别与位姿估计...

测试程序1 milk.pcd milk_cartoon_all_small_clorox.pcd 终端输出1&#xff1a; Model total points: 12575; Selected Keypoints: 193 Scene total points: 307200; Selected Keypoints: 7739 [pcl::SHOTEstimation::computeFeature] The local reference frame is not valid!…

windows驱动开发-内存概述

“90%的程序问题都是由内存引起的&#xff0c;剩下的10%是使用内存引起的&#xff01;”这是一句非常经典的论证&#xff0c;实际上&#xff0c;在程序开发中&#xff0c;内存问题就是最大的问题&#xff0c;没有之一。 现代的计算机体系中&#xff0c;内存承载了太多的功能&a…

解决“该扩展程序未列在 Chrome 网上应用店中,并可能是在您不知情的情况下添加的”的方法

一、问题 安装插件出现时“该扩展程序未列在 Chrome 网上应用店中&#xff0c;并可能是在您不知情的情况下添加的” 二、解决方法 1、把需要安装的第三方插件&#xff0c;后缀.crx 改成 .rar&#xff0c;然后解压&#xff0c;得到一个文件夹 2、再打开chrome://extensions/谷歌…