Compare commits

1 Commits

Author SHA1 Message Date
4d8dd20b06 check if threadIdx.x > 0 && threadIdx.y > 0 2023-06-27 20:38:59 +02:00
6 changed files with 104 additions and 315 deletions

View File

@@ -1,72 +1,30 @@
cmake_minimum_required(VERSION 3.18) cmake_minimum_required(VERSION 3.18)
project(TV_Denoising_CUDA) project(TV_Denoising_CUDA)
set(CMAKE_BUILD_TYPE "Release") # Find CUDA
enable_language(CUDA)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) # Find OpenCV
set(VCPKG_INSTALLED_DIR "E:/programming/vcpkg/installed")
# Find the Qt5 package set(OpenCV_DIR "${VCPKG_INSTALLED_DIR}/x64-windows/share/opencv2")
find_package(Qt5 COMPONENTS Widgets REQUIRED) find_package(OpenCV REQUIRED)
# Find OpenCV # Set CUDA flags and properties
if(NOT DEFINED OpenCV_DIR) set(CUDA_SEPARABLE_COMPILATION ON)
set(OpenCV_DIR "C:/Users/Culis/.conan2/p/b/openc720995090ff52/b/") set(CUDA_PROPAGATE_HOST_FLAGS OFF)
# message(FATAL_ERROR "OpenCV_DIR is set to ${OpenCV_DIR}. Please set it to the appropriate directory.")
endif() # Add the CUDA source files
file(GLOB CUDA_SOURCE_FILES "src/*.cu")
find_package(OpenCV REQUIRED) set_source_files_properties(${CUDA_SOURCE_FILES} PROPERTIES CUDA_SOURCE_PROPERTY_FORMAT OBJ)
if(NOT OpenCV_FOUND) # Add the C++ source files
message(FATAL_ERROR "OpenCV package not found. Please make sure OpenCV is installed and OpenCV_DIR is set correctly.") file(GLOB CPP_SOURCE_FILES "src/*.cpp")
endif()
# Set the include directories
find_package(OpenCV REQUIRED) include_directories(${OpenCV_DIR} ${CUDA_INCLUDE_DIRS} "include")
# Find CUDA # Create the executable
enable_language(CUDA) add_executable(TV_Denoising_CUDA ${CPP_SOURCE_FILES} ${CUDA_SOURCE_FILES})
# Set CUDA flags and properties # Link CUDA libraries
set(CUDA_SEPARABLE_COMPILATION ON) target_link_libraries(TV_Denoising_CUDA ${CUDA_LIBRARIES})
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"
)

View File

@@ -1,9 +0,0 @@
{
"version": 4,
"vendor": {
"conan": {}
},
"include": [
"E:\\programming\\C++\\tv-image-denoising\\build\\CMakePresets.json"
]
}

View File

@@ -6,7 +6,6 @@ Using the TV algorithm to denoise images with c++ and cuda.
g++ g++
vcpkg vcpkg
cmake cmake
gtk
opencv2 opencv2
cuda cuda
## Build ## Build

View File

