图像处理之Retinex算法(C++)

图像处理之Retinex算法(C++)


文章目录

  • 图像处理之Retinex算法(C++)
  • 前言
  • 一、单尺度Retinex(SSR)
    • 1.原理
    • 2.代码实现
    • 3.结果展示
  • 二、多尺度Retinex(MSR)
    • 1.原理
    • 2.代码实现
    • 3.结果展示
  • 三、带色彩恢复的多尺度Retinex(MSRCR)
    • 1.原理
    • 2.代码实现
    • 3.结果展示
  • 总结


前言

Retinex 理论的基本思想就是光照强度决定了原始图像中所有像素点的动态范围大小,而原始图像的固有属性则是由物体自身的反射系数决定,即假设反射图像和光照图像相乘为原始图像。所以 Retinex 的思路即是去除光照的影响,保留住物体的固有属性。
Retinex模型示意图
如图所示,假设观察者处成像的图像为I(x,y),可以通过下式表达:
成像公式1
参数解释:
I(x,y)–源图像;
L(x,y)–表示光照分量;
R(x,y)–表示物体本身固有性质的反射分量。
对公式两端取对数,公式为:
公式2
公式3
上式即为Retinex理论处理的基本过程。
Retinex算法处理的基本过程如下:
Retinex理论处理的基本过程
流程图解读:图像I(x,y)经过Log变换转换成i(x,y),然后对图像I(x,y)进行亮度图像估计,在对估计的亮度分量进行Log变换得到l(x,y),二者相减得到物体反射分量的Log变换r(x,y),再进行Log变换的反变换Exp,即得到R(x,y)。此处亮度图像估计即通过高斯滤波或者引导滤波对图像滤波
参考资料:Retinex理论


一、单尺度Retinex(SSR)

1.原理

单尺度 Retinex 算法的处理过程非常拟合人眼的视觉成像过程,该算法的基本思路是:先构建高斯环绕函数,然后利用高斯环绕函数分别对图像的三个色彩通道 (R 、G 和 B) 进行滤波,则滤波后的图像就是我们所估计的光照分量,接着再在对数域中对原始图像和光照分量进行相减得到反射分量作为输出结果图像。该算法能压缩图像的动态范围、一定程度上的保持图像的颜色和细节的增强。改进:将高斯环绕函数对三个色彩通道滤波,改为使用引导滤波对三个彩色通道滤波。
公式3
参数解释:
Ii(x,y)–原始图像;
Li(x,y)–表示光照分量;
Ri(x,y)–表示物体本身固有性质的反射分量;
G(x,y)–高斯环绕函数;
Ii(x,y)G(x,y)–对原始图像使用高斯核进行卷积运算。
SSR算法中标准差σ一般取80-100。

2.代码实现

