Skip to content

Commit c1d8bca

Browse files
committed
Add simple convolution C++ code
1 parent ccb5cf0 commit c1d8bca

File tree

9 files changed

+254
-0
lines changed

9 files changed

+254
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
cmake_minimum_required(VERSION 3.18)
2+
project(algorithms LANGUAGES CXX)
3+
4+
set(CMAKE_CXX_STANDARD 23)
5+
set(CMaKE_CXX_STANDARD_REQUIRED YES)
6+
set(CMAKE_CXX_EXTENSIONS NO)
7+
8+
add_compile_options(-Wall -Wextra -Wpedantic)
9+
10+
add_subdirectory(src)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
add_subdirectory(convolution)
2+
3+
add_executable(test_convolution.exe
4+
test_convolution.cpp)
5+
target_include_directories(test_convolution.exe
6+
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/convolution)
7+
target_link_libraries(test_convolution.exe
8+
PRIVATE convolution)
9+
10+
add_executable(benchmark_convolution.exe
11+
benchmark_convolution.cpp)
12+
target_include_directories(benchmark_convolution.exe
13+
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/convolution)
14+
target_link_libraries(benchmark_convolution.exe
15+
PRIVATE convolution)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include <convolution.h>
2+
#include <chrono>
3+
#include <iostream>
4+
#include <numeric>
5+
#include <random>
6+
7+
Matrix create_image(int rows, int cols) {
8+
std::mt19937 gen(1234);
9+
std::uniform_real_distribution<double> dis(0.0, 1.0);
10+
Matrix image(rows, cols);
11+
for (int i = 0; i < rows; ++i) {
12+
for (int j = 0; j < cols; ++j) {
13+
image(i, j) = dis(gen);
14+
}
15+
}
16+
return image;
17+
}
18+
19+
Matrix create_kernel(int rows, int cols) {
20+
Matrix kernel(rows, cols);
21+
for (int i = 0; i < rows; ++i) {
22+
for (int j = 0; j < cols; ++j) {
23+
kernel(i, j) = 1.0/(rows*cols);
24+
}
25+
}
26+
return kernel;
27+
}
28+
29+
double element_sum(const Matrix& matrix) {
30+
return std::accumulate(matrix.data(), matrix.data() + matrix.rows()*matrix.cols(), 0.0);
31+
}
32+
33+
int main(int argc, char** argv) {
34+
int rows = 1000;
35+
int cols = 1000;
36+
int kernel_rows = 7;
37+
int kernel_cols = 7;
38+
if (argc > 1) {
39+
rows = atoi(argv[1]);
40+
cols = atoi(argv[1]);
41+
}
42+
if (argc > 2) {
43+
kernel_rows = atoi(argv[2]);
44+
kernel_cols = atoi(argv[2]);
45+
}
46+
std::cout << "Image size: " << rows << "x" << cols << "\n";
47+
std::cout << "Kernel size: " << kernel_rows << "x" << kernel_cols << "\n";
48+
Matrix image = create_image(rows, cols);
49+
Matrix kernel = create_kernel(kernel_rows, kernel_cols);
50+
auto start = std::chrono::high_resolution_clock::now();
51+
auto result = convolve(image, kernel);
52+
auto end = std::chrono::high_resolution_clock::now();
53+
std::chrono::duration<double> diff = end - start;
54+
std::cout << "Time: " << diff.count() << " s\n";
55+
std::cout << "Sum: " << element_sum(result) << "\n";
56+
return 0;
57+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
add_library(convolution SHARED
2+
convolution.cpp matrices.cpp)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include "convolution.h"
2+
#include <exception>
3+
4+
Matrix convolve(const Matrix& image, const Matrix& kernel) {
5+
if (kernel.rows() % 2 != 1 || kernel.cols() % 2 != 1) {
6+
throw std::invalid_argument("Only odd dimensions on kernel supported");
7+
}
8+
/*
9+
s_mid and t_mid are number of pixels between the center pixel
10+
and the edge, ie for a 5x5 filter they will be 2.
11+
12+
The output size is calculated by adding s_mid, t_mid to each
13+
side of the dimensions of the input image.
14+
*/
15+
auto s_mid {kernel.rows()/2};
16+
auto t_mid {kernel.cols()/2};
17+
auto x_max {image.rows() + 2*s_mid};
18+
auto y_max {image.cols() + 2*t_mid};
19+
// Allocate result image.
20+
Matrix new_image(x_max, y_max);
21+
// Do convolution
22+
for (int x = 0; x < x_max; ++x) {
23+
for (int y = 0; y < y_max; ++y) {
24+
// Calculate pixel value for h at (x,y). Sum one component
25+
// for each pixel (s, t) of the filter kernel.
26+
auto s_from {std::max(s_mid - x, -s_mid)};
27+
auto s_to {std::min((x_max - x) - s_mid, s_mid + 1)};
28+
auto t_from {std::max(t_mid - y, -t_mid)};
29+
auto t_to {std::min((y_max - y) - t_mid, t_mid + 1)};
30+
double value {0.0};
31+
for (int s = s_from; s < s_to; ++s) {
32+
for (int t = t_from; t < t_to; ++t) {
33+
auto v {x - s_mid + s};
34+
auto w {y - t_mid + t};
35+
value += kernel(s_mid - s, t_mid - t)*image(v, w);
36+
}
37+
}
38+
new_image(x, y) = value;
39+
}
40+
}
41+
return new_image;
42+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#ifndef CONVOLUTION_HDR
2+
#define CONVOLUTION_HDR
3+
4+
#include "matrices.h"
5+
6+
Matrix convolve(const Matrix& image, const Matrix& kernel);
7+
8+
#endif
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include "matrices.h"
2+
3+
Matrix::Matrix(const Matrix& other) :
4+
rows_(other.rows_), cols_(other.cols_),
5+
data_(new double[rows_ * cols_]) {
6+
for (int i = 0; i < rows_ * cols_; ++i) {
7+
data_[i] = other.data_[i];
8+
}
9+
}
10+
11+
Matrix& Matrix::operator=(const Matrix& other) {
12+
if (this != &other) {
13+
rows_ = other.rows_;
14+
cols_ = other.cols_;
15+
data_.reset(new double[rows_ * cols_]);
16+
for (int i = 0; i < rows_ * cols_; ++i) {
17+
data_[i] = other.data_[i];
18+
}
19+
}
20+
return *this;
21+
}
22+
Matrix::Matrix(Matrix&& other) noexcept :
23+
rows_{other.rows_}, cols_{other.cols_},
24+
data_{std::move(other.data_)} {
25+
other.rows_ = 0;
26+
other.cols_ = 0;
27+
}
28+
29+
Matrix& Matrix::operator=(Matrix&& other) noexcept {
30+
if (&other != this) {
31+
rows_ = other.rows_;
32+
cols_ = other.cols_;
33+
data_ = std::move(other.data_);
34+
35+
other.rows_ = 0;
36+
other.cols_ = 0;
37+
}
38+
return *this;
39+
}
40+
41+
std::ostream& operator<<(std::ostream& os, const Matrix& m) {
42+
for (int i = 0; i < m.rows_; ++i) {
43+
for (int j = 0; j < m.cols_; ++j) {
44+
os << m(i, j) << " ";
45+
}
46+
os << std::endl;
47+
}
48+
return os;
49+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#ifndef MATRICES_HDR
2+
#define MATRICES_HDR
3+
4+
#include <iostream>
5+
#include <memory>
6+
7+
8+
struct Matrix {
9+
private:
10+
int rows_;
11+
int cols_;
12+
std::unique_ptr<double[]> data_;
13+
public:
14+
Matrix(int rows, int cols) :
15+
rows_(rows), cols_(cols), data_(new double[rows * cols]) {}
16+
// copy constructor & assignment operator
17+
Matrix(const Matrix& other);
18+
Matrix& operator=(const Matrix& other);
19+
// move constructor & assignment operator
20+
Matrix(Matrix&& other) noexcept;
21+
Matrix& operator=(Matrix&& other) noexcept;
22+
// matrix indexing by row and column
23+
double& operator()(int i, int j) { return data_[i * cols_ + j]; }
24+
double operator()(int i, int j) const { return data_[i * cols_ + j]; }
25+
// getters for number of rows and columns
26+
int rows() const { return rows_; }
27+
int cols() const { return cols_; }
28+
// accessors for the data
29+
double* data() { return data_.get(); }
30+
const double* data() const { return data_.get(); }
31+
// destructor
32+
~Matrix() = default;
33+
// textual representation of the matrix
34+
friend std::ostream& operator<<(std::ostream& os, const Matrix& m);
35+
};
36+
37+
#endif
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#include <convolution.h>
2+
#include <iostream>
3+
4+
int main() {
5+
// Create a 10x10 image
6+
Matrix image(10, 10);
7+
for (int i = 0; i < image.rows(); ++i) {
8+
for (int j = 0; j < image.cols(); ++j) {
9+
image(i, j) = i*image.cols() + j;
10+
}
11+
}
12+
13+
// Print the image
14+
std::cout << image << std::endl;
15+
16+
// Create a 3x3 kernel
17+
Matrix kernel(3, 3);
18+
for (int i = 0; i < kernel.rows(); ++i) {
19+
for (int j = 0; j < kernel.cols(); ++j) {
20+
kernel(i, j) = 1.0/(kernel.rows()*kernel.cols());
21+
}
22+
}
23+
24+
// Print the kernel
25+
std::cout << kernel << std::endl;
26+
27+
// Create a convolution object
28+
auto new_image = convolve(image, kernel);
29+
30+
// Print the result
31+
std::cout << new_image << std::endl;
32+
33+
return 0;
34+
}

0 commit comments

Comments
 (0)