当前位置: 首页 > news >正文

VS2026+QT6.9+opencv图像增强(多帧平均降噪)(CLAHE对比度增强)(边缘增强)(图像超分辨率)

目录

一、前言

二、代码详解

三、gitee完整项目下载


一、前言

本方案使用于医疗设备X射线穿透,探测器获取16位灰度图像产生的随机噪声,方法是一次获取多帧图像并求平均来消除这种随机噪声,并做简单的预处理。

图像处理的流程如下:

1、主线程中获取5张raw原图,在子线程中进行图像的处理(注意:这5张raw图是5张连续的帧,每帧的噪声是随机的,这是X射线图的特性,帧数越多,去噪效果理应越好,视情况而定)

  • 当 N=1(单帧):σₙₑw=σ(无去噪效果);
  • 当 N=5:σₙₑw≈σ/2.24(噪声强度降低约 55%);
  • 当 N=10:σₙₑw≈σ/3.16(噪声强度降低约 68%);
  • 当 N=100:σₙₑw=σ/10(噪声强度降低 90%)

2、多帧ORB对齐

3、多帧平均去噪

4、CLAHE对比度增强

5、Sobel边缘提取

6、ESPCN超分

准备工作:

1、本次项目所用到的opencv头文件需要使用cmake编译opencv_contrib拓展库

cmake版本:cmake-4.1.1-windows-x86_64

opencv版本:opencv-4.12.0-windows

opencv拓展库版本:opencv_contrib-4.12.0

cmake & opencv & opencv_contrib百度网盘下载

编译教程:有空更新

#include <opencv2/opencv.hpp>       // 引入opencv头文件
#include <opencv2/dnn_superres.hpp> // 引入超分辨率头文件
#include <opencv2/features2d.hpp>   // 引入特征检测头文件

2、本次项目所用到的超分辨率模型为ESPCN_x4.pb模型(提供x2 x3 x4的EDSR模型文件试验)

ESPCN & EDSR 超分辨率模型文件百度网盘下载

完整项目中自带espcn_x4模型

3、本次项目所用到的5张RAW原图,1536x1184,16位灰度图

HFW_RAW五张原图百度网盘下载

图像处理效果如下:

raw原图:

1_avg_result:多帧平均后

2_display_8u:归一化8位

3_clahe:对比度增强

4_Sobel:边缘提取

5_morph_grad:形态学梯度增强

6_fused:多尺度融合

7_gamma_corrected:伽马亮度增强

8_ESPCN_output:超分辨率

二、代码详解

本次项目的UI界面:

解决方案:

opencv_raw.h 

#pragma once#include <QtWidgets/QWidget>
#include "ui_opencv_raw.h"
#include <QFile>
#include <QFileDialog>
#include <QThread>
#include <QImage>
#include <QMessageBox>
#include <vector>
#include <iostream>#include <opencv2/opencv.hpp>       // 引入opencv头文件
#include <opencv2/dnn_superres.hpp> // 引入超分辨率头文件
#include <opencv2/features2d.hpp>   // 引入特征检测头文件using namespace std;                // 引入标准命名空间
using namespace cv;                 // 引入opencv命名空间
using namespace dnn_superres;       // 引入超分辨率命名空间class raw_thread;                   // 向前声明class opencv_raw : public QWidget
{Q_OBJECTpublic:opencv_raw(QWidget* parent = nullptr);~opencv_raw();raw_thread* raw;    // 对象QThread* thread;    // 线程int width = 1536;   // 图像宽度int height = 1184;  // 图像高度QFile file;         // 打开raw文件Mat clone;          // 深拷贝图像signals:void send_raw_5files(const vector<Mat>&);
public slots:void read_image(const Mat&);
private:Ui::opencv_rawClass ui;
};class raw_thread : public QObject
{Q_OBJECTpublic slots:void read_raw_5files(const vector<Mat>&);// Gamma校正Mat gammaCorrection(const Mat& src, double gamma);// ORB特征对齐cv::Mat alignWithORB(const cv::Mat& ref_8u, const cv::Mat& frame_8u, cv::Mat& H);// 多帧平均函数cv::Mat multiFrameAverage(const std::vector<cv::Mat>& aligned_frames);
signals:void send_image(const Mat&);
};

opencv_raw.cpp