#include <iostream>
#include <opencv.hpp>
using namespace std;/*
* @param cv::Mat src	输入图像
* @param cv::Mat& dst	输出图像
* @param double sigma   高斯核的标准差
* @breif 单尺度Retinex图像处理
*/
cv::Mat single_scale_retinex(const cv::Mat& src,double sigma)
{cv::Mat gaussImg, log_src(src.size(),CV_32F), log_gaussImg(src.size(), CV_32F);cv::Mat difference(src.size(), CV_32F);cv::Mat dst(src.size(), CV_32F);// 对输入图像进行高斯模糊处理cv::GaussianBlur(src, gaussImg, cv::Size(0,0), sigma);// 对模糊后的图像和原图像分别进行对数运算,得到两个对数图像// 差分操作前确保两个矩阵类型和尺寸相同// 对差分后的图像进行指数运算gaussImg.convertTo(gaussImg, CV_32F);for(int i=0;i<gaussImg.rows;i++)for (int j = 0; j < gaussImg.cols; j++){log_gaussImg.at<float>(i, j) = log(gaussImg.at<float>(i, j) + 1);log_src.at<float>(i, j) = log(src.at<uchar>(i, j) + 1);difference.at<float>(i, j) = log_src.at<float>(i, j) - log_gaussImg.at<float>(i, j);//dst.at<float>(i, j) = exp(difference.at<float>(i, j));}// 归一化到0-255范围内(疑问:此处对差分图像归一化反而图像显得正常,参考https://blog.csdn.net/TmacDu/article/details/103499795博主也是这样实现)cv::normalize(difference, dst, 0, 255, cv::NORM_MINMAX, CV_8U);return dst;
}/*
* @param cv::Mat src	输入图像
* @param cv::Mat& dst	输出图像
* @param double sigma   高斯核的标准差
* @breif 单尺度Retinex图像处理
*/
void retinex_process(const cv::Mat& src, cv::Mat& dst, double sigma)
{std::vector<cv::Mat> channels;std::vector<cv::Mat> channels_dst;cv::split(src, channels);for (int i = 0; i < channels.size(); i++){channels_dst.push_back(single_scale_retinex(channels[i], sigma));}cv::merge(channels_dst, dst);
}int main()
{// 读取图片string filepath = "F://work_study//algorithm_demo//retinex_test1.jpg";cv::Mat src = cv::imread(filepath);if (src.empty()){return -1;}cv::Mat dst(src.size(),CV_8UC3,cv::Scalar(0,0,0));//执行单尺度的Retinex算法retinex_process(src, dst,80);cv::imshow("dst.jpg", dst);cv::waitKey(0);return 0;
}

3.结果展示

原图
结果

二、多尺度Retinex(MSR)

1.原理

SSR算法在动态范围压缩和色调恢复的两种效果中,只能以牺牲一种功能为代价来改进另一个,因此Jobson等一批研究者们针对单尺度Retinex模型中存在的不足,提出了将不同尺度下的增强结果线性地组合在一起,充分将局部信息和整体信息考虑进去的多尺度Retinex算法。这种算法的主要思想就是结合几种不同的尺度的中心围绕函数通过加权平均以后来估计光照分量。MSR算法可以产生同时拥有良好动态范围压缩、色彩稳定性以及良好色调恢复的单一输出图像。MSR算法的公式为:
MSR
参数解释:
N–表示尺度的个数,即高斯滤波的标准差的个数,一般将N的取值为3,用三个不同尺度的高斯滤波器对原始图像进行滤波,尺度的比例建议为15:80:250。
wk–经过实验验证,发现w1=w2=w3=1/3时,适用于大量的低照度图像,运算简单。
Fk(x,y)–在第k个尺度上的高斯滤波函数。

参考资料:MSR资料

2.代码实现

