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

news/2024/10/18 8:33:27/

作者:翟天保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.ppmy.cn/news/1431017.html

相关文章

Ruby中Rack中间件的作用是什么?如何应用?

在 Ruby 中&#xff0c;Rack 是一个 Web 服务器接口&#xff0c;它允许开发者使用统一的方式构建 Web 应用程序。Rack 中间件是 Rack 框架的一个核心概念&#xff0c;它可以在请求被传递给应用程序之前或之后对请求和响应进行处理。 Rack 中间件的作用包括但不限于&#xff1a…

【氮化镓】GaN HEMT SEEs效应影响因素和机制

研究背景&#xff1a;AlGaN/GaN HEMT因其在高电压、高温和高频率下的操作能力而受到关注&#xff0c;尤其在航空航天和汽车应用中&#xff0c;其辐射响应变得尤为重要。重离子辐射可能导致绝缘体失效&#xff0c;即单事件效应&#xff08;SEEs&#xff09;引起的栅介质击穿。 …

【入门篇】本章包括创建云项目、数据库的使用、云存储管理、云函数的基本使用、实战举例(小程序之云函数开发入门到使用发布上线实操)

云函数 云函数相当于服务器接口的概念,它并属于小程序端代码。它是以函数的形式运行后端代码来响应事件以及调用其他服务。运行环境是Node.js。 一、基创建云函数项目 打开微信开发者工具: 打开微信开发者工具,并登录你的微信开发者账号。 创建项目: 如果还没有创建项目,你…

npm 打包后自动压缩成zip文件

在package.json里面的scripts下面的build添加 powershell -NoProfile -ExecutionPolicy Unrestricted -Command ./zip.ps1 新的build就是 "build": "vite build && esno ./build/script/postBuild.ts && powershell -NoProfile -ExecutionP…

【Java基础】23.接口

文章目录 一、接口的概念1.接口介绍2.接口与类相似点3.接口与类的区别4.接口特性5.抽象类和接口的区别 二、接口的声明三、接口的实现四、接口的继承五、接口的多继承六、标记接口 一、接口的概念 1.接口介绍 接口&#xff08;英文&#xff1a;Interface&#xff09;&#xf…

7.Prism框架之对话框服务

文章目录 一. 目标二. 技能介绍① 什么是Dialog?② Prism中Dialog的实现方式③ Dialog使用案例一 (修改器)④ Dialog使用案例2(异常显示窗口) 一. 目标 1. 什么是Dialog?2. 传统的Dialog如何实现?3. Prism中Dialog实现方式4. 使用Dialog实现一个异常信息弹出框 二. 技能介…

Selenium(一):八大元素定位

元素定位八大方法 1、find_element_by_id 通过id定位 find_element(By.ID,"kw") #建议使用2、find_element_by_name 通过标签名定位 find_element(By.NAME,"wd") #建议使用3、find_element_link_text 通过链接文本定位 find_element(By.LINK_TEXT,&q…

.NET 高级开发人员面试常见问题及解答

当面试.NET高级开发人员时&#xff0c;面试官通常会围绕技术深度、问题解决能力、项目经验以及编程理念等方面提出问题。以下是20个常见的面试问题及其详细解答&#xff1a; 问题&#xff1a;请简述ASP.NET MVC的工作原理&#xff1f; 解答&#xff1a;ASP.NET MVC是一个基于MV…