#include "opencv_raw.h"opencv_raw::opencv_raw(QWidget* parent): QWidget(parent)
{ui.setupUi(this);thread = new QThread(this);         // 创建线程raw = new raw_thread();             // 创建线程对象raw->moveToThread(thread);          // 将线程对象移动到线程中thread->start();                    // 启动线程connect(raw, &raw_thread::send_image, this, &opencv_raw::read_image);connect(this, &opencv_raw::send_raw_5files, raw, &raw_thread::read_raw_5files);// 多帧平均按钮connect(ui.pushButton_3, &QPushButton::clicked, [this]() {QString folderPath = QFileDialog::getExistingDirectory(this, "选择包含5张RAW的文件夹", QDir::currentPath());if (folderPath.isEmpty()) return;QDir dir(folderPath);QStringList filters;filters << "*.raw";QFileInfoList fileInfoList = dir.entryInfoList(filters, QDir::Files);if (fileInfoList.size() < 5) {QMessageBox::warning(this, "警告", "文件夹中RAW文件不足5个");return;}// 只取前5个文件vector<Mat> rawImages;for (int i = 0; i < 5; ++i) {QFile file(fileInfoList[i].filePath());if (!file.open(QIODevice::ReadOnly)) {qWarning() << "无法打开文件:" << fileInfoList[i].filePath();continue;}Mat img(height, width, CV_16UC1);qint64 bytesRead = file.read(reinterpret_cast<char*>(img.data), width * height * 2);if (bytesRead != width * height * 2) {qWarning() << "文件大小不匹配:" << fileInfoList[i].filePath();continue;}rawImages.push_back(img);file.close();}if (rawImages.size() == 5) {QMessageBox::information(this, "提示", "成功加载5张RAW文件,开始BM3D处理");emit send_raw_5files(rawImages);}else {QMessageBox::warning(this, "警告", "实际成功加载的RAW文件不足5个");}});}opencv_raw::~opencv_raw()
{thread->quit();thread->wait(); // 等待线程结束
}void opencv_raw::read_image(const Mat& src)
{// 深拷贝clone = src.clone();// 转换为QImageQImage qImg(clone.data, clone.cols, clone.rows, clone.step, QImage::Format_Grayscale8);qImg.save("9_output.png");// 显示在label中QPixmap pixmap = QPixmap::fromImage(qImg);if (!pixmap.isNull()) {QSize labelSize = ui.label->size();QSize scaledSize = pixmap.size().scaled(labelSize, Qt::KeepAspectRatio);ui.label->setPixmap(pixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));ui.label->setAlignment(Qt::AlignCenter);}
}

raw_thread.cpp

#include "opencv_raw.h"void raw_thread::read_raw_5files(const std::vector<cv::Mat>& raw_frames) {// 1. 多帧ORB对齐std::vector<cv::Mat> aligned_frames;aligned_frames.push_back(raw_frames[0]);for (size_t i = 1; i < raw_frames.size(); ++i) {cv::Mat ref_8u, frame_8u;cv::normalize(raw_frames[0], ref_8u, 0, 255, cv::NORM_MINMAX, CV_8UC1);cv::normalize(raw_frames[i], frame_8u, 0, 255, cv::NORM_MINMAX, CV_8UC1);cv::Mat aligned_8u, H;aligned_8u = alignWithORB(ref_8u, frame_8u, H);if (H.empty()) {qWarning() << "第" << i << "帧对齐失败,使用原图";aligned_frames.push_back(raw_frames[i].clone());continue;}cv::Mat aligned_16u;cv::warpPerspective(raw_frames[i], aligned_16u, H, raw_frames[0].size(),cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(0));aligned_frames.push_back(aligned_16u);qDebug() << "第" << i << "帧16位对齐完成";}// 2. 多帧平均去噪cv::Mat avg_result = multiFrameAverage(aligned_frames);cv::imwrite("1_avg_result.png", avg_result);// 3. 16位转8位进行图像处理cv::Mat display_8u;double min_val, max_val;cv::minMaxLoc(avg_result, &min_val, &max_val);if (max_val - min_val < 30000) {cv::normalize(avg_result, display_8u, 0, 255, cv::NORM_MINMAX, CV_8UC1);}else {avg_result.convertTo(display_8u, CV_8UC1, 255.0 / 65535.0);}cv::imwrite("2_display_8u.png", display_8u);// 4. CLAHE对比度增强cv::Mat clahe_img;cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE(2.0, cv::Size(4, 4));clahe->apply(display_8u, clahe_img);cv::imwrite("3_clahe.png", clahe_img);// 5. Sobel边缘提取cv::Mat sobel_x, sobel_y, edge;cv::Sobel(clahe_img, sobel_x, CV_16S, 1, 0, 3);cv::Sobel(clahe_img, sobel_y, CV_16S, 0, 1, 3);cv::convertScaleAbs(sobel_x, sobel_x);cv::convertScaleAbs(sobel_y, sobel_y);cv::addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0, edge);cv::imwrite("4_Sobel.png", edge);// 6. 形态学梯度增强cv::Mat morph_grad;cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));cv::morphologyEx(clahe_img, morph_grad, cv::MORPH_GRADIENT, kernel);cv::imwrite("5_morph_grad.png", morph_grad);// 7. 多尺度融合cv::Mat fused;cv::addWeighted(clahe_img, 0.6, edge, 0.15, 0, fused);cv::addWeighted(fused, 1.0, morph_grad, 0.15, 0, fused);cv::imwrite("6_fused.png", fused);// 8. 伽马亮度增强double gamma = 0.6;cv::Mat gamma_corrected = gammaCorrection(fused, gamma);cv::imwrite("7_gamma_corrected.png", gamma_corrected);// 9. 转换为3通道(适配ESPCN输入)cv::Mat src3c;cv::cvtColor(gamma_corrected, src3c, cv::COLOR_GRAY2BGR);// 10. ESPCN超分cv::Mat sr_img;bool sr_success = true;std::string model_path = "ESPCN_x4.pb";  // 模型路径try {cv::dnn_superres::DnnSuperResImpl sr;sr.readModel(model_path);sr.setModel("espcn", 4);sr.upsample(src3c, sr_img);cv::cvtColor(sr_img, sr_img, cv::COLOR_BGR2GRAY); // 转回单通道qDebug() << "ESPCN超分后尺寸:" << sr_img.cols << "x" << sr_img.rows;if (!cv::imwrite("8_ESPCN_output.png", sr_img)) {qWarning() << "超分结果保存失败";}}catch (const cv::Exception& e) {qCritical() << "超分失败:" << e.what();sr_img = gamma_corrected.clone();sr_success = false;}if (!sr_success) {emit send_image(gamma_corrected);return;}// 发送最终结果在label中显示emit send_image(sr_img);
}// ORB特征对齐(输入8位图像,返回对齐后的8位图像和单应性矩阵H)
cv::Mat raw_thread::alignWithORB(const cv::Mat& ref_8u, const cv::Mat& frame_8u, cv::Mat& H) {H = cv::Mat(); // 初始化H为空if (ref_8u.empty() || frame_8u.empty()) {qCritical() << "ORB对齐失败:输入图像为空";return frame_8u.clone();}// 初始化ORB检测器cv::Ptr<cv::ORB> orb = cv::ORB::create(8000, 1.2f, 8);// 提取特征点和描述符std::vector<cv::KeyPoint> ref_kp, frame_kp;cv::Mat ref_desc, frame_desc;orb->detectAndCompute(ref_8u, cv::noArray(), ref_kp, ref_desc);orb->detectAndCompute(frame_8u, cv::noArray(), frame_kp, frame_desc);qDebug() << "ORB特征点数量(参考帧:" << ref_kp.size() << ",当前帧:" << frame_kp.size() << ")";// 检查特征点数量if (ref_kp.size() < 10 || frame_kp.size() < 10) {qWarning() << "特征点不足,返回原图";return frame_8u.clone();}// 匹配描述符cv::BFMatcher matcher(cv::NORM_HAMMING);std::vector<cv::DMatch> matches;matcher.match(ref_desc, frame_desc, matches);// 筛选优质匹配if (matches.size() < 10) {qWarning() << "有效匹配点不足(" << matches.size() << "),返回原图";return frame_8u.clone();}std::sort(matches.begin(), matches.end(), [](const cv::DMatch& a, const cv::DMatch& b) {return a.distance < b.distance;});int keep = std::max(10, (int)(matches.size() * 0.2));matches.resize(keep);// 提取匹配点坐标std::vector<cv::Point2f> ref_pts, frame_pts;for (const auto& m : matches) {ref_pts.push_back(ref_kp[m.queryIdx].pt);frame_pts.push_back(frame_kp[m.trainIdx].pt);}// 计算单应性矩阵HH = cv::findHomography(frame_pts, ref_pts, cv::RANSAC, 5.0);if (H.empty()) {qWarning() << "单应性矩阵计算失败,返回原图";return frame_8u.clone();}// 对齐8位图像cv::Mat aligned_8u;cv::warpPerspective(frame_8u,aligned_8u,H,ref_8u.size(),cv::INTER_LINEAR,cv::BORDER_CONSTANT,cv::Scalar(0));return aligned_8u;
}// 多帧平均
cv::Mat raw_thread::multiFrameAverage(const std::vector<cv::Mat>& aligned_frames) {if (aligned_frames.empty()) {qCritical() << "多帧平均失败:输入帧为空";return cv::Mat();}// 校验帧尺寸和类型int rows = aligned_frames[0].rows;int cols = aligned_frames[0].cols;for (const auto& frame : aligned_frames) {if (frame.rows != rows || frame.cols != cols || frame.type() != CV_16UC1) {qCritical() << "多帧平均失败:帧尺寸或类型不匹配";return cv::Mat();}}// 32位整数累加避免溢出cv::Mat sum_mat(rows, cols, CV_32SC1, cv::Scalar(0));for (const auto& frame : aligned_frames) {cv::Mat frame_32s;frame.convertTo(frame_32s, CV_32SC1);sum_mat += frame_32s;}// 计算平均值并转回16位cv::Mat avg_mat;sum_mat.convertTo(avg_mat, CV_16UC1, 1.0 / aligned_frames.size(), 0.5); // 四舍五入return avg_mat;
}// 伽马亮度增强
cv::Mat raw_thread::gammaCorrection(const cv::Mat& src, double gamma) {cv::Mat lut(1, 256, CV_8UC1);uchar* ptr = lut.ptr();for (int i = 0; i < 256; ++i) {ptr[i] = cv::saturate_cast<uchar>(255.0 * pow(i / 255.0, gamma));}cv::Mat dst;cv::LUT(src, lut, dst);return dst;
}