#include <iostream>
#include <opencv.hpp>
using namespace std;/*
* @param cv::Mat src	输入图像
* @param cv::Mat& dst	输出图像
* @param double sigma   高斯核的标准差
* @breif 单尺度Retinex图像处理
*/
cv::Mat single_scale_retinex(const cv::Mat& src,double sigma)
{cv::Mat gaussImg, log_src(src.size(),CV_32F), log_gaussImg(src.size(), CV_32F);cv::Mat difference(src.size(), CV_32F);cv::Mat dst(src.size(), CV_32F);// 对输入图像进行高斯模糊处理cv::GaussianBlur(src, gaussImg, cv::Size(0,0), sigma);// 对模糊后的图像和原图像分别进行对数运算,得到两个对数图像// 差分操作前确保两个矩阵类型和尺寸相同// 对差分后的图像进行指数运算gaussImg.convertTo(gaussImg, CV_32F);for(int i=0;i<gaussImg.rows;i++)for (int j = 0; j < gaussImg.cols; j++){log_gaussImg.at<float>(i, j) = log(gaussImg.at<float>(i, j) + 1);log_src.at<float>(i, j) = log(src.at<uchar>(i, j) + 1);difference.at<float>(i, j) = log_src.at<float>(i, j) - log_gaussImg.at<float>(i, j);//dst.at<float>(i, j) = exp(difference.at<float>(i, j));}// 归一化到0-255范围内(疑问:此处对差分图像归一化反而图像显得正常,参考https://blog.csdn.net/TmacDu/article/details/103499795博主也是这样实现)cv::normalize(difference, dst, 0, 255, cv::NORM_MINMAX, CV_8U);return dst;
}/*
* @param cv::Mat src	输入图像
* @param cv::Mat& dst	输出图像
* @param double sigma   高斯核的标准差
* @breif 单尺度Retinex图像处理
*/
void retinex_process(const cv::Mat& src, cv::Mat& dst, double sigma)
{std::vector<cv::Mat> channels;std::vector<cv::Mat> channels_dst;cv::split(src, channels);for (int i = 0; i < channels.size(); i++){channels_dst.push_back(single_scale_retinex(channels[i], sigma));}cv::merge(channels_dst, dst);
}/*
* @param cv::Mat src	输入图像
* @param cv::Mat& dst	输出图像
* @param std::vector<double> sigma   高斯核的标准差
* @param std::vector<double> w   权重系数
* @breif 多尺度Retinex图像处理
*/
void multi_retinex_process(const cv::Mat& src, cv::Mat& dst, std::vector<double>& sigma, std::vector<double>& w)
{std::vector<cv::Mat> temp(sigma.size());for (int i = 0; i < sigma.size(); i++){retinex_process(src,temp[i],sigma[i]);}for (int i = 0; i < w.size(); i++){dst += w[i] * temp[i];}
}int main()
{// 读取图片string filepath = "F://work_study//algorithm_demo//retinex_test3.jpg";cv::Mat src = cv::imread(filepath);if (src.empty()){return -1;}cv::Mat dst(src.size(),CV_8UC3,cv::Scalar(0,0,0));//高斯滤波器的尺度std::vector<double> sigma={15,80,250};//各个尺度下的SSR的权重系数std::vector<double> w(3, 1 / 3.0);//执行多尺度的Retinex算法multi_retinex_process(src, dst,sigma,w);cv::imwrite("dst.jpg", dst);cv::waitKey(0);return 0;
}

3.结果展示

MSR结果

三、带色彩恢复的多尺度Retinex(MSRCR)

1.原理

在上图中,我们可以看出无论是单尺度还是多尺度Retinex图像增强后都会发生图像色彩失真,原因是R、G、B三通道的像素值比例发生改变,针对这个现象,Jobson和Rahman等人又一次提出带颜色恢复的多尺度 Retinex(MSRCR)。该算法可以将 MSR 得到的结果按照一定的比例进行调整以求恢复原来的比例数值,具体是通过引入了颜色恢复因子 C ,其公式如下:颜色恢复因子公式
参数解释:
β–增益常数,经验参数为46。
α–调节因子,经验参数为125。
Ci(x,y)–第i个彩色通道的色彩恢复函数(CRF),用来调节三个通道颜色在图像中所占的比例。
MSRCR可以提供必要的颜色恢复,从而把相对较暗区域而无法观察到的信息图像细节展现出来,消除了MSR输出中明显的颜色失真和灰色区域。可以为大多数图像提供良好的效果。
MSRCR公式
参数解释:
t–偏移量系数;
G–增益系数;

2.代码实现

