Compare commits
16 Commits
6d07690e53
...
Add-QT-GUI
Author | SHA1 | Date | |
---|---|---|---|
658bc88550 | |||
61767661e6 | |||
6cefc7bd18 | |||
96097b614d | |||
dff0311eac | |||
54b04d0dc9 | |||
82b6094c1e | |||
7c2cd2fff0 | |||
8b1bec6fa5 | |||
20d8872930 | |||
4f65f39bbe | |||
ba78f9242d | |||
d9192ef9fb | |||
35979f6ea3 | |||
c8e2f9b694 | |||
3cacbda492 |
19
.gitignore
vendored
19
.gitignore
vendored
@@ -27,8 +27,27 @@
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
out/
|
||||
build/
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
.vscode/*
|
||||
|
72
CMakeLists.txt
Normal file
72
CMakeLists.txt
Normal file
@@ -0,0 +1,72 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(TV_Denoising_CUDA)
|
||||
|
||||
set(CMAKE_BUILD_TYPE "Release")
|
||||
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR})
|
||||
|
||||
# Find the Qt5 package
|
||||
find_package(Qt5 COMPONENTS Widgets REQUIRED)
|
||||
|
||||
# Find OpenCV
|
||||
if(NOT DEFINED OpenCV_DIR)
|
||||
set(OpenCV_DIR "C:/Users/Culis/.conan2/p/b/openc720995090ff52/b/")
|
||||
# message(FATAL_ERROR "OpenCV_DIR is set to ${OpenCV_DIR}. Please set it to the appropriate directory.")
|
||||
endif()
|
||||
|
||||
find_package(OpenCV REQUIRED)
|
||||
|
||||
if(NOT OpenCV_FOUND)
|
||||
message(FATAL_ERROR "OpenCV package not found. Please make sure OpenCV is installed and OpenCV_DIR is set correctly.")
|
||||
endif()
|
||||
|
||||
find_package(OpenCV REQUIRED)
|
||||
|
||||
# Find CUDA
|
||||
enable_language(CUDA)
|
||||
|
||||
# Set CUDA flags and properties
|
||||
set(CUDA_SEPARABLE_COMPILATION ON)
|
||||
set(CUDA_PROPAGATE_HOST_FLAGS OFF)
|
||||
|
||||
# Add the CUDA source files
|
||||
file(GLOB CUDA_SOURCE_FILES "src/*.cu")
|
||||
set_source_files_properties(${CUDA_SOURCE_FILES} PROPERTIES CUDA_SOURCE_PROPERTY_FORMAT OBJ)
|
||||
|
||||
# Add the C++ source files
|
||||
file(GLOB CPP_SOURCE_FILES "src/*.cpp")
|
||||
|
||||
# Set the include directories
|
||||
include_directories(${OpenCV_DIR} ${CUDA_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} "include")
|
||||
|
||||
# Create the executable
|
||||
add_executable(TV_Denoising_CUDA ${CPP_SOURCE_FILES} ${CUDA_SOURCE_FILES})
|
||||
|
||||
# Link CUDA libraries
|
||||
target_link_libraries(TV_Denoising_CUDA ${CUDA_LIBRARIES} ${OpenCV_LIBS} Qt5::Widgets)
|
||||
|
||||
set_property(TARGET TV_Denoising_CUDA PROPERTY ENVIRONMENT "PATH=${CMAKE_CURRENT_BINARY_DIR}/Debug;${CMAKE_CURRENT_BINARY_DIR}/Release")
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties(TV_Denoising_CUDA PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" WIN32_EXECUTABLE TRUE)
|
||||
endif()
|
||||
|
||||
# Copy necessary DLLs and runtime files to the release directory
|
||||
install(
|
||||
DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/"
|
||||
DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/Release/"
|
||||
FILES_MATCHING PATTERN "*.dll"
|
||||
)
|
||||
install(
|
||||
TARGETS TV_Denoising_CUDA
|
||||
RUNTIME DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/"
|
||||
LIBRARY DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/"
|
||||
ARCHIVE DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/"
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY "${OpenCV_INSTALL_PATH}/"
|
||||
DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/"
|
||||
FILES_MATCHING PATTERN "*.dll"
|
||||
)
|
9
CMakeUserPresets.json
Normal file
9
CMakeUserPresets.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"version": 4,
|
||||
"vendor": {
|
||||
"conan": {}
|
||||
},
|
||||
"include": [
|
||||
"E:\\programming\\C++\\tv-image-denoising\\build\\CMakePresets.json"
|
||||
]
|
||||
}
|
30
README.md
30
README.md
@@ -1,3 +1,31 @@
|
||||
# tv-image-denoising
|
||||
|
||||
Using the TV algorithm to denoise images with c++ and cuda.
|
||||
Using the TV algorithm to denoise images with c++ and cuda.
|
||||
|
||||
## Requirements
|
||||
g++
|
||||
vcpkg
|
||||
cmake
|
||||
gtk
|
||||
opencv2
|
||||
cuda
|
||||
## Build
|
||||
```bash
|
||||
# Generate build files
|
||||
cmake -B build .
|
||||
# Build
|
||||
cmake --build build
|
||||
```
|
||||
## Run
|
||||
```bash
|
||||
./TV_Denoising_CUDA <input_image> <output_image> <lambda> <iterations>
|
||||
```
|
||||
|
||||
## Original Exercise:
|
||||
7. Image denoising (up to 50+10+20=80%+)
|
||||
- [x] Use and implement a total variation image denoising method for the GPU. The input should be a noisy image, processed on the GPU by solving the energy minimization problem, then the output image should be displayed or saved to the disk. (50%)
|
||||
|
||||
- [ ] You can gain extra points for providing a simple GUI that allows the user to tune the parameters of the denoising algorithm, and immediately display the results! (+10%)
|
||||
|
||||
- [ ] Additional extra points can be achieved by also submitting a comparative evaluation of yours and other denoising techniques, applied to various inputs. I.e., use metrics such as SSIM, NCC, MAD, etc. to compare results to ground truth images. (+20%)
|
||||
(Plus points: The more techniques you compare or the better quality that your figures,charts & diagrams are, you may be rewarded by further extra points.)
|
||||
|
11
conanfile.txt
Normal file
11
conanfile.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
[requires]
|
||||
opencv/4.5.5
|
||||
qt/5.15.8
|
||||
|
||||
[options]
|
||||
qt/5.15.8:qttools=True
|
||||
qt/5.15.8:shared=True
|
||||
|
||||
[generators]
|
||||
CMakeDeps
|
||||
CMakeToolchain
|
12
include/tv_denoising.hpp
Normal file
12
include/tv_denoising.hpp
Normal file
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
Author: Vargha Csongor Csaba
|
||||
Created: 2023-06-25 10:23:33
|
||||
*/
|
||||
#ifndef TV_DENOISING_H
|
||||
#define TV_DENOISING_H
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
extern "C" void TVDenoising(cv::Mat& image, float lambda, int maxIterations);
|
||||
|
||||
#endif // TV_DENOISING_H
|
217
src/main.cpp
Normal file
217
src/main.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
Author: Vargha Csongor Csaba
|
||||
Created: 2023-06-25 10:10:13
|
||||
|
||||
Description:
|
||||
This file contains the main function for the TV image denoising cli tool.
|
||||
It reads an image file, denoises it using the TV denoising algorithm,
|
||||
and saves the denoised image to a file.
|
||||
You can run it with the following command:
|
||||
./TV_Denoising_CUDA <input_image> <output_image> <lambda> <iterations>
|
||||
where:
|
||||
- <input_image> is the path to the input image file you want to denoise.
|
||||
- <output_image> is the path to the output denoised image file.
|
||||
- <lambda> is the regularization parameter for TV denoising (optional, default: 0.02).
|
||||
- <iterations> is the number of iterations for TV denoising (optional, default: 10).
|
||||
*/
|
||||
#include <iostream>
|
||||
#include <QApplication>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QDebug>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QSlider>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QImageReader>
|
||||
#include <QImage>
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include "tv_denoising.hpp"
|
||||
|
||||
// Global variables for GUI elements
|
||||
QLabel *originalImageLabel;
|
||||
QLabel *resultImageLabel;
|
||||
QLineEdit *inputImagePathEdit;
|
||||
QLineEdit *outputImagePathEdit;
|
||||
QSlider *lambdaSlider;
|
||||
QSlider *iterationsSlider;
|
||||
|
||||
void updateDenoisedImage()
|
||||
{
|
||||
std::string inputImagePath = inputImagePathEdit->text().toStdString();
|
||||
std::string outputImagePath = outputImagePathEdit->text().toStdString();
|
||||
float lambda = lambdaSlider->value() / 100.0;
|
||||
int iterations = iterationsSlider->value();
|
||||
|
||||
// Read the input image
|
||||
cv::Mat image = cv::imread(inputImagePath, cv::IMREAD_GRAYSCALE);
|
||||
|
||||
// Check if the image was successfully loaded
|
||||
if (image.empty())
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Error", "Failed to read the input image.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform TV denoising
|
||||
TVDenoising(image, lambda, iterations);
|
||||
|
||||
// Display the denoised image
|
||||
QImage resultImage(image.data, image.cols, image.rows, QImage::Format_Grayscale8);
|
||||
resultImageLabel->setPixmap(QPixmap::fromImage(resultImage));
|
||||
|
||||
// Save the denoised image
|
||||
cv::imwrite(outputImagePath, image);
|
||||
}
|
||||
|
||||
void openInputImage()
|
||||
{
|
||||
QString filePath = QFileDialog::getOpenFileName(nullptr, "Select Input Image");
|
||||
if (!filePath.isEmpty())
|
||||
inputImagePathEdit->setText(filePath);
|
||||
}
|
||||
|
||||
void openOutputImage()
|
||||
{
|
||||
QString filePath = QFileDialog::getSaveFileName(nullptr, "Select Output Image");
|
||||
if (!filePath.isEmpty())
|
||||
outputImagePathEdit->setText(filePath);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// Create the main window
|
||||
QWidget window;
|
||||
window.setWindowTitle("TV Image Denoising");
|
||||
window.resize(800, 600);
|
||||
|
||||
// Create the layout
|
||||
QGridLayout *layout = new QGridLayout(&window);
|
||||
|
||||
// Create the original image label
|
||||
QLabel *originalImageLabel = new QLabel("Original Image");
|
||||
layout->addWidget(originalImageLabel, 0, 0, 1, 2); // Row 0, spanning 1 row, 2 columns
|
||||
|
||||
// Create the original image display widget
|
||||
QLabel *originalImageDisplay = new QLabel();
|
||||
originalImageDisplay->setAlignment(Qt::AlignCenter);
|
||||
layout->addWidget(originalImageDisplay, 1, 0, 1, 2); // Row 1, spanning 1 row, 2 columns
|
||||
|
||||
// Create the result image label
|
||||
QLabel *resultImageLabel = new QLabel("Result Image");
|
||||
layout->addWidget(resultImageLabel, 0, 2, 1, 2); // Row 0, spanning 1 row, 2 columns
|
||||
|
||||
// Create the result image display widget
|
||||
QLabel *resultImageDisplay = new QLabel();
|
||||
resultImageDisplay->setAlignment(Qt::AlignCenter);
|
||||
layout->addWidget(resultImageDisplay, 1, 2, 1, 2); // Row 1, spanning 1 row, 2 columns
|
||||
|
||||
// Create the input image path input field
|
||||
QHBoxLayout *inputLayout = new QHBoxLayout;
|
||||
QLabel *inputLabel = new QLabel("Input Image:");
|
||||
QLineEdit *inputImagePathEdit = new QLineEdit;
|
||||
QPushButton *inputBrowseButton = new QPushButton("Browse");
|
||||
QObject::connect(inputBrowseButton, &QPushButton::clicked, openInputImage);
|
||||
inputLayout->addWidget(inputLabel);
|
||||
inputLayout->addWidget(inputImagePathEdit);
|
||||
inputLayout->addWidget(inputBrowseButton);
|
||||
layout->addLayout(inputLayout, 2, 0, 1, 4); // Row 2, spanning 1 row, 4 columns
|
||||
|
||||
// Create the output image path input field
|
||||
QHBoxLayout *outputLayout = new QHBoxLayout;
|
||||
QLabel *outputLabel = new QLabel("Output Image:");
|
||||
QLineEdit *outputImagePathEdit = new QLineEdit;
|
||||
QPushButton *outputBrowseButton = new QPushButton("Browse");
|
||||
QObject::connect(outputBrowseButton, &QPushButton::clicked, openOutputImage);
|
||||
outputLayout->addWidget(outputLabel);
|
||||
outputLayout->addWidget(outputImagePathEdit);
|
||||
outputLayout->addWidget(outputBrowseButton);
|
||||
layout->addLayout(outputLayout, 3, 0, 1, 4); // Row 3, spanning 1 row, 4 columns
|
||||
|
||||
// Create the lambda slider
|
||||
QLabel *lambdaLabel = new QLabel("Lambda:");
|
||||
QSlider *lambdaSlider = new QSlider(Qt::Horizontal);
|
||||
lambdaSlider->setObjectName("LambdaSlider"); // Set object name for styling
|
||||
lambdaSlider->setMinimum(0);
|
||||
lambdaSlider->setMaximum(100);
|
||||
|
||||
// Create the iterations slider
|
||||
QLabel *iterationsLabel = new QLabel("Iterations:");
|
||||
QSlider *iterationsSlider = new QSlider(Qt::Horizontal);
|
||||
iterationsSlider->setObjectName("IterationsSlider"); // Set object name for styling
|
||||
iterationsSlider->setMinimum(1);
|
||||
iterationsSlider->setMaximum(100);
|
||||
|
||||
// Create labels to display slider values
|
||||
QLabel *lambdaValueLabel = new QLabel();
|
||||
QLabel *iterationsValueLabel = new QLabel();
|
||||
|
||||
// Set stylesheet for dark design
|
||||
QString styleSheet = "QLabel { color: white; }" // Set label text color to white
|
||||
"QSlider::groove:horizontal { background-color: #555555; height: 6px; }" // Set groove color and height
|
||||
"QSlider::handle:horizontal { background-color: #ffffff; width: 10px; margin: -6px 0; }"; // Set handle color and size
|
||||
|
||||
lambdaSlider->setStyleSheet(styleSheet);
|
||||
iterationsSlider->setStyleSheet(styleSheet);
|
||||
|
||||
// Set size policies for sliders
|
||||
lambdaSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
iterationsSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
|
||||
// Create the denoise button
|
||||
QPushButton *denoiseButton = new QPushButton("Denoise");
|
||||
QObject::connect(denoiseButton, &QPushButton::clicked, updateDenoisedImage);
|
||||
|
||||
// Add the lambda slider and its label
|
||||
layout->addWidget(lambdaLabel, 4, 0); // Row 4, column 0
|
||||
layout->addWidget(lambdaSlider, 4, 1); // Row 4, column 1
|
||||
layout->addWidget(lambdaValueLabel, 5, 0); // Row 5, column 0
|
||||
|
||||
// Add the iterations slider and its label
|
||||
layout->addWidget(iterationsLabel, 4, 2); // Row 4, column 2
|
||||
layout->addWidget(iterationsSlider, 4, 3); // Row 4, column 3
|
||||
layout->addWidget(iterationsValueLabel, 5, 2); // Row 5, column 2
|
||||
|
||||
// Add the denoise button
|
||||
layout->addWidget(denoiseButton, 6, 0, 1, 4); // Row 6, spanning 1 row, 4 columns
|
||||
|
||||
QList<QByteArray> supportedFormats = QImageReader::supportedImageFormats();
|
||||
qDebug() << "Supported image formats:" << supportedFormats;
|
||||
|
||||
// Load and display the input image
|
||||
QString inputImagePath = inputImagePathEdit->text();
|
||||
QImage inputImage(inputImagePath);
|
||||
if (inputImage.isNull())
|
||||
{
|
||||
qDebug() << "Failed to load input image";
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Input image loaded successfully";
|
||||
}
|
||||
QPixmap inputPixmap = QPixmap::fromImage(inputImage);
|
||||
originalImageDisplay->setPixmap(inputPixmap.scaled(originalImageDisplay->size(), Qt::KeepAspectRatio));
|
||||
|
||||
// Load and display the output image
|
||||
QString outputImagePath = outputImagePathEdit->text();
|
||||
QImage outputImage(outputImagePath);
|
||||
if (outputImage.isNull())
|
||||
{
|
||||
qDebug() << "Failed to load input image";
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Input image loaded successfully";
|
||||
}
|
||||
QPixmap outputPixmap = QPixmap::fromImage(outputImage);
|
||||
resultImageDisplay->setPixmap(outputPixmap.scaled(resultImageDisplay->size(), Qt::KeepAspectRatio));
|
||||
|
||||
// Show the main window
|
||||
window.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
77
src/tv_denoising.cu
Normal file
77
src/tv_denoising.cu
Normal file
@@ -0,0 +1,77 @@
|
||||
#include <cuda.h>
|
||||
#include <cuda_runtime.h>
|
||||
#include <device_launch_parameters.h>
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
#define BLOCK_SIZE_X 16
|
||||
#define BLOCK_SIZE_Y 16
|
||||
|
||||
__global__ void tvDenoisingKernel(float* image, int width, int height, float lambda, int maxIterations)
|
||||
{
|
||||
// Calculate the global thread index
|
||||
int col = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
int row = blockIdx.y * blockDim.y + threadIdx.y;
|
||||
int index = row * width + col;
|
||||
|
||||
// Declare shared memory arrays
|
||||
__shared__ float gradientX[BLOCK_SIZE_X][BLOCK_SIZE_Y];
|
||||
__shared__ float gradientY[BLOCK_SIZE_X][BLOCK_SIZE_Y];
|
||||
__shared__ float updatedImage[BLOCK_SIZE_X][BLOCK_SIZE_Y];
|
||||
|
||||
// Perform TV denoising iteratively
|
||||
for (int iteration = 0; iteration < maxIterations; ++iteration)
|
||||
{
|
||||
// Calculate the gradients using central differences
|
||||
gradientX[threadIdx.x][threadIdx.y] = image[index + 1] - image[index - 1];
|
||||
gradientY[threadIdx.x][threadIdx.y] = image[index + width] - image[index - width];
|
||||
|
||||
// Synchronize threads to ensure all gradient calculations are complete
|
||||
__syncthreads();
|
||||
|
||||
// Apply TV denoising update rule
|
||||
updatedImage[threadIdx.x][threadIdx.y] = image[index] + lambda * (
|
||||
gradientX[threadIdx.x][threadIdx.y] - gradientX[threadIdx.x - 1][threadIdx.y] +
|
||||
gradientY[threadIdx.x][threadIdx.y] - gradientY[threadIdx.x][threadIdx.y - 1]
|
||||
);
|
||||
|
||||
// Update the global image array with the updated pixel values
|
||||
image[index] = updatedImage[threadIdx.x][threadIdx.y];
|
||||
|
||||
// Synchronize threads to ensure all image updates are complete
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void TVDenoising(cv::Mat& image, float lambda, int maxIterations)
|
||||
{
|
||||
// Convert the image to float precision
|
||||
cv::Mat floatImage;
|
||||
image.convertTo(floatImage, CV_32F);
|
||||
|
||||
// Get image dimensions
|
||||
int width = image.cols;
|
||||
int height = image.rows;
|
||||
|
||||
// Calculate the number of blocks and threads per block
|
||||
dim3 blockSize(16, 16);
|
||||
dim3 gridSize((width + blockSize.x - 1) / blockSize.x, (height + blockSize.y - 1) / blockSize.y);
|
||||
|
||||
// Allocate GPU memory for the image
|
||||
float* d_image;
|
||||
cudaMalloc(&d_image, width * height * sizeof(float));
|
||||
|
||||
// Copy the image data from host to device
|
||||
cudaMemcpy(d_image, floatImage.ptr<float>(0), width * height * sizeof(float), cudaMemcpyHostToDevice);
|
||||
|
||||
// Invoke the TV denoising kernel
|
||||
tvDenoisingKernel<<<gridSize, blockSize>>>(d_image, width, height, lambda, maxIterations);
|
||||
|
||||
// Copy the denoised image data back from device to host
|
||||
cudaMemcpy(floatImage.ptr<float>(0), d_image, width * height * sizeof(float), cudaMemcpyDeviceToHost);
|
||||
|
||||
// Convert the denoised image back to the original data type
|
||||
floatImage.convertTo(image, image.type());
|
||||
|
||||
// Free the GPU memory
|
||||
cudaFree(d_image);
|
||||
}
|
Reference in New Issue
Block a user