程序运行效果如下:

三、gitee完整项目下载

https://gitee.com/zjq11223344/opencv_rawhttps://gitee.com/zjq11223344/opencv_raw

http://www.dtcms.com/a/464864.html

相关文章:

  • Java 开发面试题(多线程模块)
  • 17-基于STM32的宠物饲养系统设计与实现
  • Docker镜像构建指南:Dockerfile语法与docker build命令全解析
  • 网页模板网站推荐网站每天更新多少文章
  • 三大数学工具在深度学习中的本质探讨:从空间表示到动态优化
  • 力扣1234. 替换子串得到平衡字符串
  • 数据链路层协议之STP协议
  • 给Windows电脑重命名有啥好处?
  • 网站后期的维护管理淘宝无货源一键铺货软件
  • 网站开发工程师是干嘛的网站开发职位
  • Java 创建 Word 文档:实现高效文档生成
  • C#限制当前单元格的值为指定值时禁止编辑的方法
  • 【gdb/sqlite3移植/mqtt】
  • 2025年渗透测试面试题总结-106(题目+回答)
  • 使用verdaccio搭建轻量的npm私有仓库
  • react + ant 封装Crud-根据配置生成对应的页面
  • 10-支持向量机(SVM):讲解基于最大间隔原则的分类算法
  • 微算法科技(NASDAQ:MLGO)开发延迟和隐私感知卷积神经网络分布式推理,助力可靠人工智能系统技术
  • 【Qt开发】输入类控件(六)-> QDial
  • 在JavaScript / HTML中,Chrome报错此服务器无法证实它就是xxxxx - 它的安全证书没有指定主题备用名称
  • 如何建一个免费的网站流量对网站排名的影响因素
  • PawSQL宣布支持DB2数据库SQL审核和性能优化
  • 在JavaScript / HTML中,div容器在内容过多时不显示超出的部分
  • webrtc弱网-RobustThroughputEstimator源码分析与算法原理
  • WPF依赖属性
  • 数据可视化 ECharts
  • javascript 性能优化实例一则
  • mapbox基础,使用矢量切片服务(pbf)加载line线图层
  • LLVM(Low Level Virtual Machine)介绍
  • Docker 一键部署指南:GitLab、Nacos、Redis、MySQL 与 MinIO 全解析