#include <iostream>
#include <opencv.hpp>
using namespace std;
using namespace cv;
/*
* @param cv::Mat src	输入图像
* @param cv::Mat& dst	输出图像
* @param double sigma   高斯核的标准差
* @breif 单尺度Retinex图像处理
*/
cv::Mat single_scale_retinex(const cv::Mat& src, double sigma)
{cv::Mat gaussImg, log_src(src.size(), CV_32F), log_gaussImg(src.size(), CV_32F);cv::Mat difference(src.size(), CV_32F);cv::Mat dst(src.size(), CV_32F);// 对输入图像进行高斯模糊处理cv::GaussianBlur(src, gaussImg, cv::Size(0, 0), sigma);// 对模糊后的图像和原图像分别进行对数运算,得到两个对数图像// 差分操作前确保两个矩阵类型和尺寸相同// 对差分后的图像进行指数运算gaussImg.convertTo(gaussImg, CV_32F);for (int i = 0; i < gaussImg.rows; i++)for (int j = 0; j < gaussImg.cols; j++){log_gaussImg.at<float>(i, j) = log(gaussImg.at<float>(i, j) + 1);log_src.at<float>(i, j) = log(src.at<uchar>(i, j) + 1);difference.at<float>(i, j) = log_src.at<float>(i, j) - log_gaussImg.at<float>(i, j);//dst.at<float>(i, j) = exp(difference.at<float>(i, j));}// 归一化到0-255范围内(疑问:此处对差分图像归一化反而图像显得正常,参考https://blog.csdn.net/TmacDu/article/details/103499795博主也是这样实现)// 计算带颜色恢复的归一化放到最后,此处注释//cv::normalize(difference, dst, 0, 255, cv::NORM_MINMAX, CV_8U);return difference;
}/*
* @param cv::Mat src	输入图像
* @param cv::Mat& dst	输出图像
* @param double sigma   高斯核的标准差
* @breif 单尺度Retinex图像处理
*/
void retinex_process(const cv::Mat& src, cv::Mat& dst, double sigma)
{std::vector<cv::Mat> channels;std::vector<cv::Mat> channels_dst;cv::split(src, channels);for (int i = 0; i < channels.size(); i++){channels_dst.push_back(single_scale_retinex(channels[i], sigma));}cv::merge(channels_dst, dst);
}/*
* @param cv::Mat src	输入图像
* @param cv::Mat& dst	输出图像
* @param std::vector<double> sigma   高斯核的标准差
* @param std::vector<double> w   权重系数
* @breif 多尺度Retinex图像处理
*/
void multi_retinex_process(const cv::Mat& src, cv::Mat& dst, std::vector<double>& sigma, std::vector<double>& w)
{std::vector<cv::Mat> temp(sigma.size());for (int i = 0; i < sigma.size(); i++){retinex_process(src, temp[i], sigma[i]);}for (int i = 0; i < w.size(); i++){dst += w[i] * temp[i];}
}/*
* @param cv::Mat src	输入图像
* @param cv::Mat& dst	输出图像
* @param double a   调节因子
* @param double b   增益常数
* @breif 计算颜色恢复因子
*/
void color_restoration(const cv::Mat& src, cv::Mat& dst, double a, double b)
{dst = cv::Mat(src.size(), CV_32FC3);for (int i = 0; i < src.rows; i++)for (int j = 0; j < src.cols; j++){int sum = src.at<cv::Vec3b>(i, j)[0] + src.at<cv::Vec3b>(i, j)[1] + src.at<cv::Vec3b>(i, j)[2];dst.at<cv::Vec3f>(i, j)[0] = b * (log(a * src.at<cv::Vec3b>(i, j)[0] + 1.0) - log(sum + 1.0));dst.at<cv::Vec3f>(i, j)[1] = b * (log(a * src.at<cv::Vec3b>(i, j)[1] + 1.0) - log(sum + 1.0));dst.at<cv::Vec3f>(i, j)[2] = b * (log(a * src.at<cv::Vec3b>(i, j)[2] + 1.0) - log(sum + 1.0));}
}/*
* @param cv::Mat src	输入图像
* @param cv::Mat& dst	输出图像
* @param std::vector<double> sigma   高斯核的标准差
* @param std::vector<double> w   权重系数
* @param double G   增益系数
* @param double t   偏移量系数
* @param double a   调节因子
* @param double b   增益常数
* @breif 带颜色恢复的多尺度Retinex图像处理
*/
void multi_retinex_color_restoration_process(const cv::Mat& src, cv::Mat& dst, std::vector<double>& sigma, std::vector<double>& w, double G, double t, double a, double b)
{dst.convertTo(dst, CV_32FC3);//计算多尺度Retinex图像cv::Mat multiRSImg(src.size(), CV_32FC3, cv::Scalar(0, 0, 0));multi_retinex_process(src, multiRSImg, sigma, w);//计算颜色恢复因子cv::Mat colorResImg(src.size(), CV_32FC3, cv::Scalar(0, 0, 0));color_restoration(src, colorResImg, a, b);//进行带颜色恢复的多尺度Retinex计算//关键点:saturate_cast<uchar>相当于对图像色彩做了保护for (int i = 0; i < src.rows; i++)for (int j = 0; j < src.cols; j++){dst.at<cv::Vec3f>(i, j)[0] = saturate_cast<uchar>(G * ((multiRSImg.at<cv::Vec3f>(i, j)[0] * colorResImg.at<cv::Vec3f>(i, j)[0]) + t));dst.at<cv::Vec3f>(i, j)[1] = saturate_cast<uchar>(G * ((multiRSImg.at<cv::Vec3f>(i, j)[1] * colorResImg.at<cv::Vec3f>(i, j)[1]) + t));dst.at<cv::Vec3f>(i, j)[2] = saturate_cast<uchar>(G * ((multiRSImg.at<cv::Vec3f>(i, j)[2] * colorResImg.at<cv::Vec3f>(i, j)[2]) + t));}cv::normalize(dst, dst, 0, 255, cv::NORM_MINMAX, CV_8UC3);
}int main()
{// 读取图片string filepath = "F://work_study//algorithm_demo//retinex_test4.jpg";cv::Mat src = cv::imread(filepath);if (src.empty()){return -1;}cv::Mat dst(src.size(), CV_8UC3, cv::Scalar(0, 0, 0));//高斯滤波器的尺度std::vector<double> sigma = { 15,80,250 };//各个尺度下的SSR的权重系数std::vector<double> w(3, 1 / 3.0);multi_retinex_color_restoration_process(src, dst, sigma, w, 5, 25, 125, 46);cv::imwrite("dst.jpg", dst);cv::waitKey(0);return 0;
}