@@ -1,11 +0,0 @@
[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

View File

@@ -1,217 +1,61 @@
/* /*
Author: Vargha Csongor Csaba Author: Vargha Csongor Csaba
Created: 2023-06-25 10:10:13 Created: 2023-06-25 10:10:13
Description: Description:
This file contains the main function for the TV image denoising cli tool. 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, It reads an image file, denoises it using the TV denoising algorithm,
and saves the denoised image to a file. and saves the denoised image to a file.
You can run it with the following command: You can run it with the following command:
./TV_Denoising_CUDA <input_image> <output_image> <lambda> <iterations> ./TV_Denoising_CUDA <input_image> <output_image> <lambda> <iterations>
where: where:
- <input_image> is the path to the input image file you want to denoise. - <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. - <output_image> is the path to the output denoised image file.
- <lambda> is the regularization parameter for TV denoising (optional, default: 0.02). - <lambda> is the regularization parameter for TV denoising (optional, default: 0.02).
- <iterations> is the number of iterations for TV denoising (optional, default: 10). - <iterations> is the number of iterations for TV denoising (optional, default: 10).
*/ */
#include <iostream> #include <iostream>
#include <QApplication> #include <opencv2/opencv.hpp>
#include <QVBoxLayout> #include "tv_denoising.hpp"
#include <QHBoxLayout>
#include <QLabel> int main(int argc, char** argv)
#include <QDebug> {
#include <QLineEdit> // Check if the required arguments are provided
#include <QPushButton> if (argc < 3)
#include <QSlider> {
#include <QFileDialog> std::cerr << "Usage: ./TV_Denoising_CUDA <input_image> <output_image> [<lambda>] [<iterations>]" << std::endl;
#include <QMessageBox> return 1;
#include <QImageReader> }
#include <QImage>
#include <opencv2/opencv.hpp> // Read the input arguments
#include "tv_denoising.hpp" std::string inputImagePath = argv[1];
std::string outputImagePath = argv[2];
// Global variables for GUI elements float lambda = 0.02;
QLabel *originalImageLabel; int iterations = 10;
QLabel *resultImageLabel;
QLineEdit *inputImagePathEdit; // Check if optional arguments are provided and update the corresponding variables
QLineEdit *outputImagePathEdit; if (argc >= 4)
QSlider *lambdaSlider; lambda = std::stof(argv[3]);
QSlider *iterationsSlider; if (argc >= 5)
iterations = std::stoi(argv[4]);
void updateDenoisedImage()
{ // Read the input image
std::string inputImagePath = inputImagePathEdit->text().toStdString(); cv::Mat image = cv::imread(inputImagePath, cv::IMREAD_GRAYSCALE);
std::string outputImagePath = outputImagePathEdit->text().toStdString();
float lambda = lambdaSlider->value() / 100.0; // Check if the image was successfully loaded
int iterations = iterationsSlider->value(); if (image.empty())
{
// Read the input image std::cerr << "Failed to read the input image." << std::endl;
cv::Mat image = cv::imread(inputImagePath, cv::IMREAD_GRAYSCALE); return 1;
}
// Check if the image was successfully loaded
if (image.empty()) // Perform TV denoising
{ TVDenoising(image, lambda, iterations);
QMessageBox::critical(nullptr, "Error", "Failed to read the input image.");
return; // Display and save the denoised image
} cv::imshow("Denoised Image", image);
cv::waitKey(0);
// Perform TV denoising cv::imwrite(outputImagePath, image);
TVDenoising(image, lambda, iterations);
return 0;
// 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();
}

View File

@@ -21,7 +21,7 @@ __global__ void tvDenoisingKernel(float* image, int width, int height, float lam
// Perform TV denoising iteratively // Perform TV denoising iteratively
for (int iteration = 0; iteration < maxIterations; ++iteration) for (int iteration = 0; iteration < maxIterations; ++iteration)
{ {
// Calculate the gradients using central differences // Calculate the gradients using central differences
gradientX[threadIdx.x][threadIdx.y] = image[index + 1] - image[index - 1]; gradientX[threadIdx.x][threadIdx.y] = image[index + 1] - image[index - 1];
gradientY[threadIdx.x][threadIdx.y] = image[index + width] - image[index - width]; gradientY[threadIdx.x][threadIdx.y] = image[index + width] - image[index - width];
@@ -29,10 +29,12 @@ __global__ void tvDenoisingKernel(float* image, int width, int height, float lam
__syncthreads(); __syncthreads();
// Apply TV denoising update rule // Apply TV denoising update rule
updatedImage[threadIdx.x][threadIdx.y] = image[index] + lambda * ( if (threadIdx.x > 0 && threadIdx.y > 0) {
gradientX[threadIdx.x][threadIdx.y] - gradientX[threadIdx.x - 1][threadIdx.y] + updatedImage[threadIdx.x][threadIdx.y] = image[index] + lambda * (
gradientY[threadIdx.x][threadIdx.y] - gradientY[threadIdx.x][threadIdx.y - 1] 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 // Update the global image array with the updated pixel values
image[index] = updatedImage[threadIdx.x][threadIdx.y]; image[index] = updatedImage[threadIdx.x][threadIdx.y];
@@ -65,6 +67,12 @@ extern "C" void TVDenoising(cv::Mat& image, float lambda, int maxIterations)
// Invoke the TV denoising kernel // Invoke the TV denoising kernel
tvDenoisingKernel<<<gridSize, blockSize>>>(d_image, width, height, lambda, maxIterations); tvDenoisingKernel<<<gridSize, blockSize>>>(d_image, width, height, lambda, maxIterations);
// Check for errors during kernel launch
cudaError_t err = cudaGetLastError();
if (err != cudaSuccess)
{
printf("Error: %s\n", cudaGetErrorString(err));
}
// Copy the denoised image data back from device to host // Copy the denoised image data back from device to host
cudaMemcpy(floatImage.ptr<float>(0), d_image, width * height * sizeof(float), cudaMemcpyDeviceToHost); cudaMemcpy(floatImage.ptr<float>(0), d_image, width * height * sizeof(float), cudaMemcpyDeviceToHost);