3.结果展示

原图
MSRCR
参数比较多,不好调,大家可以阅读一位大佬写的文章,只需要调一个参数,文章链接:MSRCR
本文调节的参数在代码中,尝试很久,效果跟大佬文章效果类似。


总结

本文介绍了单尺度、多尺度、带颜色恢复的Retinex的算法原理和实现,使用C++和Opencv一步一步进行实现,代码结构简单清晰,便于阅读,但是效率有待提高,后续对代码进行重构,欢迎大家阅读和讨论代码中的不足。
本文代码均已在本地运行正确,有问题欢迎交流。

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

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

相关文章

Axure设计美观友好的后台框架页

使用Axure设计后台框架页 优点介绍&#xff1a; **1、使用中继器灵活配置菜单项&#xff1b; 2、二级菜单面板跟随一级菜单位置显示&#xff1b; 3、菜单链接打开后&#xff0c;联动添加tab标签&#xff1b; 4、标签页与iframe内容联动&#xff0c;可关闭&#xff1b; 5、左侧…

JVM--Java对象到底存在哪?

Java对象存放在堆中&#xff0c;但堆又分为新生代和老年代&#xff0c;新生代又细分为 Eden、From Survivor、To Survivor。那我们创建的对象到底在哪里&#xff1f; 堆分为新生代和老年代&#xff0c;新生代用于存放使用后就要被回收的对象&#xff08;朝生夕死&#xff09;&a…

iOS - 多线程的安全隐患

文章目录 iOS - 多线程的安全隐患1. 卖票案例2. 多线程安全隐患的解决方案2.1 iOS中的线程同步方案2.2 同步方案的使用2.2.1 OSSpinLock2.2.1.1 使用方法&#xff1a;2.2.1.2 案例 2.2.2 os_unfair_lock2.2.2.1 使用方法&#xff1a;2.2.2.2 案例 2.2.3 pthread_mutex2.2.3.1 使…

【Ne4j图数据库入门笔记1】图形数据建模初识

1.1 图形建模指南 图形数据建模是用户将任意域描述为节点的连接图以及与属性和标签关系的过程。Neo4j 图数据模型旨在以 Cypher 查询的形式回答问题&#xff0c;并通过组织图数据库的数据结构来解决业务和技术问题。 1.1.1 图形数据模型介绍 图形数据模型通常被称为对白板友…

【大模型】大模型时代的语音合成:音频的离散化表示

&#x1f512;文章目录 &#x1f4ca;什么是音频离散化&#x1f3ac;音频离散化是什么 &#x1f308;SoundStream、Encodec&#x1f302; SoundStream&#x1f680;Encodec &#x1f4ca;什么是音频离散化 &#x1f3ac;音频离散化是什么 在自然语言处理&#xff08;NLP&…

Amazon云计算AWS之[2]弹性计算云EC2

文章目录 说明EC2基本架构Amazon机器映象&#xff08;AMI&#xff09;实例&#xff08;Instance&#xff09;弹性块存储&#xff08;EBS&#xff09; EC2关键技术地理区域和可用区域EC2通信机制弹性负载均衡监控服务自动缩放服务管理控制台 EC2安全及容错机制EC2弹性IP地址 说明…

KingbaseES数据库copy导入导出

数据库版本&#xff1a;KingbaseES V008R006C008B0014 文章目录如下 1. 语法说明 2. 导出数据 2.1. 基本用法 2.2. 只导出某列 2.3. 指定分隔符 2.4. 导出为二进制格式 2.5. 替换导出的NULL数据 2.6. 导出SELECT查询结果 3. 导入数据 3.1. 基本用法 3.2. 只导入某列…

恒峰智慧科技—森林消防泵:既可灭除火灾,又可清理水患

在广袤的森林中&#xff0c;火灾与水患如同潜伏的猛兽&#xff0c;时刻威胁着生态的安全。然而&#xff0c;随着科技的进步&#xff0c;我们有了更强大的武器来对抗这些威胁——森林消防泵。这款神奇的设备不仅能迅速扑灭火灾&#xff0c;还能在雨季到来时清理水患&#xff0c;…

Linux:进程与计划任务

文章目录 Linux&#xff1a;进程与计划任务一、进程1、进程是什么2、进程状态 二、列出进程命令1、查看静态的进程统计信息——“ps”Play1&#xff1a;“ps aux”Play2:ps -elf 2、查看静态的进程统计信息——“top”段首解析进程信息区解释 三、运行与终止进程3.1、运行进程3…

spring的跨域问题

跨域问题 什么是跨域解决跨域 什么是跨域 跨域问题本质是浏览器的一种保护机制&#xff0c;它的初衷是为了保证用户的安全&#xff0c;防止恶意网站窃取数据。如果出现了以下情况中的任意一种&#xff0c;那么它就是跨域请求&#xff1a; 1、协议不同&#xff0c;如 http 和 h…

Redis入门到通关之Redis数据结构-List篇

文章目录 ☃️概述☃️数据结构☃️源码☃️其他 欢迎来到 请回答1024 的博客 &#x1f353;&#x1f353;&#x1f353;欢迎来到 请回答1024的博客 关于博主&#xff1a; 我是 请回答1024&#xff0c;一个追求数学与计算的边界、时间与空间的平衡&#xff0c;0与1的延伸的后端…

8、案例实战【处理百万级交易无压力】:支付系统JVM调优实战指南

8.1、前文回顾 本文将以一个日交易量达百万次的支付系统为背景,为大家深入分析在上线部署一个系统时,如何根据系统的业务量来合理设置JVM的堆内存大小。 通过阅读之前的文章,相信大家已经对编写的代码如何在JVM中运行的基本原理有了一定的了解,同时也知道如何通过参数来设…

数据结构(Wrong Question)

一、绪论 1.1 数据结构的基本概念 D 因为抽象数据类型&#xff08;ADT&#xff09;描述了数据的逻辑结构和抽象运算&#xff0c;通常用&#xff08;数据对象&#xff0c;数据对象&#xff0c;基本操作集&#xff09;这样的三元组来表示&#xff0c;从而可构成一个完整的数据结…

3节点ubuntu24.04服务器docker-compose方式部署高可用elk+kafka日志系统并接入nginx日志

一&#xff1a;系统版本: 二&#xff1a;部署环境&#xff1a; 节点名称 IP 部署组件及版本 配置文件路径 机器CPU 机器内存 机器存储 Log-001 10.10.100.1 zookeeper:3.4.13 kafka:2.8.1 elasticsearch:7.7.0 logstash:7.7.0 kibana:7.7.0 zookeeper:/data/zookeep…

(Oracle)SQL优化案例:组合索引优化

项目场景 项目上的ETL模型里有如下SQL语句。执行速度非常慢&#xff0c;每次只查询200条数据&#xff0c;但却需要20多秒的时间。再加上该SQL查询出的数据同步频率很高&#xff0c;这个速度是完全不能忍受的。 因为项目隐私&#xff0c;所以对表及字段做了改写。 SELECT ID…

DaVinci Fusion Studio 19 for Mac/win:影视后期特效合成的巅峰之作

在影视后期制作的广袤天地里&#xff0c;一款强大的特效合成软件如同一位技艺高超的魔法师&#xff0c;能够化腐朽为神奇&#xff0c;将普通的影像素材转变为震撼人心的视觉盛宴。而DaVinci Fusion Studio 19&#xff0c;正是这样一款备受影视从业者推崇的巅峰之作。 无论是Ma…

基于Python+Selenium+Pytest的Dockerfile如何写

使用 Dockerfile 部署 Python 应用程序与 Selenium 测试 在本文中&#xff0c;我们将介绍如何使用 Dockerfile 部署一个 Python 应用程序&#xff0c;同时利用 Selenium 进行自动化测试。我们将使用官方的 Python 运行时作为父镜像&#xff0c;并在其中安装所需的依赖项和工具…

Android视角看鸿蒙第十二课-鸿蒙的布局之相对布局RelativeContainer

Android视角看鸿蒙第十二课-鸿蒙的布局之相对布局RelativeContainer 导读 相对布局和线性、层叠布局一样都是类似于Android布局的&#xff0c;之前两篇文章已经了解线性、层叠布局的使用方法&#xff0c;这篇文章一起来学习下鸿蒙中的相对布局。 之前的文章中&#xff0c;我偶…

VNISEdit 制作安装包

1. 环境依赖 1.1. NSIS 下载 下载地址&#xff1a;https://nsis.sourceforge.io/Download 1.2. VNISEdit 下载 下载地址1&#xff1a;https://sourceforge.net/projects/hmne/ 下载 exe 安装。 下载地址2&#xff1a;https://hmne.sourceforge.net/ 可以下载 exe 安装。也…

centos 安装配置文件中心 nacos2.2.3 稳定版

安装mysql 8 参考文章 centos7搭建mysql5.6 && mysql 8.0_centos7 mysql5.6-CSDN博客 安装 jdk 17 官网下载 对应的版本 Java Downloads | Oracle wget https://download.java.net/java/GA/jdk17.0.2/dfd4a8d0985749f896bed50d7138ee7f/8/GPL/openjdk-17.0.2_l…