├── .gitattributes
├── README.md
├── doc
├── CUDA_QuickReference.pdf
└── thread_indexing.pdf
├── images
├── bench_cpu_knn.png
├── bench_cpu_nlm_514.png
├── bench_gpu_knn.png
├── bench_gpu_knn_shared.png
├── bench_gpu_nlm_256.png
├── bench_gpu_nlm_514.png
├── conv_res.jpg
├── edge_detect.jpg
├── hysterys.jpg
├── knn_results.jpg
├── nlm_results.jpg
└── pixelize.jpg
└── src
├── cpu
├── CMakeLists.txt
├── Makefile
├── README.txt
├── bench
│ ├── Makefile
│ ├── bench_edge_detect.cpp
│ ├── bench_knn.cpp
│ ├── bench_non_local_means_cpu.cpp
│ ├── benchs.ipynb
│ ├── nlm.txt
│ └── nlm2.txt
├── edge_detect.cpp
├── edge_detect.hh
├── fft
│ ├── Makefile
│ └── test_opencv.cpp
├── knn.cpp
├── knn.hh
├── main.cpp
├── non_local_means_cpu.cpp
├── non_local_means_cpu.hh
├── timer.hh
└── timer_test.cc
├── gpu
├── CMakeLists.txt
├── bench
│ ├── .ipynb_checkpoints
│ │ └── benchs-checkpoint.ipynb
│ ├── CMakeLists.txt
│ ├── bench_non_local_means_gpu.cu
│ ├── benchs.ipynb
│ ├── build
│ │ ├── knn_gpu1.txt
│ │ ├── knn_gpu2.txt
│ │ ├── knn_shared_gpu1.txt
│ │ ├── knn_shared_gpu2.txt
│ │ ├── nlm_gpu1.txt
│ │ ├── nlm_gpu2.txt
│ │ └── results
│ │ │ ├── conv_big_gpu1.txt
│ │ │ ├── conv_lenna_gpu1.txt
│ │ │ ├── conv_shared_big_gpu1.txt
│ │ │ ├── conv_shared_lenna_gpu1.txt
│ │ │ ├── edge_gpu.txt
│ │ │ ├── knn_big_gpu1.txt
│ │ │ ├── knn_lenna_gpu1.txt
│ │ │ ├── knn_shared_big_gpu1.txt
│ │ │ ├── knn_shared_lenna_gpu1.txt
│ │ │ ├── nlm_lenna514_gpu1.txt
│ │ │ └── nlm_lenna_gpu1.txt
│ └── timer.hh
├── kernel.cu
├── kernel.cuh
├── kernelcall.cu
├── kernelcall.cuh
└── main.cu
└── utils
├── utils.cpp
└── utils.hh
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.ipynb linguist-detectable=false
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Image Processing with CUDA C++
2 |
3 | ## Objective
4 | The objective of this project is to implement from scratch in CUDA C++ various image processing algorithms.
5 | A Cpu and a Gpu version of the following algorithms is implemented and commented:
6 | - Canny Edge Detection
7 | - Non Local-Means De-Noising
8 | - K-Nearest Neighbors De-Noising
9 | - Convolution Blurring
10 | - Pixelize
11 |
12 | We benchmarked the [Gpu](https://github.com/ConsciousML/canny-edge-cuda/blob/master/src/gpu/bench/benchs.ipynb) and [Cpu](https://github.com/ConsciousML/canny-edge-cuda/blob/master/src/cpu/bench/benchs.ipynb) version.
13 |
14 | ## Setup:
15 | Make sure you have a CUDA capable GPU and install cudatoolkit for your OS.
16 | Then run:
17 | ```bash
18 | cd src/gpu
19 | mkdir build && cd build
20 | cmake ..
21 | make
22 | ```
23 |
24 | ## Algorithms :
25 | ### Canny Edge Detection
26 | 
27 |
28 | Detects the edges of an image.
29 |
30 |
31 | Usage:
32 | ```bash
33 | ./main edge_detect
34 | ```
35 |
36 | ### Non Local-Means De-noising
37 | 
38 |
39 | Removes the grain of an image.
40 |
41 | Benchmark:
42 | - Cpu:
43 |
44 |
45 |
46 | - Gpu:
47 |
48 |
49 |
50 | Usage:
51 | ```bash
52 | ./main nlm
53 | ```
54 |
55 | ### K-Nearest Neighbors De-noising
56 | 
57 |
58 | Removes the noise of an image using the KNN algorithm.
59 |
60 | Benchmark:
61 | - Cpu:
62 |
63 |
64 |
65 | - Gpu:
66 |
67 |
68 |
69 | Usage:
70 | ```bash
71 | ./main nlm
72 | ```
73 |
74 | ### Convolution Blurring
75 | 
76 |
77 | Blurs an image using the convolution operator.
78 |
79 | Usage:
80 | ```bash
81 | ./main conv
82 | ./main shared_conv
83 | ```
84 | Use `shared_conv` for an optimized version using shared memory.
85 |
86 | ### Pixelize
87 | 
88 |
89 | Pixelizes an image.
90 |
91 | Usage
92 | ```bash
93 | ./main pixelize
94 | ```
95 |
--------------------------------------------------------------------------------
/doc/CUDA_QuickReference.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/doc/CUDA_QuickReference.pdf
--------------------------------------------------------------------------------
/doc/thread_indexing.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/doc/thread_indexing.pdf
--------------------------------------------------------------------------------
/images/bench_cpu_knn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/images/bench_cpu_knn.png
--------------------------------------------------------------------------------
/images/bench_cpu_nlm_514.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/images/bench_cpu_nlm_514.png
--------------------------------------------------------------------------------
/images/bench_gpu_knn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/images/bench_gpu_knn.png
--------------------------------------------------------------------------------
/images/bench_gpu_knn_shared.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/images/bench_gpu_knn_shared.png
--------------------------------------------------------------------------------
/images/bench_gpu_nlm_256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/images/bench_gpu_nlm_256.png
--------------------------------------------------------------------------------
/images/bench_gpu_nlm_514.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/images/bench_gpu_nlm_514.png
--------------------------------------------------------------------------------
/images/conv_res.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/images/conv_res.jpg
--------------------------------------------------------------------------------
/images/edge_detect.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/images/edge_detect.jpg
--------------------------------------------------------------------------------
/images/hysterys.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/images/hysterys.jpg
--------------------------------------------------------------------------------
/images/knn_results.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/images/knn_results.jpg
--------------------------------------------------------------------------------
/images/nlm_results.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/images/nlm_results.jpg
--------------------------------------------------------------------------------
/images/pixelize.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ConsciousML/img-processing-cuda/c59cc3026364ac4824423e1a5fb9a57365bb05d5/images/pixelize.jpg
--------------------------------------------------------------------------------
/src/cpu/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8)
2 | project( main )
3 | find_package( OpenCV REQUIRED )
4 | file(GLOB SRC
5 | "*.cpp")
6 | set(CMAKE_CXX_STANDARD 11)
7 | add_executable( main ${SRC} )
8 | target_link_libraries( main ${OpenCV_LIBS} )
9 |
--------------------------------------------------------------------------------
/src/cpu/Makefile:
--------------------------------------------------------------------------------
1 | CXX=g++
2 | CXXFLAGS=-std=c++11 -Werror
3 | LIBS=`pkg-config opencv --cflags --libs`
4 | NLMSRC=bench_non_local_means_cpu.cpp non_local_means_cpu.cpp
5 | NLMOBJ= $(NLMSRC:.c=.o)
6 | KNNSRC=bench_knn.cpp knn.cpp
7 | KNNOBJ= $(KNNSRC:.c=.o)
8 | EXECS= nlm knn
9 | nlm: $(NLMOBJ)
10 | $(CXX) -o $@ $^ $(LIBS) $(CXXFLAGS)
11 | knn: $(KNNOBJ)
12 | $(CXX) -o $@ $^ $(LIBS) $(CXXFLAGS)
13 | clean:
14 | ${RM} $(EXECS)
15 |
--------------------------------------------------------------------------------
/src/cpu/README.txt:
--------------------------------------------------------------------------------
1 | to compile the project run the following commands:
2 | mkdir build
3 | cd build
4 | cmake..
5 | make
6 |
7 | usage: ./main
8 |
9 | algorithms:
10 | -nlm:
11 | usage: ./main nlm
12 | example: ./main ../../../pictures/lenna.jpg nlm 2 2 150.0
13 |
14 | -knn:
15 | usage: ./main knn
16 | example: ./main ../../../pictures/lenna.jpg knn 2 150.0
17 |
18 | -conv:
19 | usage: ./main conv
20 | example: ./main ../../../pictures/lenna.jpg conv 2
21 |
22 | -edge_detect:
23 | usage: ./main edge_detect
24 | example: ./main ../../../pictures/lenna.jpg edge_detect
25 |
--------------------------------------------------------------------------------
/src/cpu/bench/Makefile:
--------------------------------------------------------------------------------
1 | CXX=g++
2 | INCDIR =../
3 | CXXFLAGS= -I$(INCDIR) -std=c++11
4 | LIBS=`pkg-config opencv --cflags --libs`
5 | NLMSRC=bench_non_local_means_cpu.cpp ../non_local_means_cpu.cpp
6 | NLMOBJ= $(NLMSRC:.c=.o)
7 | KNNSRC=bench_knn.cpp ../knn.cpp
8 | KNNOBJ= $(KNNSRC:.c=.o)
9 | EDGSRC=bench_edge_detect.cpp ../edge_detect.cpp
10 | EDGOBJ= $(EDGSRC:.c=.o)
11 | EXECS= nlm knn edge
12 | nlm: $(NLMOBJ)
13 | $(CXX) -o $@ $^ $(LIBS) $(CXXFLAGS)
14 | knn: $(KNNOBJ)
15 | $(CXX) -o $@ $^ $(LIBS) $(CXXFLAGS)
16 | edge: $(EDGOBJ)
17 | $(CXX) -o $@ $^ $(LIBS) $(CXXFLAGS)
18 | clean:
19 | ${RM} $(EXECS)
20 |
--------------------------------------------------------------------------------
/src/cpu/bench/bench_edge_detect.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "timer.hh"
3 | #include
4 | #include
5 | #include "opencv2/core/core.hpp"
6 | #include "opencv2/imgproc/imgproc.hpp"
7 | #include "opencv2/highgui/highgui.hpp"
8 | #include "edge_detect.hh"
9 | #include "edge_detect.hh"
10 | using namespace std;
11 | using namespace cv;
12 | int main()
13 | {
14 | double t = 1;
15 | std::ofstream myfile;
16 | myfile.open ("edge.txt", std::ofstream::out | std::ofstream::app);
17 | Mat image;
18 | Mat res;
19 | image;
20 | std::string path_image("../../../pictures/lenna.jpg");
21 | image = imread(path_image, 0);
22 | double param_decay = 150.0;
23 | {
24 | t = 1;
25 | scoped_timer timer(t, myfile);
26 | res = conv_with_mask(image, 1);
27 | }
28 | std::string path_image2("../../../pictures/Lenna514.png");
29 | Mat image2;
30 | image2 = imread(path_image2, 0);
31 | if (!image2.data)
32 | {
33 | cout << "Could not open or find the image" << std::endl;
34 | return 1;
35 | }
36 | {
37 | t = 1;
38 | scoped_timer timer(t, myfile);
39 | res = conv_with_mask(image2, 1);
40 | }
41 | std::string path_image3("../../../pictures/my_face.jpg");
42 | Mat image3;
43 | image3 = imread(path_image3, 0);
44 | if (!image3.data)
45 | {
46 | cout << "Could not open or find the image" << std::endl;
47 | return 1;
48 | }
49 | {
50 | t = 1;
51 | scoped_timer timer(t, myfile);
52 | res = conv_with_mask(image3, 1);
53 | }
54 | std::string path_image4("../../../pictures/temple01.jpg");
55 | Mat image4;
56 | image4 = imread(path_image4, 0);
57 | if (!image4.data)
58 | {
59 | cout << "Could not open or find the image" << std::endl;
60 | return 1;
61 | }
62 | {
63 | t = 1;
64 | scoped_timer timer(t, myfile);
65 | res = conv_with_mask(image4, 1);
66 | }
67 | myfile.close();
68 | }
69 |
--------------------------------------------------------------------------------
/src/cpu/bench/bench_knn.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "timer.hh"
3 | #include
4 | #include
5 | #include "opencv2/core/core.hpp"
6 | #include "opencv2/imgproc/imgproc.hpp"
7 | #include "opencv2/highgui/highgui.hpp"
8 | #include "edge_detect.hh"
9 | #include "knn.hh"
10 | using namespace std;
11 | using namespace cv;
12 | int main()
13 | {
14 | double t = 1;
15 | std::ofstream myfile;
16 | myfile.open ("knn.txt", std::ofstream::out | std::ofstream::app);
17 | Mat image;
18 | Mat res;
19 | image;
20 | std::string path_image("../../../pictures/lenna.jpg");
21 | image = imread(path_image, CV_LOAD_IMAGE_UNCHANGED);
22 | double param_decay = 150.0;
23 | for(size_t j = 2; j <= 24; j = j + 2)
24 | {
25 | {
26 | t = 1;
27 | scoped_timer timer(t, myfile);
28 | res = knn(image, j, param_decay);
29 | }
30 | }
31 | std::string path_image2("../../../pictures/Lenna514.png");
32 | Mat image2;
33 | image2 = imread(path_image2, CV_LOAD_IMAGE_UNCHANGED);
34 | if (!image2.data)
35 | {
36 | cout << "Could not open or find the image" << std::endl;
37 | return 1;
38 | }
39 | for(size_t j = 2; j <= 24; j = j + 2)
40 | {
41 | {
42 | t = 1;
43 | scoped_timer timer(t, myfile);
44 | res = knn(image2, j, param_decay);
45 | }
46 | }
47 | myfile.close();
48 | }
49 |
--------------------------------------------------------------------------------
/src/cpu/bench/bench_non_local_means_cpu.cpp:
--------------------------------------------------------------------------------
1 | #include "non_local_means_cpu.hh"
2 | #include
3 | #include "timer.hh"
4 | #include
5 | #include
6 | #include "opencv2/core/core.hpp"
7 | #include "opencv2/imgproc/imgproc.hpp"
8 | #include "opencv2/highgui/highgui.hpp"
9 | #include "edge_detect.hh"
10 | using namespace std;
11 | using namespace cv;
12 | int main()
13 | {
14 | double t = 1;
15 | std::ofstream myfile;
16 |
17 | myfile.open ("nlm.txt", std::ofstream::out | std::ofstream::app);
18 | Mat image;
19 | Mat res;
20 | image;
21 | std::string path_image("../../../pictures/lenna.jpg");
22 | image = imread(path_image, CV_LOAD_IMAGE_UNCHANGED);
23 | if (!image.data)
24 | {
25 | cout << "Could not open or find the image" << std::endl;
26 | return 1;
27 | }
28 | // size_t conv_size = 2;
29 | // size_t radius = 2;
30 | double param_decay = 150.0;
31 | for(size_t i = 2; i <= 5; i = i + 1)
32 | {
33 | for(size_t j = 2; j <= 6 ; j = j + 1)
34 | {
35 | {
36 | t = 1;
37 | scoped_timer timer(t, myfile);
38 | res = non_local_means_cpu(image, i, j, param_decay);
39 | }
40 | }
41 |
42 | }
43 | myfile.close();
44 |
45 | myfile.open ("nlm2.txt", std::ofstream::out | std::ofstream::app);
46 | std::string path_image2("../../../pictures/Lenna514.png");
47 | Mat image2;
48 | image2 = imread(path_image2, CV_LOAD_IMAGE_UNCHANGED);
49 |
50 | if (!image2.data)
51 | {
52 | cout << "Could not open or find the image" << std::endl;
53 | return 1;
54 | }
55 | for(size_t i = 2; i <= 5; i = i + 1)
56 | {
57 | for(size_t j = 2; j <= 6 ; j = j + 1)
58 | {
59 | {
60 | t = 1;
61 | scoped_timer timer(t, myfile);
62 | res = non_local_means_cpu(image2, i, j, param_decay);
63 | }
64 | }
65 | }
66 | myfile.close();
67 | }
68 |
--------------------------------------------------------------------------------
/src/cpu/bench/nlm.txt:
--------------------------------------------------------------------------------
1 | 2 s
2 | 3 s
3 | 6 s
4 | 9 s
5 | 12 s
6 | 5 s
7 | 10 s
8 | 17 s
9 | 22 s
10 | 30 s
11 | 8 s
12 | 15 s
13 | 25 s
14 | 38 s
15 | 55 s
16 | 12 s
17 | 24 s
18 | 41 s
19 | 62 s
20 | 86 s
21 |
--------------------------------------------------------------------------------
/src/cpu/bench/nlm2.txt:
--------------------------------------------------------------------------------
1 | 6 s
2 | 11 s
3 | 20 s
4 | 31 s
5 | 42 s
6 | 14 s
7 | 26 s
8 | 44 s
9 | 67 s
10 | 96 s
11 | 26 s
12 | 49 s
13 | 80 s
14 | 115 s
15 | 163 s
16 | 38 s
17 | 72 s
18 | 121 s
19 | 193 s
20 | 261 s
21 |
--------------------------------------------------------------------------------
/src/cpu/edge_detect.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "edge_detect.hh"
3 |
4 | // Applies a convolution at a given location x, y.
5 | // where conv_size is the size of the convolution mask
6 | std::valarray simple_conv(cv::Mat image, int x, int y, int conv_size)
7 | {
8 | std::valarray rgb = {0, 0, 0};
9 | int cnt = 0;
10 | for (int j = y - conv_size; j < y + conv_size; j++)
11 | {
12 | for (int i = x - conv_size; i < x + conv_size; i++)
13 | {
14 | if (i >= 0 and j >= 0)
15 | {
16 | cnt++;
17 | rgb[0] += image.at(i, j)[0];
18 | rgb[1] += image.at(i, j)[1];
19 | rgb[2] += image.at(i, j)[2];
20 | }
21 | }
22 | }
23 | rgb[0] /= cnt;
24 | rgb[1] /= cnt;
25 | rgb[2] /= cnt;
26 | return rgb;
27 | }
28 |
29 | // Applies a convolution on an entire image
30 | // where conv_size is the size of the convolution mask
31 | cv::Mat convolution(cv::Mat image, int conv_size)
32 | {
33 | auto conv_img = image.clone();
34 | for (int y = 0; y < image.cols; y++)
35 | {
36 | for (int x = 0; x < image.rows; x++)
37 | {
38 | auto gc = simple_conv(image, x, y, conv_size);
39 | conv_img.at(x, y)[0] = gc[0];
40 | conv_img.at(x, y)[1] = gc[1];
41 | conv_img.at(x, y)[2] = gc[2];
42 | }
43 | }
44 | return conv_img;
45 | }
46 |
47 | // Applies a convolution given an image, a location and a mask
48 | std::pair conv_mask(cv::Mat image, int x, int y, int conv_size, int mask1[][3], int mask2[][3])
49 | {
50 | double sum1 = 0.0;
51 | double sum2 = 0.0;
52 | int u = 0;
53 | int v = 0;
54 | double cnt1 = 0;
55 | double cnt2 = 0;
56 | for (int j = y - conv_size; j <= y + conv_size; j++)
57 | {
58 | for (int i = x - conv_size; i <= x + conv_size; i++)
59 | {
60 | if (i >= 0 and j >= 0)
61 | {
62 | int weight1 = mask1[u][v];
63 | int weight2 = mask2[u][v];
64 | auto pix = image.at(i, j);
65 | sum1 += pix * weight1;
66 | sum2 += pix * weight2;
67 | cnt1 += abs(weight1);
68 | cnt2 += abs(weight2);
69 | }
70 | v++;
71 | }
72 | u++;
73 | v = 0;
74 | }
75 | double res = sqrt(pow(sum1, 2) + pow(sum2, 2));
76 | double d = atan2(sum2, sum1);
77 | d = (d > 0 ? d : (2 * M_PI + d)) * 360 / (2 * M_PI);
78 | return std::pair(res, d);
79 | }
80 |
81 | // Classifies the gradient direction for each edges
82 | cv::Mat non_max_suppr(cv::Mat& image, double *grad, double *dir, double thresh)
83 | {
84 | for (int y = 0; y < image.cols; y++)
85 | {
86 | for (int x = 0; x < image.rows; x++)
87 | {
88 | double curr_dir = dir[x + y * image.rows];
89 | double curr_grad = grad[x + y * image.rows];
90 | if (22.5 <= curr_dir and curr_dir < 67.5
91 | and (x - 1) >= 0
92 | and (y + 1) < image.cols
93 | and (x + 1) < image.rows
94 | and (y - 1) >= 0)
95 | {
96 | double val1 = grad[(x - 1) + (y + 1) * image.rows];
97 | double val2 = grad[(x + 1) + (y - 1) * image.rows];
98 | if (val1 < curr_grad and val2 < curr_grad and curr_grad > thresh)
99 | image.at(x, y) = 255;
100 | else
101 | image.at(x, y) = 0;
102 | }
103 | else if (67.5 <= curr_dir and curr_dir < 112.5
104 | and (x - 1) >= 0
105 | and (x + 1) < image.rows)
106 | {
107 | double val1 = grad[(x - 1) + y * image.rows];
108 | double val2 = grad[(x + 1) + y * image.rows];
109 | if (val1 < curr_grad and val2 < curr_grad and curr_grad > thresh)
110 | image.at(x, y) = 255;
111 | else
112 | image.at(x, y) = 0;
113 | }
114 | else if (112.5 <= curr_dir and curr_dir < 157.5
115 | and (x - 1) >= 0
116 | and (y - 1) >= 0
117 | and (x + 1) < image.rows
118 | and (y + 1) < image.cols)
119 | {
120 | double val1 = grad[(x - 1) + (y - 1) * image.rows];
121 | double val2 = grad[(x + 1) + (y + 1) * image.rows];
122 | if (val1 < curr_grad and val2 < curr_grad and curr_grad > thresh)
123 | image.at(x, y) = 255;
124 | else
125 | image.at(x, y) = 0;
126 | }
127 | else if (((0 <= curr_dir and curr_dir < 22.5)
128 | or (157.5 <= curr_dir and curr_dir <= 180.0))
129 | and (y - 1) >= 0
130 | and (y + 1) < image.cols)
131 | {
132 | double val1 = grad[x + (y - 1) * image.rows];
133 | double val2 = grad[x + (y + 1) * image.rows];
134 | if (val1 < curr_grad and val2 < curr_grad and curr_grad > thresh)
135 | image.at(x, y) = 255;
136 | else
137 | image.at(x, y) = 0;
138 | }
139 | }
140 | }
141 | return image;
142 | }
143 |
144 | // Computes the gradient of edges and fixes broken edges
145 | bool hysterysis(cv::Mat& image, double *grad, double *dir, double t)
146 | {
147 | bool changed = false;
148 | for (int y = 0; y < image.cols; y++)
149 | {
150 | for (int x = 0; x < image.rows; x++)
151 | {
152 | if (image.at(x, y) == 255)
153 | continue;
154 | double curr_dir = dir[x + y * image.rows];
155 | double curr_grad = grad[x + y * image.rows];
156 | if (22.5 <= curr_dir and curr_dir < 67.5
157 | and (x - 1) >= 0
158 | and (y + 1) < image.cols
159 | and (x + 1) < image.rows
160 | and (y - 1) >= 0)
161 | {
162 | double dir1 = dir[(x - 1) + (y + 1) * image.rows];
163 | double dir2 = dir[(x + 1) + (y - 1) * image.rows];
164 | if (((22.5 <= dir1 and dir1 < 67.5) or (22.5 <= dir2 and dir2 < 67.5)) and curr_grad > t)
165 | {
166 | image.at(x, y) = 255;
167 | changed = true;
168 | }
169 | }
170 | else if (67.5 <= curr_dir and curr_dir < 112.5
171 | and (x - 1) >= 0
172 | and (x + 1) < image.rows)
173 | {
174 | double dir1 = grad[(x - 1) + y * image.rows];
175 | double dir2 = grad[(x + 1) + y * image.rows];
176 | if (((67.5 <= dir1 and dir1 < 112.5) or (67.5 < dir2 and dir2 < 112.5)) and curr_grad > t)
177 | {
178 | image.at(x, y) = 255;
179 | changed = true;
180 | }
181 | }
182 | else if (112.5 <= curr_dir and curr_dir < 157.5
183 | and (x - 1) >= 0
184 | and (y - 1) >= 0
185 | and (x + 1) < image.rows
186 | and (y + 1) < image.cols)
187 | {
188 | double dir1 = grad[(x - 1) + (y - 1) * image.rows];
189 | double dir2 = grad[(x + 1) + (y + 1) * image.rows];
190 | if (((112.5 <= dir1 and dir1 < 157.5) or (112.5 <= dir2 and dir2 < 157.5)) and curr_grad > t)
191 | {
192 | image.at(x, y) = 255;
193 | changed = true;
194 | }
195 | }
196 | else if (((0 <= curr_dir and curr_dir < 22.5)
197 | or (157.5 <= curr_dir and curr_dir <= 180.0))
198 | and (y - 1) >= 0
199 | and (y + 1) < image.cols)
200 | {
201 | double dir1 = grad[x + (y - 1) * image.rows];
202 | double dir2 = grad[x + (y + 1) * image.rows];
203 | if ((((0 <= dir1 and dir1 < 22.5) and (157.5 <= dir1 and dir1 <= 180.5))
204 | or ((0 <= dir2 and dir2 < 22.5) and (157.5 <= dir2 and dir2 <= 180.5))) and curr_grad > t)
205 | {
206 | image.at(x, y) = 255;
207 | changed = true;
208 | }
209 | }
210 | }
211 | }
212 | return changed;
213 | }
214 |
215 | // Applies a convolution on an entire image given a convolution mask
216 | cv::Mat conv_with_mask(cv::Mat image, int conv_size)
217 | {
218 | cv::Mat dst;
219 | cv::Mat tmp_image;
220 | double otsu_threshold = cv::threshold(image, dst, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
221 | auto edge_image = image.clone();
222 | double *grad = new double[image.cols * image.rows];
223 | double *dir = new double[image.cols * image.rows];
224 | int mask1[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
225 | int mask2[3][3] = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
226 | for (int y = 0; y < image.cols; y++)
227 | {
228 | for (int x = 0; x < image.rows; x++)
229 | {
230 | auto pair = conv_mask(image, x, y, conv_size, mask1, mask2);
231 | grad[x + y * image.rows] = pair.first;
232 | dir[x + y * image.rows] = pair.second / 2;
233 | }
234 | }
235 | non_max_suppr(edge_image, grad, dir, otsu_threshold);
236 | bool changed = hysterysis(edge_image, grad, dir, otsu_threshold * 0.5);
237 | std::cout << changed << std::endl;
238 | while (changed)
239 | {
240 | changed = hysterysis(edge_image, grad, dir, otsu_threshold * 0.5);
241 | std::cout << changed << std::endl;
242 | }
243 | return edge_image;
244 | }
245 |
246 |
--------------------------------------------------------------------------------
/src/cpu/edge_detect.hh:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | #include "opencv2/core/core.hpp"
5 | #include "opencv2/imgproc/imgproc.hpp"
6 | #include "opencv2/highgui/highgui.hpp"
7 |
8 |
9 | std::valarray conv_mask(cv::Mat image, int x, int y, int conv_size);
10 | cv::Mat convolution(cv::Mat image, int conv_size);
11 | cv::Mat conv_with_mask(cv::Mat image, int conv_size);
12 |
--------------------------------------------------------------------------------
/src/cpu/fft/Makefile:
--------------------------------------------------------------------------------
1 | CXX=g++
2 | INCDIR =../utils
3 | CXXFLAGS= -I$(INCDIR) -std=c++11
4 | VPATH=$(INCDIR)
5 | #VPATH =
6 | #SRCUTILS=$(INCDIR)/utils.cpp
7 | SRC=test_opencv.cpp $(SRCUTILS)
8 | CFLAGS=pkg-config --cflags opencv
9 | LIBS=`pkg-config opencv --cflags --libs`
10 | OBJ=$(SRC:.c=.o)
11 | all:exec
12 | exec: $(OBJ)
13 | $(CXX) $(SRC) -o bin $(LIBS) $(CXXFLAGS)
14 |
15 | clean:
16 | rm *.o
17 |
--------------------------------------------------------------------------------
/src/cpu/fft/test_opencv.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "opencv2/core/core.hpp"
3 | #include "opencv2/imgproc/imgproc.hpp"
4 | #include "opencv2/highgui/highgui.hpp"
5 | #include
6 | #include
7 | //#include
8 | using namespace std;
9 | using namespace cv;
10 | std::complex subDFT(Mat& I, int m, int n);
11 | Mat DFT(Mat& I);
12 | std::complex subDFT(Mat& I, int m, int n)
13 | {
14 | int nRows = I.rows;
15 | int nCols = I.cols;
16 | std::complex i(0,1);
17 | double pi = std::acos(-1);
18 | Mat I2(nRows,nCols ,CV_32FC1, 1);
19 | std::complex sum(0,0);
20 | for (int r = 0; r < nRows; ++r)
21 | {
22 | for (int c = 0; c < nCols; ++c)
23 | {
24 | auto pix = I.at(r,c);
25 | sum += double(pix) * std::exp(-2.0 * i * pi * double((((r * m) /nRows) + ((c * n) /nCols ))));
26 | }
27 | }
28 | return sum;
29 | }
30 | Mat DFT(Mat& I)
31 | {
32 | int nRows = I.rows;
33 | int nCols = I.cols;
34 | const int channels = I.channels();
35 | std::complex B[nRows][nCols];
36 | for (int r = 0; r < nRows; ++r)
37 | {
38 | for (int c = 0; c < nCols; ++c)
39 | {
40 | B[r][c] = subDFT(I, r, c);
41 | }
42 | }
43 | return cv::Mat(nRows, nCols, CV_64FC2, &B);
44 | }
45 | int main(int argc, char** argv)
46 | {
47 | if (argc != 2)
48 | {
49 | printf("usage: main \n");
50 | return 1;
51 | }
52 | Mat image;
53 | image = imread(argv[1], CV_LOAD_IMAGE_GRAYSCALE);
54 | if (!image.data)
55 | {
56 | cout << "Could not open or find the image" << std::endl;
57 | return 1;
58 | }
59 |
60 | auto res = DFT(image);
61 | namedWindow("Display Window", CV_WINDOW_AUTOSIZE);
62 | imshow("Display Window", res);
63 | waitKey(0);
64 | return 0;
65 | }
66 |
--------------------------------------------------------------------------------
/src/cpu/knn.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "knn.hh"
3 |
4 |
5 | // Applies a gaussian convolution
6 | std::valarray gauss_conv(cv::Mat image, int x, int y, int conv_size, double h_param)
7 | {
8 | std::valarray rgb = {0, 0, 0};
9 | std::valarray cnt = {0, 0, 0};
10 | int cx = 0;
11 | for (int j = y - conv_size; j < y + conv_size; j++)
12 | {
13 | for (int i = x - conv_size; i < x + conv_size; i++)
14 | {
15 | if (i >= 0 and j >= 0)
16 | {
17 | auto ux = image.at(x, y);
18 | auto uy = image.at(i, j);
19 | double c1 = std::exp(-(std::pow(std::abs(i + j - (x + y)), 2)) / (float)std::pow(conv_size, 2));
20 | double h_div = std::pow(h_param, 2);
21 |
22 | std::valarray c2 = {std::exp(-(std::pow(std::abs(uy[0] - ux[0]), 2)) / h_div),
23 | std::exp(-(std::pow(std::abs(uy[1] - ux[1]), 2)) / h_div),
24 | std::exp(-(std::pow(std::abs(uy[2] - ux[2]), 2)) / h_div)};
25 |
26 | std::valarray c = {c1, c1, c1};
27 |
28 | rgb[0] += uy[0] * c1 * c2[0];
29 | rgb[1] += uy[1] * c1 * c2[1];
30 | rgb[2] += uy[2] * c1 * c2[2];
31 |
32 | cnt[0] += c1 * c2[0];
33 | cnt[1] += c1 * c2[1];
34 | cnt[2] += c1 * c2[2];
35 | }
36 | }
37 | }
38 | if (cnt[0] != 0 and cnt[1] != 0 and cnt[2] != 0)
39 | {
40 | rgb[0] /= cnt[0];
41 | rgb[1] /= cnt[1];
42 | rgb[2] /= cnt[2];
43 | }
44 | return rgb;
45 | }
46 |
47 |
48 | // Hat function of the K-Nearest Neighbors algorithm
49 | // Removes the noise of an image
50 | cv::Mat knn(cv::Mat image, int conv_size, double h_param)
51 | {
52 | auto knn_img = image.clone();
53 | for (int y = 0; y < image.cols; y++)
54 | {
55 | for (int x = 0; x < image.rows; x++)
56 | {
57 | auto gc = gauss_conv(image, x, y, conv_size, h_param);
58 | //auto gc = image.at(y, x);
59 | knn_img.at(x, y)[0] = gc[0];
60 | knn_img.at(x, y)[1] = gc[1];
61 | knn_img.at(x, y)[2] = gc[2];
62 | }
63 | }
64 | return knn_img;
65 | }
66 |
67 |
68 | // Applies a gauss convolution given a location and a grey image
69 | double gauss_conv_gray(cv::Mat image, int x, int y, int conv_size, double h_param)
70 | {
71 | double rgb = 0;
72 | double cnt = 0;
73 | int cx = 0;
74 | for (int j = y - conv_size; j < y + conv_size; j++)
75 | {
76 | for (int i = x - conv_size; i < x + conv_size; i++)
77 | {
78 | if (i >= 0 and j >= 0)
79 | {
80 | auto ux = image.at(x, y);
81 | auto uy = image.at(i, j);
82 | double c1 = std::exp(-(std::pow(std::abs(i + j - (x + y)), 2)) / (float)std::pow(conv_size, 2));
83 | double h_div = std::pow(h_param, 2);
84 |
85 | double c2 = std::exp(-(std::pow(std::abs(uy - ux), 2)) / h_div);
86 |
87 | rgb += uy * c1 * c2;
88 |
89 | cnt += c1 * c2;
90 | }
91 | }
92 | }
93 | if (cnt != 0)
94 | {
95 | rgb /= cnt;
96 | }
97 | return rgb;
98 | }
99 |
100 |
101 | // Hat function of the K-Nearest Neighbors algorithm on a grey image
102 | // Removes the noise of a grey image
103 | cv::Mat knn_grey(cv::Mat image, int conv_size, double h_param)
104 | {
105 | auto knn_img = image.clone();
106 | for (int y = 0; y < image.cols; y++)
107 | {
108 | for (int x = 0; x < image.rows; x++)
109 | {
110 | auto gc = gauss_conv_gray(image, x, y, conv_size, h_param);
111 | knn_img.at(x, y) = gc;
112 | }
113 | }
114 | return knn_img;
115 | }
116 |
--------------------------------------------------------------------------------
/src/cpu/knn.hh:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "opencv2/core/core.hpp"
4 | #include "opencv2/imgproc/imgproc.hpp"
5 | #include "opencv2/highgui/highgui.hpp"
6 |
7 | cv::Mat knn(cv::Mat image, int conv_size, double h_param);
8 | cv::Mat knn_grey(cv::Mat image, int conv_size, double h_param);
9 |
--------------------------------------------------------------------------------
/src/cpu/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | using namespace std;
5 | using namespace cv;
6 |
7 | #include "opencv2/core/core.hpp"
8 | #include "opencv2/imgproc/imgproc.hpp"
9 | #include "opencv2/highgui/highgui.hpp"
10 | #include "edge_detect.hh"
11 | #include "non_local_means_cpu.hh"
12 | #include "knn.hh"
13 |
14 |
15 | int main(int argc, char** argv)
16 | {
17 | if (argc < 3)
18 | {
19 | printf("usage: main \n");
20 | return 1;
21 | }
22 | string func_name = argv[2];
23 | if (func_name == "conv" and argc < 4)
24 | {
25 | printf("usage: main conv \n");
26 | return 1;
27 | }
28 | if (func_name == "knn" and argc < 5)
29 | {
30 | printf("usage: main \n");
31 | return 1;
32 | }
33 | if (func_name == "nlm" and argc < 6)
34 | {
35 | printf("usage: main nlm \n");
36 | return 1;
37 | }
38 | Mat image;
39 | image;
40 | if (func_name == "edge_detect")
41 | image = imread(argv[1], 0);
42 | else
43 | image = imread(argv[1], CV_LOAD_IMAGE_UNCHANGED);
44 | if (!image.data)
45 | {
46 | cout << "Could not open or find the image" << std::endl;
47 | return 1;
48 | }
49 | Mat res;
50 |
51 | if (func_name == "nlm")
52 | res = non_local_means_cpu(image, stoi(argv[3]), stoi(argv[4]), stod(argv[5]));
53 | else if (func_name == "knn")
54 | res = knn(image, stoi(argv[3]), stof(argv[4]));
55 | else if (func_name == "conv")
56 | res = convolution(image, stoi(argv[3]));
57 | else if (func_name == "edge_detect")
58 | {
59 | res = knn_grey(image, 2, 150.0);
60 | res = conv_with_mask(res, 1);
61 | }
62 | namedWindow("Display Window", CV_WINDOW_AUTOSIZE);
63 | imshow("Display Window", res);
64 | waitKey(0);
65 | imwrite("output.jpg", res);
66 | return 0;
67 | }
68 |
--------------------------------------------------------------------------------
/src/cpu/non_local_means_cpu.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "knn.hh"
3 |
4 | using namespace std;
5 | using namespace cv;
6 |
7 | // Applies a convolution given two points
8 | std::valarray conv(Mat image, int x1, int y1, int x2, int y2, int conv_size)
9 | {
10 | std::valarray rgb = {0, 0, 0};
11 | int cnt = 0;
12 | for (int j1 = y1 - conv_size; j1 < y1 + conv_size; j1++)
13 | for (int i1 = x1 - conv_size; i1 < x1 + conv_size; i1++)
14 | {
15 | int i2 = i1 - x1 + x2;
16 | int j2 = j1 - y1 + y2;
17 | if (i1 >= 0 and j1 >= 0 and j2 >= 0 and i2 >= 0 and
18 | i1 < image.cols and j1 < image.rows and
19 | i2 < image.cols and j2 < image.rows)
20 | {
21 | cnt++;
22 | auto pix1 = image.at(j1, i1);
23 | auto pix2 = image.at(j2, i2);
24 | rgb[0] += std::pow(std::abs(pix1[0] - pix2[0]), 2);
25 | rgb[1] += std::pow(std::abs(pix1[1] - pix2[1]), 2);
26 | rgb[2] += std::pow(std::abs(pix1[2] - pix2[2]), 2);
27 | }
28 | }
29 | if (cnt > 0) {
30 | rgb /= cnt;
31 | }
32 | return rgb;
33 | }
34 |
35 | // Applies a convolution given a location and computes the nlm formula
36 | std::valarray gauss_conv_nlm(cv::Mat image, int x, int y, int conv_size, int block_radius, double h_param)
37 | {
38 | std::valarray rgb = {0, 0, 0};
39 | std::valarray cnt = {0, 0, 0};
40 | int cx = 0;
41 | for (int j = y - conv_size; j < y + conv_size; j++)
42 | {
43 | for (int i = x - conv_size; i < x + conv_size; i++)
44 | {
45 | if (i < image.rows and j < image.cols and i >= 0 and j >= 0)
46 | {
47 | auto u = conv(image, y, x, j, i, block_radius);
48 | auto uy = image.at(i, j);
49 | double c1 = std::exp(-(std::pow(std::abs(i + j - (x + y)), 2)) / (float)std::pow(conv_size, 2));
50 | double h_div = std::pow(h_param, 2);
51 |
52 | std::valarray c2 = {std::exp(-u[0] / h_div),
53 | std::exp(-u[1] / h_div),
54 | std::exp(-u[2] / h_div)};
55 |
56 | std::valarray c = {c1, c1, c1};
57 |
58 | rgb[0] += uy[0] * c1 * c2[0];
59 | rgb[1] += uy[1] * c1 * c2[1];
60 | rgb[2] += uy[2] * c1 * c2[2];
61 |
62 | cnt[0] += c1 * c2[0];
63 | cnt[1] += c1 * c2[1];
64 | cnt[2] += c1 * c2[2];
65 | }
66 | }
67 | }
68 | if (cnt[0] != 0 and cnt[1] != 0 and cnt[2] != 0)
69 | {
70 | rgb[0] /= cnt[0];
71 | rgb[1] /= cnt[1];
72 | rgb[2] /= cnt[2];
73 | }
74 | return rgb;
75 | }
76 |
77 | // Hat function for the Non Local-Means algorithm
78 | cv::Mat non_local_means_cpu(cv::Mat image, int conv_size, int block_radius, double h_param)
79 | {
80 | auto nlm_img = image.clone();
81 | for (int y = 0; y < image.cols; y++)
82 | {
83 | for (int x = 0; x < image.rows; x++)
84 | {
85 | auto gc = gauss_conv_nlm(image, x, y, conv_size, block_radius, h_param);
86 | nlm_img.at(x, y)[0] = gc[0];
87 | nlm_img.at(x, y)[1] = gc[1];
88 | nlm_img.at(x, y)[2] = gc[2];
89 | }
90 | }
91 | return nlm_img;
92 | }
93 |
--------------------------------------------------------------------------------
/src/cpu/non_local_means_cpu.hh:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "opencv2/core/core.hpp"
3 | #include "opencv2/imgproc/imgproc.hpp"
4 | #include "opencv2/highgui/highgui.hpp"
5 |
6 | cv::Mat non_local_means_cpu(cv::Mat image, int conv_size, int block_radius, double weight_decay);
7 |
--------------------------------------------------------------------------------
/src/cpu/timer.hh:
--------------------------------------------------------------------------------
1 | #ifndef TIMER_HH_
2 | #define TIMER_HH_
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | using std::chrono::duration_cast;
9 | using std::chrono::nanoseconds;
10 | using std::chrono::steady_clock;
11 |
12 |
13 | struct scoped_timer {
14 | scoped_timer(double & s, std::ofstream & f) : seconds(s), t0(steady_clock::now()),myfile(f) {
15 | }
16 |
17 | ~scoped_timer()
18 | {
19 | seconds = std::chrono::duration_cast
20 | (steady_clock::now() - t0).count();
21 | myfile << seconds << " ms" << std::endl;
22 | }
23 | double & seconds;
24 | steady_clock::time_point t0;
25 | std::ofstream & myfile;
26 |
27 | };
28 |
29 | #endif
30 |
--------------------------------------------------------------------------------
/src/cpu/timer_test.cc:
--------------------------------------------------------------------------------
1 | #include "timer.hh"
2 | #include
3 | #include
4 |
5 |
6 | int main()
7 | {
8 | double t = 1;
9 | std::ofstream myfile;
10 | myfile.open ("time_rec.txt", std::ofstream::out | std::ofstream::app);
11 |
12 | {
13 | scoped_timer timer(t, myfile);
14 | long a = 0;
15 | for(int i = 0; i < 1000; i++)
16 | {
17 | for(int j = 0; j < 1000; j++)
18 | {
19 | a = a + 1;
20 | }
21 | }
22 | }
23 | myfile.close();
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/gpu/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8)
2 | project(gpu_img_proc)
3 | find_package(CUDA REQUIRED)
4 | find_package(OpenCV REQUIRED)
5 | include_directories(${OpenCV_INCLUDE_DIRS} -D WITH_CUDA=ON)
6 | set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS}; "-arch=sm_35; -ccbin /usr/bin/gcc-5 ; -std=c++11; -O2; -DVERBOSE;")
7 | cuda_add_executable(main main.cu kernel.cu kernelcall.cu)
8 | target_link_libraries(main ${OpenCV_LIBS})
9 |
--------------------------------------------------------------------------------
/src/gpu/bench/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8)
2 | project(gpu_img_proc)
3 | find_package(CUDA REQUIRED)
4 | find_package(OpenCV REQUIRED)
5 | include_directories(${OpenCV_INCLUDE_DIRS} -D WITH_CUDA=ON)
6 | set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS}; "-arch=sm_35; -ccbin /usr/bin/gcc-5 ; -std=c++11; -O2; -DVERBOSE;")
7 | cuda_add_executable(main ../kernel.cu ../kernelcall.cu bench_non_local_means_gpu.cu)
8 | target_link_libraries(main ${OpenCV_LIBS})
9 |
--------------------------------------------------------------------------------
/src/gpu/bench/bench_non_local_means_gpu.cu:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #include "timer.hh"
6 | #include "../kernelcall.cuh"
7 | #include "opencv2/core/core.hpp"
8 | #include "opencv2/imgproc/imgproc.hpp"
9 | #include "opencv2/highgui/highgui.hpp"
10 |
11 | using namespace std;
12 | using namespace cv;
13 |
14 |
15 | int main()
16 | {
17 | std::ofstream myfile;
18 | myfile.open ("edge_gpu.txt", std::ofstream::out | std::ofstream::app);
19 | Mat image;
20 | Mat res;
21 | std::string path_image("../../../../pictures/temple01.jpg");
22 | cv::Mat gray;
23 | image = imread(path_image, CV_LOAD_IMAGE_UNCHANGED);
24 | cv::cvtColor(image, gray, cv::COLOR_BGR2GRAY);
25 | if (!image.data)
26 | {
27 | cout << "Could not open or find the image" << std::endl;
28 | return 1;
29 | }
30 | int width = image.rows;
31 | int height = image.cols;
32 |
33 | Rgb* device_dst;
34 | double* device_img;
35 | Rgb* out;
36 |
37 | device_dst = empty_img_device(gray);
38 | device_img = img_to_device_grey(gray);
39 |
40 | double param_decay = 150.0;
41 | float milliseconds = 0;
42 | cudaEvent_t start, stop;
43 | cudaEventCreate(&start);
44 | cudaEventCreate(&stop);
45 | cudaEventRecord(start, 0);
46 | kernel_edge_detect(device_dst, device_img, width, height, 1, 91);
47 | cudaEventRecord(stop, 0);
48 |
49 | cudaThreadSynchronize();
50 | cudaEventElapsedTime(&milliseconds, start, stop);
51 | cudaEventDestroy(start);
52 | cudaEventDestroy(stop);
53 | myfile << milliseconds / 1000 << std::endl;
54 | myfile.close();
55 | }
56 |
--------------------------------------------------------------------------------
/src/gpu/bench/benchs.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import numpy as np"
10 | ]
11 | },
12 | {
13 | "cell_type": "code",
14 | "execution_count": 4,
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "f1 = open(\"build/nlm_gpu1.txt\", \"r\")\n",
19 | "nlm1 = f1.readlines()\n",
20 | "ln = int(len(nlm1)/4)\n",
21 | "nlm = np.array([np.array([nlm1[i]]).astype(np.double) for i in range(len(nlm1))])\n",
22 | "nlm = np.array([nlm[:ln], nlm[ln:2*ln], nlm[2*ln:3*ln], nlm[3 * ln:4*ln]])\n",
23 | "nlm = nlm.reshape(4, ln)\n",
24 | "f1.close()"
25 | ]
26 | },
27 | {
28 | "cell_type": "code",
29 | "execution_count": 6,
30 | "metadata": {},
31 | "outputs": [
32 | {
33 | "data": {
34 | "image/png": "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\nvLw8li1bxrXXXlvqMd9++20eeOABNm7cyLp16wgMPH0e+KeeeoqNGzeycuVKmjdvzoQJE0hISOCT\nTz7ht99+Y+PGjdSpU4fYUqraJk2aVJxgHG8zZ84s931YuHAhvXr1wkeqEmq9dcePM2H7dgY1a8bn\n3buTcskl2Pr1I+WSSyRBeDKttdvfevfurUvaunXrGcvKFBysNZx5Cw6u/D5KeO211/TkyZPPWO7v\n76/z8vK01lrn5eVpf39/rbXW48aN0zExMcXPa9iwodZa6+zsbH3++efrnJwc/dVXX+nRo0eXeczY\n2FjdtWtXPXPmTL1t27bi5Q0aNCi+b7PZ9LXXXqvff/99rbXW//vf/3Tr1q11RESEjoiI0J06ddLT\npk0759ftaMuWLTo0NFTv2LGj1PVn9RkJj3YoL08Hr16tg1av1gdzc60OR9gB63QVz7+1o00iOvr0\nNgkAPz+z3IUcf22bz88MX9GvXz+WLFnCJ598wqhRo8rcfvTo0fTt25dvv/2WoUOH8s4779C/f//T\nnjN9+nQCAwOLq5q01owbN47nnnuu3NgmTZrEihUrzlg+atSo4lKPo927dzNy5EjmzZtH+/bty923\nqNkKtSZy61b25eXxa8+etKhXz+qQRHWqapZxxa3KJQmttY6JMSUHpcxfh1/152LLli26Y8eO+tCh\nQ1prrTMyMrTWWg8bNkzPmzdPa631Bx98oEeMGKG1NiWJzz77rHh7x1//33zzjR4xYoQODAzUueX8\nCktKStI2m01rrfXDDz+sX3nlldP2tWjRIn3ppZeeto/4+HjdoUMHnZ6eXhxnSkpKlV77kSNHdHh4\nuF64cGG5z5OSRO0wNTlZs2KFfnvPHqtDESVQDSUJyxNAZW7VkiScYO7cubpbt246PDxcjxs3Tmut\ndUpKir7qqqt0WFiY7t+/v05NTdVal58k8vLydLNmzfT48ePLPd5zzz2nu3btqiMiIvTgwYOLE1PR\nvvr166eDg4OLq5aefPJJrbXWH3/8sY6IiNBhYWG6V69ees2aNVV63U8//bT28/MrPk5ERERxEnLk\nDp+RcK5vDx3SrFihx23dWvwDRriP6kgSSturPaqbUup8YB4QAGhgttZ6llJqOnAPcND+1Mla6+/K\n21efPn10yZnpEhIS6NKlS7XHLaqPfEY1287sbHqvX0+Qjw+re/XCr04dq0MSJSil1mut+1RlH85s\n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\n",
35 | "text/plain": [
36 | ""
37 | ]
38 | },
39 | "metadata": {},
40 | "output_type": "display_data"
41 | }
42 | ],
43 | "source": [
44 | "import matplotlib.pyplot as plt\n",
45 | "colors =['ro-','bo-','go-','co-','yo-','ko-','wo-','mo-']\n",
46 | "ds = [2,3,4,5]\n",
47 | "labels = ['conv_size = ' + str(ds[i]) for i in range(len(ds))]\n",
48 | "#plt.yscale('log')\n",
49 | "#plt.xscale('log')\n",
50 | "\n",
51 | "plt.xlabel('Radius parameter')\n",
52 | "plt.ylabel('Time in s')\n",
53 | "#plt.ylim([1e-4, 1e4])\n",
54 | "plt.xlim([1, 8])\n",
55 | "radius = [2,3,4,5,6]\n",
56 | "for i in range(len(ds)):\n",
57 | " plt.plot(radius, nlm[i,:], colors[i% len(colors)])\n",
58 | "plt.legend(labels)\n",
59 | "plt.title(\"Time measurement in non local means for Lena 256 * 256 \")\n",
60 | "plt.show() \n"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": 8,
66 | "metadata": {},
67 | "outputs": [],
68 | "source": [
69 | "f1 = open(\"build/nlm_gpu2.txt\", \"r\")\n",
70 | "nlm1 = f1.readlines()\n",
71 | "ln = int(len(nlm1)/4)\n",
72 | "nlm = np.array([np.array([nlm1[i]]).astype(np.double) for i in range(len(nlm1))])\n",
73 | "nlm = np.array([nlm[:ln], nlm[ln:2*ln], nlm[2*ln:3*ln], nlm[3 * ln:4*ln]])\n",
74 | "nlm = nlm.reshape(4, ln)\n",
75 | "f1.close()"
76 | ]
77 | },
78 | {
79 | "cell_type": "code",
80 | "execution_count": 9,
81 | "metadata": {},
82 | "outputs": [
83 | {
84 | "data": {
85 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYkAAAEWCAYAAACT7WsrAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XlYVeX2wPHvQhDFAedZQJxFQFOzOcvK1Ept0iTTrMhf\no82lmV6LstukzVdvOXIzsyxTG7ym2WB5tRxAHBEQB5wVZOa8vz/2AREBUTgTrM/znIdz9j5773UG\nztrvsN9XjDEopZRSxfFydQBKKaXclyYJpZRSJdIkoZRSqkSaJJRSSpVIk4RSSqkSaZJQSilVIrdN\nEiIyTkT+7eo4VPmJyHciMtLVcZRGRCaJyDwHHyNBRK5z5DGcSUSGiMgeEUkTke6ujkc5hsuShP2L\nlX+ziUhGoccRxphXjTH3uyo+VTYiMkpEfi3tOcaY/saY2c6KSTnNm8Ajxpjaxpi/y7szEVklIi79\nnxeRWSKSXeT3qZp9XXURWWhP9kZE+pSwj+oiEiciyWU53jnWjxKRvCLx9Cm0/mUR2SwiuSIyqZT9\nfGqPud25YirKZUnC/sWqbYypDSQBNxdaFu2quDyBiHi7OgalgEAg9kI2zP/hdVP/LPz7ZIzJK7Tu\nV+Bu4EAp2z8DHCpppVg+FpFA++NGIjJdRPxK2GRNkXhWFVq3E3gWWFrK8a4A2pYSb6ncubqpoPgv\nIkH2LHivvXh7TETGiEgvEdkkIsdF5P0i24+2Z/NjIvJD/gdSzHEqdN8iMs2+n5Misl5Eriy07mIR\nWWdflyIib9uX9yl61lG4asL+XiwUkXkichIYJSJeIvK8iOwSkSMiskBEGjjoNRn79jvs235g/6J3\nBj4GLrWf4Rwv4T0uOEO0nxn9KiJv2o+1W0T6l/A1yH8fnrbHfUJEPheRGoXWPyAiO0XkqIgsFpEW\n54q7pGMVOe4tIhJr326V/bXmr2stIl+JyCH7e/++fXlbEfnJvuywiESLSL0yHm+WiHwoVtVcmoj8\nJiLNRGSq/X3aKoWqdESkhYh8aY9ht4g8VmjdxSKyxh77fhF5X0Sql+V9EZF2IvKz/b0+LCKfFxOr\nr4ikAdWAjSKyy768s/29Om5/724p8vo+EpFlInIKuKYs70uh7S8Rkd/t+94oZ55NrxLrjPo3EUkV\nkR9FpFGh9V+IyAH7a1otIiHnc+x8xphsY8xUY8yvQF5xzxGRNlhJ5LVS9mPs6/8BXAl8CLxvjEm/\ngJhmG2O+A1JLiMcbeA949Hz3XfggLr8BCcB1RZZNAubZ7wcBBusHqQZwA5AJfA00AVoCB4Gr7c8f\nhJVhOwPewIvA7yUcu0L3jfUFaWhf9xTWGUcN+7o1wAj7/drAJfb7fYDkkt4T+3uRAwzGSuw1gceB\nP4BWgC/wL+AzB70mAywB6gEBWGdJN9rXjQJ+Pcfnuwq4v9Dzc4AHsH5k/g/YB0gp3421QAugARAH\njLGvuxY4DFxkfw/eA1aXJe5ijjOJ09+3DsAp4HrAB+tMbSdQ3R7zRuAdoJb9/b3Cvl07+za+QGNg\nNTC1tO95oXWz7K+lh32fPwG7gXvsx3wFWGl/rhewHnjJHlMwEA/0s6/vAVxi/yyD7O/Z2DJ+np8B\n4+3HKHhtJcRsgHb2+z7292icPaZrsX64OhZ6fSeAy/P3Xdr3pMjylsARYIB92+vtjxsX2m6X/XOr\naX88pdD2o4E69s9lKrChlNc0Czhqv60HbivheclAn2KWLwGGUMz/dJHnBQIz7Z/b50DXEp43Cuu7\neBjYDkwAvIt53jxgUjHLnwGmFf28zufm8gRR0j8PxSeJloXWHwGGFnr8Zf4/AvAdcF+hdV5AOhBY\nzLEdtm/7+mNAuP3+aqyzh0ZFnnPWF4qzk8TqIuvjgL6FHjfH+vHN/2GosNdk39cVhdYvAJ4v9CU+\n3ySxs9A6P/v+m5Xy3bi70ON/Ah/b73+CVTWQv662/T0IOlfcxRyn8PdtArCgyPux1/45XYr1o3rW\nP2ox+xwM/F3a97zQulnAjEKPHwXiCj0OBY7b7/cGkops/wIws4R9jwUWFXpc2uc5B5gOtCrD6yuc\nJK7EOiHyKrT+M+w/XPbXN6es35Miy58D5hZZ9gMwstB2LxZa9xDwfQnHqGeP27+E9Rdx+iRvAFai\nu7yY552VJLCSw3f2+30oIUkAgnUCF2h/XxrZ33O/Yp4bDLSxfwdDgS3AC8U876wkAbTGStz+RT+v\n87m5bXVTCVIK3c8o5nFt+/1AYJq9aHoc66xAsM5IHLpve9VInL1oexzwx/oSANyHdbazVUT+JyI3\nleVF2+0p8jgQWFQojjisInDTin5NdoXrYNMLbXshCvZlThexS9tfScduASQW2lcaVjIsb9xF92vD\nev9bYv3jJRpjcotuJCJNRWS+iOwVq1pwHqc/+7I4n8+rRf7nZf/MxmH/7EWkg4gssVexnAReLSaO\nkt6XZ7E++7X2KqPRZYy9BbDH/l7lS+TMz6Lod7isAoE7irzeK7BOjPIV+3pEpJqITBGrWvYkVqKG\nEj4XY8xfxpgjxphcY8wyIBq49VwBikgtrBOYx871XGMZY4xJtD8+bIyJNMVUNxlj4o0xu40xNmPM\nZmAycPu5jmE3FZhsjDlRxucXy9OSRFntAR40xtQrdKtpjPndkfsWq/3hWeBOoL4xph5WEVsAjDE7\njDF3YVX5vA4stH+5TmGdUQMFjXqNixzXFBNH/yJx1DDG7K3I11SGbYvG5Uz7sH5AgIJ/1IZYZ/0V\nuV/BSg57sd6rACm+88CrWO9HqDGmLlbVY5naQM7THmB3kc+rjjFmgH39R8BWoL09jnFljcMYc8AY\n84AxpgXwIPChlK1HzD6gtYgU/k0J4MzP4kK/K3uwShKFX28tY8yUMmw7HKs69TqsE7Yg+/Kyfi6m\njM9tb9/3LyJyAPgKaG5P1EElbWSMGVXGOM43HoC+wBv2GPKT6BoRGX4+B6ysSeJj4IX8BioR8ReR\nO5yw7zpALvbqCBF5Caibv6GI3C0ije1nW/mNvDasusYaIjJQRHyw2gR8yxBHlJzuIdFYRAY54DWd\nSwrQqnDDqBN9BtwrIt1ExBfrR/pPY0xCOfe7ABgoIn3tn8dTQBbwO1b7yH5giojUEpEaInK5fbs6\nQBpwQkRaYtUHO8JaIFVEnhORmvaz5a4i0qtQHCeBNBHphNXuUyYicoeItLI/PIb1o2QrZZN8f2Kd\nwT8rIj72huWbgfllPbadt/09zb/5YJXIbhaRfvbXWkOszh6tzrUzrPciC6uE6Yf1HSmRiNwuIrXF\n6hhyA1aiX1xova+c7jhR3R6LADFYJxLd7Lf7sf43unHhJShEpL+I5JcQO2FVhX5TaL2PPR4vTr93\n+T3HOgDhhWIC6zNZdD4xVMokYYxZhHWmPt9exIwBSuxBU4H7/gH4HutHPxGrsbjwF+RGIFasniHT\ngGHGmAx7cfAh4N9YZ16nsOo8SzMN68v7o4ikYjVi93bAazqXn7C6QR4QkcMXcvwLZYz5L9Y/zZdY\nP9xtgWEVsN9tWD8O72E1GN6M1UU721jdIW/GaqROwvqchto3/QdWnfYJrC6JX5U3lhLiywNuwvrH\n322P8d9YZ8oAT2OdQacCM7AaRsuqF/Cn/Tu6GHjcGBNfhpiysd6X/vZ4PgTuMcZsPY9jg1UKyih0\nm2mM2YNVGhiHdQK2BysBl+X3aw7W/+JerPr8P87x/Mftzz0OvAE8YM7scrrNHldLrP/3DKy2u1x7\nKeyAMeYAVpWtzf642J5QZdQX2CRWj7BlWN+pwoluhj2Gu7A6HGQAIwCMMQeLxARw2BiTcT4BiL1B\nQymllDpLpSxJKKWUqhiaJJRSSpVIk4RSSqkSaZJQSilVIo8YKK5Ro0YmKCjI1WEopZRHWb9+/WFj\nTNFrrs6LRySJoKAg1q1b5+owlFLKo4hI4rmfVTqtblJKKVUiTRJKKaVKpElCKaVUiTyiTaI4OTk5\nJCcnk5mZ6epQVDFq1KhBq1at8PHxcXUoSqly8NgkkZycTJ06dQgKCkLKNtmYchJjDEeOHCE5OZk2\nbdq4OhylVDl4bHVTZmYmDRs21AThhkSEhg0baimvCotOSSFozRq8Vq0iaM0aolNSzr2RckseW5IA\nNEG4Mf1sqq7olBQit20j3WaNMJ6YlUXktm0ARDRtWtqmyg15bElCKeWexsfHFySIfOk2G+Pjzzni\nuHJDmiSUUhUqKSvrvJYr91Z1kkR0NAQFgZeX9Tc62tURVZjLLrvMqcdbvnw5PXr0IDQ0lB49evDT\nTz859fjKvbXyLX5SxYASliv35tFtEmUWHQ2RkZBun2c8MdF6DBAR4bq4Ksjvv1fE1N1l16hRI779\n9ltatGhBTEwM/fr1Y+/e8k4rrSoDYwytqldnT5FSg5+XF1HBwS6KSpVH5ShJjB0LffqUfLvvvtMJ\nIl96urW8pG3Gjj3nYefMmUNYWBjh4eGMGDECgISEBK699lrCwsLo27cvSUlJAIwaNYrHHnuMyy67\njODgYBYuXAjAsGHDWLp0acE+R40aVbCuqNjYWC6++GK6detGWFgYO3bsAKB27doAvPTSS3Tr1o1u\n3brRsmVL7r33XgDmzZtXsN2DDz5IXl55ZlOE7t2706JFCwBCQkLIyMggS6sSFDAtOZk1qanc0agR\ngb6+CBDo68v0jh210dpDVY4kcS4l/YCV44ctNjaWV155hZ9++omNGzcybdo0AB599FFGjhzJpk2b\niIiI4LHHHivYZv/+/fz6668sWbKE559/HoChQ4eyYMECALKzs1mxYgUDBw4s9pgff/wxjz/+OBs2\nbGDdunW0anXmPPCTJ09mw4YNrFq1igYNGvDII48QFxfH559/zm+//caGDRuoVq0a0cVUtT3xxBMF\nCabwbcqUKaW+D19++SUXXXQRvlqVUOWtOnaMp3ftYkijRnweEkLCpZdi69OHhEsv1QThyYwxbn/r\n0aOHKWrLli1nLStRYKAxcPYtMLDs+yji3XffNePGjTtrecOGDU12drYxxpjs7GzTsGFDY4wxI0eO\nNPPmzSt4Xu3atY0xxmRkZJjWrVubzMxM8/XXX5vhw4eXeMzo6GjTpUsXM2XKFLN9+/aC5bVq1Sq4\nb7PZzMCBA82nn35qjDHmvffeM82bNzfh4eEmPDzcdOjQwUycOPGCX3dhMTExJjg42OzcubPY9ef1\nGSmPticjwzT+9VfT6c8/zYmcHFeHo+yAdaacv79Vo00iKurMNgkAPz9ruRMVPtu2Pj9r+Io+ffrw\nww8/8PnnnzNs2LAStx8+fDi9e/dm6dKlDBgwgH/9619ce+21Zzxn0qRJtGrVqqCqyRjDyJEjee21\n10qN7YknnmDlypVnLR82bFhBqaew5ORkhgwZwpw5c2jbtm2p+1aVW5bNxm2xsWTabCwKCaGud9X4\nWakyyptlnHErd0nCGGPmzbNKDiLW30Jn9RciJibGtG/f3hw+fNgYY8yRI0eMMcbcfPPNZs6cOcYY\nY2bOnGkGDx5sjLFKEl988UXB9oXP/pcsWWIGDx5sWrVqZbKysko85q5du4zNZjPGGPPUU0+Zd955\n54x9LV682Fx22WVn7CM2Nta0a9fOpKSkFMSZkJBQrtd+7NgxExYWZr788stSn6cliaohcutWw8qV\n5quDB10diiqCCihJOOyHHWgNrAS2ALHA4/blk4C9wAb7bcC59lUhScIBZs2aZUJCQkxYWJgZOXKk\nMcaYhIQEc80115jQ0FBz7bXXmsTERGNM6UkiOzvb1K9f34waNarU47322mumS5cuJjw83PTr168g\nMeXvq0+fPiYwMLCgamnChAnGGGPmz59vwsPDTWhoqLnooovMmjVryvW6X375ZePn51dwnPDw8IIk\nVJg7fEbKsWbs3WtYudK8sGuXq0NRxaiIJCHGXu1R0USkOdDcGPOXiNQB1gODgTuBNGPMm2XdV8+e\nPU3Rmeni4uLo3LlzRYasKph+RpXb2pMnufLvv+lTrx7LwsKopkOxuB0RWW+M6VmefTis8tAYsx/Y\nb7+fKiJxQEtHHU8p5TwHs7O5LTaWFr6+/KdLF00QlZhTusCKSBDQHfjTvugREdkkIp+KSP0StokU\nkXUisu7QoUPOCNNt/PDDD2d1RR0yZIirw1IKgFybjaFbtnA4J4evQkJoqHOGVGoO74YgIrWBL4Gx\nxpiTIvIR8DJg7H/fAkYX3c4YMx2YDlZ1k6PjdCf9+vWjX79+rg5DqWI9Fx/PquPHmdOpE93r1HF1\nOMrBHFqSEBEfrAQRbYz5CsAYk2KMyTPG2IAZwMWOjEEpVXHmp6TwdnIyj7ZsyYhmzVwdjnIChyUJ\nsSYU+ASIM8a8XWh580JPGwLEOCoGpVTF2ZyWxn3btnGFvz9v6rUxVYYjq5suB0YAm0Vkg33ZOOAu\nEemGVd2UADzowBiUUhXgWE4OQ2Ji8Pf25osuXajuVTVG9FGO7d30K1Bcl4dljjqmUqri2Yzh7rg4\nkrKy+LlbN5rpOF1VSpU5HajE00k4fT6JtWvXFvS6Cg8PZ9GiRU49vnKuyQkJLDt6lGnt2nGpv7+r\nw1FOViUGWank00k4fT6Jrl27sm7dOry9vdm/fz/h4eHcfPPNeOuYPZXOt4cP84/EREY1a8YY+/Dw\nqmqpFCUJF00nUWXnk/Dz8ytICJmZmYheSFUp7UhP5+64OC6qXZsP27fXz7mKqhRJ4lwcMJ1ElZ9P\n4s8//yQkJITQ0FA+/vhjLUVUMmm5uQyJicFHhK+6dqVmtWquDkm5SnkHf3LGrbwD/DlgOgmdT8Ju\ny5YtplevXiYjI6PYdcrz2Gw2c2dMjPFaudIstw8iqTwTFTDAX5UoSURFWdNHFOaC6STKNJ/E0KFD\nS9x++PDhLF68mJo1azJgwAB++umns55T0nwSGzZsYMOGDWzbto1Jkyadtd2FzkzXuXNnateuTUyM\nXu5SWby1Zw8LDh3iteBgrmvQwNXhKFcrb5Zxxq0ihgqv4OkkqvR8EvHx8SbHPvtYQkKCad68uTl0\n6NBZz9OShOdZcfSo8Vq50tweE1PwXVOeC52ZruwiIiq2J1NISAjjx4/n6quvplq1anTv3p1Zs2bx\n3nvvce+99/LGG2/QuHFjZs6cec593XDDDYwYMYJBgwZRvXr1Ep+3YMEC5s6di4+PD82aNWPcuHFn\nrH/77bfZu3cvF19sjXRyyy23MHnyZF555RVuuOEGbDYbPj4+fPDBBwQGBl7wa//111+ZMmUKPj4+\neHl58eGHH9KoUaML3p9yD0mZmQzdsoVOfn582rGjNlQrAMfNJ1GRdD4Jz6SfkefIzMvjyg0b2J6e\nztoePehYtH5WeSS3nk9CKeUZjDE8tGMH61JT+aZrV00Q6gyaJNzQDz/8wHPPPXfGsjZt2uiVzcoh\npu/fz8wDB5gQGMgtWm2oitAk4YZ0PgnlLGtOnODRHTvo36ABE4OCXB2OckNVogusUupsB7KyuD02\nlta+vkR37qxTkKpiaUlCqSoox2bjzi1bOJabyx8XXUR9nYJUlUCThFJV0DO7dvHLiRNEd+5MmH3s\nL6WKo9VNSlUx8w4cYNrevYxt1YrhTZu6Ohzl5qpMkojeHE3Q1CC8/uFF0NQgojdXngklnD2fRL6k\npCRq167Nm2++6ZLjq/O3ITWVyO3budrfn38GB7s6HOUBqkR1U/TmaCK/jSQ9xxovPPFEIpHfWhNK\nRIR6/oQSzp5PIt+TTz5J//79XXJsdf6O5uRwa2wsDby9+TwkBB+dglSVQaVIEmO/H8uGAxtKXP9H\n8h9k5Z05Lnh6Tjr3fXMfM9bPKHabbs26MfXGqaUed86cObz55puICGFhYcydO5eEhARGjx7N4cOH\nC4blCAgIYNSoUdStW5d169Zx4MAB/vnPf3L77bczbNgwRowYUTA8+KhRo7jpppu4/fbbzzpebGws\n9957L9nZ2dhsNr788kvat29P7dq1SUtL46WXXmLx4sUAHDp0iBtuuIGZM2cyb9483n33XbKzs+nd\nuzcffvgh1co59PPXX39NmzZtqFWrVrn2o5wjzxiGb9nC3qwsVnfvTtNShn9RqrAqcSpRNEGca3lZ\nVOX5JNLS0nj99deZOHHihb15yukm7t7ND8eO8V779vSuW9fV4SgPUilKEuc64w+aGkTiicSzlgf6\nB7Jq1KoLOuZPP/3EHXfcUTCwXQP7kMpr1qzhq6++AmDEiBE8++yzBdsMHjwYLy8vunTpQkpKCgD9\n+/fn8ccfJysri++//56rrrqKmjVrFnvMSy+9lKioKJKTk7n11ltp3779Wc8xxnD33Xfz5JNP0qNH\nD95//33Wr19Pr169AMjIyKBJkyZnbffOO++U+bVPmjSJJ554omBGPOXevj50iKikJO5r1owHmjd3\ndTjKw1SKJHEuUX2jzmiTAPDz8SOqr3MnlCjLfBLDhg0rcfvhw4fTu3dvli5dyoABA/jXv/7Ftdde\ne8ZzSppP4rXXXis1tieeeIKVK1eetXzYsGEFpZ58f/75JwsXLuTZZ5/l+PHjeHl5UaNGDR555JHS\n3wDldNvS07ln61Z61anD+zoFqboQ5R1r3Bm3CplPYtM8E/hOoJFJYgLfCTTzNpVvQomqPJ9EYRMn\nTjRvvPFGset0PgnXOpmTYzr/+adp/OuvJqmYmQNV5YfOJ1F2EaERFdqTqSrPJ6HcnzGGUVu3sj09\nneXh4bSuUcPVISkPpfNJKIfRz8h1Xk9K4vn4eN5q25YnW7d2dTjKRSpiPokq0btJqapk+dGjjIuP\nZ2jjxjxRpAecUuerylQ3eRKdT0JdqISMDIZt2UKXWrX4pFMnbahW5aZJwg3pfBLqQmTk5XFrbCx5\nxrAoJIRa5bxgUinQJKFUpWCMYcz27fydlsaS0FDa6RSkqoI4rE1CRFqLyEoR2SIisSLyuH15AxFZ\nLiI77H/rOyoGpaqKD/ftY05KCpOCghjYsKGrw1GViCMbrnOBp4wxXYBLgIdFpAvwPLDCGNMeWGF/\nrJS6QL+dOMHYnTu5qWFDJmjXZlXBHJYkjDH7jTF/2e+nAnFAS2AQMNv+tNnAYEfFoFRlt98+BWlQ\njRrM7dQJL22oVhXMKV1gRSQI6A78CTQ1xuy3rzoAFDvriYhEisg6EVl36NChcscQnZJC0Jo1eK1a\nRdCaNUTbx06qDJw9n0RCQgI1a9YsGARwzJgxTj2+smTbbNweG8vJ3FwWhYRQT6cgVQ7g8IZrEakN\nfAmMNcacLNwlzxhjRKTYq/mMMdOB6WBdTFeeGKJTUojcto10mw2AxKwsIrdtAyCiEszM5Yr5JNq2\nbcuGDSUPz64c78mdO/n95Enmd+lCVx1sUTmIQ5OEiPhgJYhoY8xX9sUpItLcGLNfRJoDB8t7nLE7\ndrAhLa3E9X+cPElWkSvL02027tu6lRn79hW7TbfatZlazCirhVXl+SSUa80+cIAP9u3jqVatGFrM\nqL5KVRRH9m4S4BMgzhjzdqFVi4GR9vsjgW8cFUO+ogniXMvLoirPJwGwe/duunfvztVXX80vv/xy\n/m+gumB/paYyZvt2rqlXjyk6BalyMEeWJC4HRgCbRSS/XmIcMAVYICL3AYnAneU90LnO+IPWrCEx\n6+wJhgJ9fVnVvfsFHbMqzyfRvHlzkpKSaNiwIevXr2fw4MHExsZSVyezcbjD2dncGhNDYx8fPu/S\nBW+dglQ5mMOShDHmV6CkrhZ9HXXc4kQFB5/RJgHg5+VFlJPPwirLfBK+vr4Fr6VHjx60bduW7du3\n07NnucYRU+eQZwx3xcVxIDubX7p3p7FOQaqcobxjjTvjViHzSRw4YAJ//93IypUm8PffzbwDB85r\n+6Kq8nwSBw8eNLm5uQUxtWjRouD1F6bzSVSs53ftMqxcaT7Zt8/VoSgPgc4nUXYRTZtWaE+mqjyf\nxOrVq3nppZfw8fHBy8uLjz/+uKC6TTnGl4cOMSUpiQebN2e0TkGqnEjnk1AOo59Rxdhy6hS9//qL\nED8/fu7eHV9th1BlVBHzSVSZkoRSniQ6JYXx8fEkZWVRTYSaIiwMCdEEoZxOk4Qb0vkkqraiF3/m\nGkM28POJE0ToNKTKyTw6SRhjKuWkKpVhPglPqMZ0V+Pj48/oiQfWNT3j4+MrxQgByrN4bNm1Ro0a\nHDlyRH+M3JAxhiNHjlBDz3ovSFIx1/SUtlwpR/LYkkSrVq1ITk6mIgb/UxWvRo0aZ10Rrsqmnrc3\nx3Jzz1oeUOg6G6WcxWOThI+PD23atHF1GEpVqJn793MsN5dqQF6h5a64+FMp8ODqJqUqmwUHD3L/\ntm1cV78+/+7YkUBfXwRr+JjpHTtqe4RyCY8tSShVmSw5fJiIuDgurVuXr7t2pVa1aozSi+aUG9CS\nhFIu9tOxY9weG0t4rVosDQujlg7jrtyIJgmlXGjNiRPcsnkz7WrW5PuwMPy9tXCv3IsmCaVc5O/U\nVPpv2kRzX1+Wh4fTSEd1VW5Ik4RSLrDl1Clu2LSJut7e/Dc8nObavVW5KU0SSjnZrowMrtu4kWrA\nivBwAivhRYfRm6MJmhqE1z+8CJoaRPTms2dDVJ5BK0CVcqLkzEz6bthAls3Gqm7daO/n5+qQKlz0\n5mgiv40kPScdgMQTiUR+GwlARGiEK0NTF0BLEko5SUp2Nn03buRobi4/hIURWru2q0NyiPErxhck\niHzpOemMXzHeRRGp8tCShFJOcDQnhxs2bmRPVhY/hIXRsxLPB550Ium8liv3pklCKQdLzc2l/6ZN\nbE1P59vQUK6sV8/VITnM3I1zMRQ/6GaAf4CTo1EVQaublHKg9Lw8bt68mfWpqSwICeGGSjrNa64t\nl6d/fJp7vr6Hzo06U9O75hnr/Xz8iOob5aLoVHloklDKQbJsNm6LjWX1iRPM6dyZQY0auTokhziW\ncYyB/xnIW2ve4pFej7BxzEZm3DKDQP9ABCHQP5DpN0/XRmsPpdVNSjlArs3G8C1b+P7oUaZ36MDw\nSjo435ZDWxg0fxCJxxOZcfMM7r/ofsDqxaRJoXLQJKFUBbMZw+ht2/jq8GHeaduWB1q0cHVIDvHt\ntm+J+CpaefO8AAAgAElEQVQCPx8/Vo1axWWtL3N1SMoBzlndJCKPi0hdsXwiIn+JyA3OCE4pT2OM\n4eEdO5ibksLkoCDGtm7t6pAqnDGGqNVRDJo/iA4NO7Aucp0miEqsLG0So40xJ4EbgPrACGCKQ6NS\nygMZY3g2Pp6P9+3j2dateTEw0NUhVbhT2acYunAoL658keGhw/nl3l9oVVdnIKzMylLdJPa/A4C5\nxphYEZHSNlCqKno5MZE39+zhoRYtmBIcTGX7N0k4nsDg+YPZfHAzb1z/Bk9d+lSle43qbGVJEutF\n5EegDfCCiNQBbI4NSynP8vaePUxMSOCepk15r337Svfj+XPCz9z+xe3k5OWwdPhSbmx3o6tDUk5S\nliRxH9ANiDfGpItIQ+Bex4allOeYvm8fT+3axW2NGvFJx454VaIEYYzho3Uf8fj3j9OuQTu+GfYN\nHRp2cHVYyonO2SZhjLEZY/4yxhy3Pz5ijNl0ru1E5FMROSgiMYWWTRKRvSKywX4bUL7wlXKt6JQU\nxmzfTv8GDfhPly54e1WeS4+y87IZs2QMDy97mH5t+/HHfX9ogqiCHPmNngUUVyZ9xxjTzX5b5sDj\nK+VQiw4dYmRcHFfXq8eXISFUr0QJIiUthWtnX8v0v6bzwhUv8M2wb/Cv4e/qsJQLOOw6CWPMahEJ\nctT+lXKlH44eZdiWLfSsU4fFXbtSsxLNS/3X/r8YPH8wh9MPM/+2+QztOtTVISkXKtOpj4hUE5EW\nIhKQfyvHMR8RkU326qj6pRwzUkTWici6Q4cOleNwSlWs1cePMyQmhs5+fnwXFkadSjQv9WebP+Py\nTy8H4LfRv2mCUGW6mO5RIAVYDiy135Zc4PE+AtpiNYTvB94q6YnGmOnGmJ7GmJ6NGze+wMMpVbH+\nd/IkN23eTICvLz+Gh1Pfx8fVIVWIPFsez//3eYZ/NZxeLXqxLnId3Zt3d3VYyg2U5RTocaCjMeZI\neQ9mjEnJvy8iM7jwZKOU021OS6Pfpk009PHhv+HhNKle3dUhVYgTmScY/tVwlu1YxpgeY5jWfxrV\nq1WO16bKryxJYg9woiIOJiLNjTH77Q+HADGlPV8pd7E9PZ3rN26kppcXK8LDaVVJ5qXedngbg+YP\nYtexXXw08CPG9Bzj6pCUmylLkogHVonIUiArf6Ex5u3SNhKRz4A+QCMRSQYmAn1EpBtggATgwQsL\nWynnSczM5LqNG7EBq8LDCa5Z85zbeIJlO5Zx15d34VvNlxX3rOCqwKtcHZJyQ2VJEkn2W3X7rUyM\nMXcVs/iTsm6vlDvYn5VF3w0bSM3LY2V4OJ1q1XJ1SOVmjOGN39/g+f8+T3izcL4e+jWB9SrfOFOq\nYpwzSRhj/uGMQJRyN4ezs7lu40YOZGfz3/BwutWp4+qQyi09J537F9/PZzGfMTRkKJ8O+hQ/Hz9X\nh6XcWIlJQkSmGmPGisi3cPaktcaYWxwamVIudCI3l36bNhGfmcmy0FAu8ff8C8n2nNjD4M8H8/f+\nv3n12ld5/ornK90YU6rilVaSmGv/+6YzAlHKXZzKy2PApk1sPnWKr7t25Zr6JV7O4zF+TfqV2xbc\nRkZOBovvWsxNHW5ydUjKQ5SYJIwx6+1/f3ZeOEq5VmZeHoM2b+aPkyf5vEsXBjRs6OqQym3G+hk8\nvOxhguoFsWrkKjo37uzqkJQHqTyXiipVTjk2G3du2cKK48eZ1akTtzdp4uqQyiUnL4ex34/lw3Uf\n0q9tPz677TPq1/T8UpFyLk0SSgF5xjAiLo5vjxzhg/btGdmsmatDKpdDpw5xxxd38HPizzxz2TO8\n1vc1qnlVnvGllPOUOUmIiJ8xJt2RwSjlCjZjiNy2jc8PHeL14GAeatnS1SGVy4YDGxg8fzApp1KY\nN2QeEWERrg5JebCyjN10mYhsAbbaH4eLyIcOj0wpJzDG8MTOnXx64AAvBgbybEB5xq50vS9iv+Dy\nTy8n15bLL/f+oglClVtZRoF9B+gHHAEwxmwE9NJMVSlM2L2bd/fuZWyrVkwOCnJ1OBfMZmxM+GkC\ndy68k/Cm4ayLXEfPFj1dHZaqBMpU3WSM2VOkP3WeY8JRynmmJCYSlZTE/c2b83bbth57zcDJrJPc\n/dXdfLv9W+7rfh8fDPgAX29fV4elKokyDfAnIpcBRkR8sEaFjXNsWEo51vvJybywezd3NWnCxx06\neGyC2HFkB4PmD2L7ke281/89Hu71sMe+FuWeypIkxgDTgJbAXuBH4GFHBqWUI83cv59Hd+5kUMOG\nzO7UiWoe+qP6464fGbpwKNWkGstHLOeaNte4OiRVCZVl7KbDgLZ+qUphwcGD3L9tG9fXr8/8Ll3w\n8cB5qY0xvPPHOzyz/BlCGofwzbBvaFO/javDUpXUOZOEiLQBHgWCCj9fx25SnmbJ4cNExMVxmb8/\ni7p2pYYHzkudmZtJ5LeRzN00l9s638aswbOoXb22q8NSlVhZqpu+xhri+1vA5thwlHKMn44d4/bY\nWMJr1WJJaCi1PDBB7D25lyGfD+F/+/7H5D6TGX/VeLzE80pCyrOUJUlkGmPedXgkSlWg6JQUxsfH\nk5SVRRMfH47l5NChVi1+CA/H39vzBhpYs2cNty64lbTsNBYNXcTgToNdHZKqIsry3zJNRCZiNVgX\nnpnuL4dFpVQ5RKekELltG+k2q+CbkpODAP/XvDkNfXxcG1wZRW+OZvyK8SSdSKJBzQYczzxOYL1A\nlo9YTtcmXV0dnqpCypIkQoERwLWcrm4y9sdKuZ3x8fEFCSKfAf65Zw8PtWrlmqDOQ/TmaCK/jSQ9\nxxoF50jGEbzEi+cuf04ThHK6siSJO4BgY0y2o4NRqiIkZWWd13J3M37F+IIEkc9mbLz6y6tE9oh0\nUVSqqipLq1cMUM/RgShVEfKMoU4JjdIBvu5/FXJOXg6JJxKLXZd0IsnJ0ShVtpJEPWCriPyPM9sk\ntAuscivHcnK4a8sWTubl4S1Crjk9666flxdRwcEujO7cNh7YyL3f3Fvi+gB/zx58UHmmsiSJiQ6P\nQqly2nLqFINiYkjMzGR6hw74VatW0LspwNeXqOBgIpo2dXWYxcrOy+bVX14l6pcoGtRswOO9H2fG\nXzPOqHLy8/Ejqm+UC6NUVVVZrrjW6UuVW/vm8GHujoujlpcXK7t143J/fwC3TQqF/bX/L+795l42\npWwiIjSCaTdOo6FfQ3q17FXQuynAP4CovlFEhOrAB8r5SkwSIvKrMeYKEUnF6hxSsAowxpi6Do9O\nqVLYjOGVxEQmJiTQs04dFoWE0KpGDVeHVSZZuVm8vPplpvw6hSa1mrB42GJu7nhzwfqI0AhNCsot\nlFaSqAVgjKnjpFiUKrO03FxGbt3KV4cPM6JpU/7VoQM1PeQq6rV71zL6m9HEHoplVLdRvH3D2zr3\ntHJbpSUJU8o6pVwmPiODQTExbDl1irfbtmVsq1YeMTx2Rk4GE1dN5K01b9GiTguWDV9G//b9XR2W\nUqUqLUk0EZEnS1ppjHnbAfEoVar/Hj3K0C1bMMD3YWFc36CBq0Mqk9/3/M7ob0az7cg2HrjoAd64\n/g38a/i7Oiylzqm0JFENqI3VBqGUSxljmJqczNO7dtHZz49vQkNpW7Omq8M6p/ScdF786UWm/jGV\nAP8Alo9YznXB17k6LKXKrLQksd8YM9lpkShVgsy8PB7cvp05KSkMadSI2Z06UccDBulbnbia+xbf\nx86jO3mo50NMuW4KdXy1iU95ltKuuC5XCUJEPhWRgyISU2hZAxFZLiI77H+1tU6Vam9WFldt2MCc\nlBT+ERTEwpAQt08QadlpPLrsUa6edTU2Y2PlyJV8MPADTRDKI5WWJPqWc9+zgBuLLHseWGGMaQ+s\nsD9Wqli/nzhBz/XriUtPZ1FICC8FBeHl5g3UP+3+ibCPwvjgfx/weO/H2TRmE32C+rg6LKUuWIlJ\nwhhztDw7NsasBoruYxAw235/NqCD4qti/XvfPvps2EAtLy/+uOgiBjdu7OqQSnUy6yRjloyh75y+\neHt5s/re1Uy9cSq1qtdydWhKlYuzy+1NjTH77fcPACVeEisikUAkQECAjllTVeTYbDyxcycf7NvH\nDfXr81mXLjRw8zkgftz1I/cvvp+9qXt5+tKnmXzNZGr6uH+julJl4bK5D40xhlKuxTDGTDfG9DTG\n9Gzs5meRqmIcys7m+o0b+WDfPp5u3ZqloaFunSCOZx7nvm/uo9+8ftSqXovfRv/GGze8oQkCiI6G\noCDw8rL+Rke7OiJ1oZxdkkgRkebGmP0i0hw46OTjKze1ITWVQTExHMzJYV7nzm4/7tLS7Ut5cMmD\n7E/bz/OXP8/EPhOp4e0ZQ4I4WnQ0REZCun18wsRE6zFAhI404nGcXZJYDIy03x8JfOPk4ys39PnB\ng1z299/YgF+7d3frBHE04yj3LLqHmz67ifo16/Pn/X/y2nWvaYIoZPz40wkiX3q6tVx5HoeVJETk\nM6AP0EhEkrGGHJ8CLBCR+4BE4E5HHV+5vzxjeHH3bqYkJXF53bp82bUrTatXd3VYJfpm6zeMWTqG\nw+mHmXDVBMZfOR5fb/efyMhZjIEVK6ySQ3GSdM4kj+SwJGGMuauEVeXtWqsqgeM5OQyPi+O7o0d5\nsHlz3m3fnupeLmsiK9Xh9MM8+t2jzI+ZT3jTcL6L+I5uzbq5Oiy3kZsLX3wBb7wBf/9ttUMUmWIc\nAO1/4pnc+6okVSlttU8QFJ+ZyUft2zOmZUtXh1SihVsW8tDShzieeZzJfSbz/BXP41PNfRvTnenU\nKfjkE3jnHUhIgI4d4d//Bm9veOihM6uc/PwgSudM8kiaJJRTLTl8mIi4OHy9vPgpPJwr67nn9OkH\nTx3k4WUPs3DLQno078GKe1YQ2jTU1WG5hYMH4b334IMP4NgxuPxymDYNbrrJKkWAlSjGj7eqmAIC\nrAShjdaeSZOEcgpjDK8lJfHi7t10r12bRV27EuCGEwQZY5gfM59Hv3uU1OxUXuv7Gk9f9jTeXvqv\nsmMHvPUWzJoF2dkwaBA88wxcdtnZz42I0KRQWeg3Xzncqbw87t26lS8OHWJ4kybM6NgRPzecIGh/\n6n7+b+n/8c22b+jdsjefDvqULo27uDosl/vjD6u9YdEiqF4dRo6EJ5+0qpdU5adJQjnU7owMBsfE\nEHPqFG8EB/NU69ZuN0GQMYa5m+Yy9vuxZORm8Ob1bzL2krFU83K/ROYsNhssXWolh19+gXr1YNw4\nePRRcOMeysoBNEkoh1l57Bh3xMaSBywLC6OfG04QlHwymQeXPMiyHcu4vPXlfDroUzo07ODqsFwm\nK8u6GO7NNyEuzmpPmDoV7rsPatd2dXTKFTRJqApnjOH9vXt5YudOOvj58U3XrrT383N1WGcwxvDp\n35/y5I9PkpOXw9R+U3nk4keqbOnh+HH417+sBuj9+yE83EoWd9wBbjwyinICTRKqQmXZbPzf9u3M\nPHCAWxo2ZG7nztR1s/kfkk4k8cC3D/Djrh+5OvBqPrnlE9o2aOvqsFxizx6rpDBjBqSmwnXXwezZ\n1l83qxVULuJe/73Ko+3LyuLWmBj+TE3lpcBAJrrZ/A82Y2P6+uk8s/wZjDF8MOADxvQcg5e450V8\njrR5s9Xe8Nln1pXSQ4fC009D9+6ujky5G00SqkL8efIkQ2JiOJmby5chIdzq4pF7ozdHM37FeJJO\nJBHgH8DYS8ayeNtiVias5Lrg65hx8wyC6gW5NEZnMwZWrYJ//hO+/966wO3hh2HsWGukVqWKo0lC\nldvM/fsZs307LX19+eGiiwh1cQtn9OZoIr+NJD3HuuQ38UQiT/zwBL7VfJl+03Tuv+h+t+th5Ui5\nufDll1bJYf16aNIEXnkF/u//wA37Eig3o0lCXbAcm42nd+3i3b176VuvHp+HhNDQDVo5x68YX5Ag\nCmvk14gHejzggohc49QpmDkT3n4bdu+G9u2txul77gE3vI5RuSlNEuqCHM7O5s4tW1h5/DhPtGrF\nP4OD8XaTAfqSThQ/3Oi+1H1OjsQ1Dh2C99+3hs04cgQuucS6UvqWW8ANr2FUbk6ThDpvm9LSGBQT\nw/6sLGZ36sQ9zZq5OiQA4o/FM2nVJEwJEx4G+FfuYUh37rRKDTNnQmamlRSeecYaW6kK1a6pCqZJ\nQp2XLw4eZNTWrdT39uaX7t3pVbeuq0Mi+WQyL//8Mp9u+BRvL28GtBvAyoSVZORmFDzHz8ePqL6V\ncxjStWut9oavvrIG1hsxAp56Cjp3dnVkqjJwj/oB5fZsxvBifDx3btlCt9q1Wdejh8sTREpaCmO/\nH0u7d9sxc8NMIi+KZNdju1gasZQZt8wg0D8QQQj0D2T6zdOJCK08I87lD5vRpw/07g3Ll8Ozz1pD\ndv/735ogVMURY4ovmruTnj17mnXr1rk6jConOiWF8fHxJGVlUcPLiwybjfubN+f99u3xdWH7w9GM\no7zx2xu8u/ZdMnMzGRU+iglXT6gSXVqzs+E//7GGzYiNhVat4Ikn4IEHoE4dV0en3I2IrDfG9CzP\nPrS6SRUrOiWFyG3bSLdPMZZhs+EjQh9/f5cliJNZJ5n6x1TeWvMWqVmpDOs6jEl9JlXKsZaio8+c\nj+HFF625G6ZNg717ITQU5syBYcN02AzlWJokVLHGxccXJIh8OcYwfvduIpzcUJ2ek877a9/n9d9e\n52jGUQZ3GszkPpMr7SRA0dEQGXl6ZrfERKukAHDttVZ1Ur9+2hitnEOThDrL8qNHScrKKnZdScsd\nISs3i+nrpxP1SxQpp1K4sd2NvHzNy/RsUa7Ss9sbN+7MqT/zNWsGK1Y4Px5VtWmSUAV2ZWTw5M6d\nLD5yBG8gt5jnBPj6OjyOnLwcZm+czeSfJ7Pn5B6uCryKL+74gisDr3T4sV1p61Zr1rek4i/zICXF\nqeEoBWiSUEBqbi5RiYm8k5xMdS8vpgQH08zHh4d27DijysnPy4uo4GCHxZFny2N+zHwm/TyJnUd3\ncnHLi/l00Kf0bdO30g6jcfw4fP65lRz++MO62K1mTcjIOPu5AZX7Mg/lpjRJVGE2Y5ibksLz8fEc\nyM5mZNOmvBYcTHN7acHby6ugd1OAry9RwcFEOGBaMmMMi7Yu4qWVLxF7KJawpmEsHraYmzrcVCmT\nQ16eVW00a5Y1JWhmJnTpYl3rcPfd1rrCbRJgDcYXVTkv81BuTpNEFfXnyZM8tmMHa1NT6V2nDt90\n7crFRa57iGja1CFJIZ8xhu92fseElRP4a/9fdGzYkc9v/5zbu9xeKYfv3rHDSgxz5kBysjUl6OjR\ncO+90KPH6YboCPvlHIV7N0VFnV6ulDNpkqhi9mVl8UJ8PHNSUmhevTpzOnUiomlTp8/7sHL3Sl5c\n+SK/7/mdoHpBzBo0i4iwCLy9KtdX8uRJWLDASg6//QZeXlbPpPyxlEoaaC8iQpOCcg+V6z9SlSgz\nL4+pycm8kphIjjG8EBDACwEB1HHyrHF/JP/Biz+9yIrdK2hRpwUfDfyI0d1HU71adafG4Ug2G6xc\naSWGL7+02hc6dYLXX7eqk1q0cHWESpWdJolKzhjD4iNHeHLnTuIzMxnUsCFvtWtH25o1nRrHhgMb\nmLByAku2L6GxX2Pe6fcOD/Z4kJo+zo3DkeLjrcQwe7ZVTeTvDyNHwqhRcPHFel2D8kyaJCqx2FOn\neGLnTpYfO0YXPz9+DAvjeifPMhN3KI6JqybyxZYvqFejHq9e+yqP9n6U2tVdOzFRRUlLg4ULrZFX\nV6+2EsH111ulhkGDrJ5KSnkyTRKV0LGcHCYmJPDh3r3U8fbm3Xbt+L8WLZw630P+sN3Rm6Px8/Fj\nwlUTePLSJ6lXo57TYnAUmw1++cVKDAsXWpP7tG8Pr75qjcDaqpWrI1Sq4rgkSYhIApAK5AG55R2A\nSlnyjGH6vn1M2L2bY7m5PNiiBZODgmhU3Xn1/YWH7fbx8uGpS5/i2cufpZFfI6fF4CgJCVbPpFmz\nrJne6tSB4cOt6qRLL9XqJFU5ubIkcY0x5rALj1+prDp2jMd37mTTqVP0qVePae3aEebEuaZT0lJ4\n7dfX+Hjdx9iMjTE9xjDuynE0r9PcaTE4wqlT1jwNM2dajdEi1vhJL78MQ4ZY1y8oVZlpdZOHS8jI\n4Jn4eBYeOkSgry8LQ0K4tVEjp12EVnjY7qzcLEZ1G8WEqyYQWC/QKcd3BGOs7qozZ1rdV9PSoG1b\nKzGMGAGBnvvSlDpvrkoSBvhRRAzwL2PMdBfF4bFO5eXxelISb+zZgwCTg4J4unVrajppEuOiw3bf\nFXoXk66eRPuG7Z1yfEdISoK5c63qpJ07oXZtuPNOqzrpiiu0OklVTa5KElcYY/aKSBNguYhsNcas\nLvwEEYkEIgECdNCaAsYY5h88yLPx8SRnZTG8SROmBAfTuqSrsipY0WG7b+18K//o8w+6NunqlONX\ntPR0+Pprq9SwYoVVirjmGpgwAW691UoUSlVlLkkSxpi99r8HRWQRcDGwushzpgPTwZqZzulBuqH1\nqak8vmMHv508yUW1azO/Sxcu9/d32PGiN0czfsV4kk4k0bpua64Oupofd/1IyqkU+rfrz8vXvEyP\nFj0cdnxHMcYaTG/mTGtwvZMnISgIJk6Ee+6BNm1cHaFS7sPpA+SISC0RqZN/H7gBiHF2HJ7kYHY2\n92/dSq/169mRkcG/O3ZkbY8eDk8Qkd9GkngiEYMh6WQSczfNpX6N+vxy7y8si1jm1gkiOtr64ffy\nsv5GR1szuk2ZYs3/fNll1rIhQ6wG6V27rCShCaKCFPcBKI/kipJEU2CRvWHVG/iPMeZ7F8Th9rJt\nNt7bu5fJCQmk22w82aoVE4KC8HfCUBrPLX+O9JyzZ75Jz03nioArHH788ihuZrd77rGubwC46ip4\n7jm4/XadF9ohivsAIiOt+zoglccRY9y/Jqdnz55m3bp1rg7DqZYdOcITO3eyPSODAQ0a8Ha7dnR0\ncH/LU9mn+CruK2ZvnM2K3cVPgSYItom2Yte5i9atrVFWi/L3h/XrrZ5KyoECA4ufOSkw0LrYRDmN\niKwv73Vo2gXWzWxLT+fJnTtZdvQoHWrWZGloKAMaNnTY8WzGxurE1czeOJuFWxaSlp1Gm3pt8Pf1\n50TWibOeH+Dvfp0IjIHNm2HpUutWXIIAq+1BE4QDHDoEf/5pNfT88UfJU+uVtFy5NU0SbuJEbi4v\nJyQwbe9e/Ly8eLNtWx5t2ZLqDhpKY+fRnczZOIe5m+aScDyBOtXrcGeXOxnZbSRXBFzBZzGfEflt\n5BlVTn4+fkT1dY+Zb9LTrd5IS5fCsmWwZ4+1vEcPq8Rw4uz8pjO7VYTsbNi48XRC+PNPq0EHrGn1\nwsKsLmFpaWdvqx+AR9Ik4WJ5xjDrwAHGxcdzKCeH+5o355U2bWjqgKE0TmSeYEHsAmZvnM1ve35D\nEK5vez2vXPMKQzoPwc/ndHVWRKhVd5zfuynAP4CovlEFy11h9+7TpYWVKyEry/o9uuEGmDQJ+veH\n5s3PrhIHndntghhjZd/8hPDHH/DXX9YbD9aY55dcAg8+aP3t0cN6o/UDqFS0TcKFfjtxgsd27OCv\ntDQur1uXae3b06OCW1LzbHksj1/O7I2z+Xrr12TmZtK5UWdGho/k7rC7aVm3ZYUeryLl5MDvv1tJ\nYckSiIuzlrdvDwMHWrcrrwT7bKtniI7Wmd3O26lTsG7dmaWE/futdTVqWEngkktO30obyVA/ALdQ\nEW0SmiRcIDkzk2fj4/ns4EFaVq/OG23bMqxJkwodSiP2YCyzN85m3qZ57E/bT4OaDbir612MDB9J\nzxY93Xbu6EOH4LvvrMTwww9WtZGPD1x99enE0N5zL+p2HzYbbN9+Zilh8+bTXcDatTszIYSFWR+E\n8ijacO1hMvLyeHPPHqYkJWEDJgQG8lxAALUqaCiNw+mH+WzzZ8zeOJv1+9fj7eXNgPYDGBk+koHt\nB+LrXcwpt4sZA3//fboaae1aa1mzZlYX1YED4brrtKtquR05Yr25+Qlh7Vo4ftxa5+9vzYo0fryV\nEC6+GBp5/qi9qmJoknCg6JQUxsfHk5SVRUNvb4wxHMnL4/bGjXkjOJigCpiRJjsvm2U7ljF742yW\nbl9Kji2H7s26M7XfVO4KvYsmtZpUwCupWGlp8N//WlVIy5ZZNRoi0KuX1bYwcCB0725dh6UuQE6O\nVSooXErYscNa5+UFXbtag1LllxI6dtQ3W5VIk4SDRKekELltG+n24vvh3FwEGNe6NVHl7IdpjOGv\n/X8xe+Ns/rP5PxzJOELTWk15rPdjjAwfSWjT0Ap4BRVr587TpYWff7Y6ydStC/36WUmhf39o4n75\nzDMkJ5/ZBXXdOsjMtNY1bWolgtGjrb89e+qAVOq8aJJwkPHx8QUJIp8Bog8evOAksS91H9Gbopm9\ncTaxh2LxrebLoE6DGBk+khva3oC3l/t8nNnZ1uxt+Ylh+3ZreadO8NhjVmK4/HKt5i5RSQ2/6enW\nFYGFk8LevdY21avDRRfBmDGnSwkBATp8rSoX9/lVqWSS8rsJlnF5STJyMvh669fM3jib5fHLsRkb\nl7a6lI8HfsydIXdSv2b9igi3Qhw4YFUfLV0Ky5dDaqrV86hPH3jkESsxBAe7OkoPUNywFqNGWUkj\nORny8qzlbdpYY4zkJ4Tw8OK7eilVDpokHCTA15fEYhJCQBn+iY0x/LbnN2ZvmM2CLQs4mXWSAP8A\nXrjiBe4Jv4cODTs4IuTzZrNZJ7VLlliJYf16a3nLlnDXXVZS6NsXatVybZxuLzvbqo+Li7Nur712\n5jUGALm5VhZ+7jkrIfTurfVzyik0SThIVHDwGW0SAH5eXkSVciqdcDyBORvnMGfjHHYd20Utn1rc\n1uU2RoaPpE9QH7zEuY2LxdV43Hwz/PijlRS++w5SUqzajEsusdYPHGj1ltQajmKcOgVbt55OBnFx\nsJ/vvm8AAA2qSURBVGWLdcVybu65t8/O1gvSlNNpknCQiKZNAQp6NwX4+hIVHFywPF9qVioLtyxk\n9sbZ/Jz4MwDXBF3DhKsmcFuX26hd3TWNjCWNpGqMdatXD2680UoKN96oPSbPcOTImUkg/37hsYuq\nVbOuRejc2ZrdqEsX637Hjlbvo8TEs/erw1ooF9Ak4UARTZuelRTAugp6ZcJKZm+czVdxX5Gek077\nBu15+ZqXGRE2wqXzQ2dkWNctPPro2TUeNpvVI2nJErj0UnDCiOXuyxirfaBwqSD/dujQ6efVrGm1\n1l9xhZUE8m/t2lkNzcWJitJhLZTbqMr/5g5XeGa3AP8AHrn4EY5mHGXuprkkn0zG39efEWEjGBk+\nkktaXeL0q6Dz8qwT3f/9z7q2au1aq3t9aTUfqanWUBhVRm4uxMcXnwwKD2JXv77143/LLWcmg8DA\n878GIX/4Ch3WQrkBHZbDQfJnditu4p78q6Bv6XgLNbydMze1MVYNxtq1p5PC+vVWNTlYF9326mVd\nbHvxxfDww6d7VhbmMVMCnO/YQRkZVj/dolVEO3ZYbQH5WrSwfvzzq4fyb02aaEOMcjs6LIcbG79i\nfLEJolWdViwdvtThxz98+HQyyP+bXwvi62td0Tx69Omk0K7dmSe8aWkeXONR2sxoAwcWXyrYvdvK\npGC9EcHB1o//wIGnE0GnTlY2VaoK0SThIEknip9gZW9qMafn5ZSebo3gnF9l9L//WTUkYJ3cdukC\nN910uqQQGlpydXg+j67xGDfu7AaV9PQz5zAFK1t26GBdhTxixOkSQvv21qinSilNEo4S4B9A4omz\ne6iUd2a33FyIjT2dENautR7nX18VEGAlgjFjrKTQo8eFD44XEeGmScFms64ZiI+3SgBF/5Y0NZ3N\nBq+/frpk0KaN1ctIKVUiTRIOEtU3qtwzuxlj/e4Vblj+6y+r+hygQQMrEQwaZCWGXr2soXoqhZMn\nS04Cu3efnvgGrOJSy5bWj37fvvD118VPTRcYCM8+67zXoFQloEnCQS5kZreDB89sQ1i7Fo4etdbl\nz/ny4IOn2xGCgz24rTQnx6rHKikRHDly5vP9/a0XnF93FhxsJYXgYOvHv/CV7DozmlIVRpOEI22K\ngKkRkAQEAI0B+wCtaWlW76LCSSH/+qn80ZxvvfV0O0JIiIcNhmeMlfVKSgJ79pzZPuDjA0FB1g9/\nz56nE0D+3/rnMUaVRzeoKOVetAusgxR3Mlu9unUR2pEjVi/L/N/INm1Olw569bIG8nSL8Y7O1Y30\n1KniE0D+36KNx82anfnDn/83ONjqWqrtA0pVKJ2+1I0FBRU/soKXlzWMRX5S6NkTGjd2enjnFh0N\nDzxwugEErEuse/Wystvu3VZJobDatYtPAm3aWG+In59TX4JSVZ1eJ+HGkorvAYsx1uB4LpOWZvUM\nSkkp/RYff/q6gXy5uVa9WJ8+1pXFRRNBo0Ye3EiilCqOJgkHCQhw0hhtxlg9gc71o59/K1oFlK9h\nQ6trVNOmVmlh167in2ezWXOPKqWqBE0SDhIVBZGjc0nPPv0W+1XPJSqqDG+5MXDsWNl/+IubyEjE\nqsfK/+Fv2/b0/aK3xo3PbhVfs0ZHIlVKaZJwlAiiwfyX8UwkiQACSCLKTCIiuTOs6Fn6j/7Bg1YX\n0aKqVbPGCMr/ce/cueQf/kaNytcQrCORKqXQhmvHKanluigfn5J/6IveGjQ4/xFFy+N8B8lTSrkV\nbbh2ZyW1XIvAypWnf/jr1XPfxl63HZdDKeUszp0P005EbhSRbSKyU0Sed0UMDldS3X1AAFx9tTWi\naP367psglFIKFyQJEakGfAD0B7oAd4lIF2fH4XBRUWdfF6B1+kopD+OKksTFwE5jTLwxJhuYDwxy\nQRyOFREB06db4wqJWH+nT9fqG6WUR3FFm0RLYE+hx8lA76JPEpFIIBIgwFO7XWqdvlLKw7mkTaIs\njDHTjTE9jTE9G7vluBVKKVX5uSJJ7AVaF3rcyr5MKaWUm3FFkvgf0F5E2ohIdWAYsNgFcSillDoH\np7dJGGNyReQR+P/27j/W6rqO4/jzNcAJSGiTMZQK5x/XTDaUa1mWDVFX/sqKPyr74VZbWyyhcq7W\n5rAtly2dzWbNkLDBkETc/IEiFqYw0+Q3iLQ1STETnTN+RInw6o/P58Td7ZzL+Z7r5XO+8H5sd/ty\nzvd87+sC97y/n8/3e94flgPDgHm2txzpHCGEEA6vyIfpbC8DlpX43iGEENpXi7YcknYD20rnGIST\ngTdKhxiEOuevc3aI/KXVPX+P7TGDOUBd2nJsG2z/kZIkPRf5y6hzdoj8pR0N+Qd7jK69BTaEEEJ5\nUSRCCCG0VJcicWfpAIMU+cupc3aI/KUd8/lrceE6hBBCGXUZSYQQQiggikQIIYSWurpISJonaaek\nzaWzVCXpfZJWSnpe0hZJs0pnqkLS8ZKelbQh57+xdKZOSBomaZ2kh0pnqUrSdkmbJK1/N25lPNIk\nnShpiaQXJG2V9NHSmdolqSf/vTe+dkmaXTpXuyR9J//ebpa0SNLxHR+rm69JSLoA2AP81vZZpfNU\nIWkCMMH2WkljgDXAVbafLxytLZIEjLa9R9IIYBUwy/afCkerRNJ3gV7gPbYvL52nCknbgV7btfww\nl6S7gadsz8192kbZfqt0rqryQmmvAB+x3cbC9WVJOpX0+3qm7X2Sfgcssz2/k+N19UjC9pPAm6Vz\ndML2q7bX5u3dwFbSWhq14GRP/uOI/NW9ZxRNSJoIXAbMLZ3lWCNpLHABcBeA7bfrWCCy6cBf61Ag\n+hgOjJQ0HBgF/L3TA3V1kThaSJoEnA08UzZJNXmqZj2wE1hhu1b5gduA64GDpYN0yMBjktbkRbjq\n5DTgdeA3ebpvrqTRpUN16AvAotIh2mX7FeBnwEvAq8A/bT/W6fGiSAwxSScA9wGzbe8qnacK2wds\nTyGt+fFhSbWZ8pN0ObDT9prSWQbh47bPIa0HPzNPv9bFcOAc4Je2zwb2At8vG6m6PE12JXBv6Szt\nknQSaUno04BTgNGSvtzp8aJIDKE8l38fsND20tJ5OpWnCVYCnyqdpYLzgSvzvP49wIWSFpSNVE0+\nI8T2TuB+0vrwdbED2NFn9LmEVDTq5tPAWtuvlQ5SwUXAi7Zft70fWAp8rNODRZEYIvnC713AVtu3\nls5TlaRxkk7M2yOBi4EXyqZqn+0f2J5oexJpuuAPtjs+mzrSJI3ONzyQp2kuAWpzl5/tfwAvS+rJ\nD00HanHTRj9fpEZTTdlLwHmSRuX3oemka6Id6eoiIWkR8DTQI2mHpK+XzlTB+cBXSGewjdvoLi0d\nqoIJwEpJG0mrCa6wXbvbSGtsPLBK0gbgWeBh248WzlTVt4GF+f/QFOCmwnkqycX5YtKZeG3k0dsS\nYC2wifQ+33F7jq6+BTaEEEJZXT2SCCGEUFYUiRBCCC1FkQghhNBSFIkQQggtRZEIIYTQUhSJ0HUk\nHci3DG+W9GDj8xoVXj9H0nV5+0eSLhqapOVJukbSKaVzhKNXFInQjfbZnpI7/74JzOz0QLZvsP34\nuxetutxkbahcQ2q90LYhzhOOMlEkQrd7mtw9V9IJkn4vaW1eZ+EzjZ0k/VDSXyStAnr6PD5f0oy8\nvV3SyXm7V9ITefuTfT7wuK7xSec+x5iU10RYmNdFWCJpVH7uBkl/zqOeO/MnXJH0hKTb8joQsyRd\nIemZfPzHJY3P+82RdLekpyT9TdLnJP00/3yP5tYuSJoq6Y+52d9ySRPyz9VL+sDaekkjm+3XLM8Q\n/DuFo1QUidC1ch//6cAD+aF/A5/NTe+mAbcomUpqvTEFuBQ4t+K3ug6YmZsZfgLY12SfHuAO2x8E\ndgHfyo//wva5edQzEui7ZsVxtntt30Lq739ebnZ3D6k7bcPpwIWkRnILgJW2J+ccl+VCcTsww/ZU\nYB7wY9tLgOeAq3P2d5rt1yJPCG2JYWfoRiNzi/JTST1nVuTHBdyUu6EezM+PJ72x32/7XwCSHvj/\nQw5oNXCrpIXAUts7muzzsu3VeXsBcC2pHfM0SdeTeva/F9gCPJj3W9zn9ROBxfnM/jjgxT7PPWJ7\nv6RNwDCg0X5jEzCJVKDOAlbkgcowUgvo/g633+ImrwlhQDGSCN1oXz4z/gCpMDSuSVwNjAOm5udf\nA6osy/gOh/7P/+91tn8CfIM0Elgt6Ywmr+3fv8ZKS0LeQTpznwz8ul+evX22byeNOiYD3+y3339y\njoPAfh/qlXOQdCInYEu+TjPF9mTblzTJeLj99jZ5TQgDiiIRulYeGVwLfC9fbB1LWiNiv6RppCIC\n8CRwVZ6THwNc0eKQ24GpefvzjQclnW57k+2bSc0MmxWJ9+vQGs1fIk0fNd7o31BaN2TGAD/OWNIS\nmABfG2C/ZrYB4xrfX9IISR/Kz+0GxrSxXwgdiSIRuprtdcBGUsvmhUBvnpb5Krl1eV4mdjGwAXiE\n9EbfzI3Az/PF2wN9Hp+dLzxvBPbnY/S3jbTwz1bgJNJiOm+RRg+bgeUDfF+AOcC9ktYAldastv02\nqQDdnLvCrufQ+gDzgV/l6blhA+wXQkeiC2wIh6G0/OxD+eJ0CMeUGEmEEEJoKUYSIYQQWoqRRAgh\nhJaiSIQQQmgpikQIIYSWokiEEEJoKYpECCGElv4L2xx5Ew+8S8EAAAAASUVORK5CYII=\n",
86 | "text/plain": [
87 | ""
88 | ]
89 | },
90 | "metadata": {},
91 | "output_type": "display_data"
92 | }
93 | ],
94 | "source": [
95 | "import matplotlib.pyplot as plt\n",
96 | "colors =['ro-','bo-','go-','co-','yo-','ko-','wo-','mo-']\n",
97 | "ds = [2,3,4,5]\n",
98 | "labels = ['conv_size = ' + str(ds[i]) for i in range(len(ds))]\n",
99 | "\n",
100 | "\n",
101 | "plt.xlabel('Radius parameter')\n",
102 | "plt.ylabel('Time in s')\n",
103 | "\n",
104 | "plt.xlim([1, 8])\n",
105 | "radius = [2,3,4,5,6]\n",
106 | "for i in range(len(ds)):\n",
107 | " plt.plot(radius, nlm[i,:], colors[i% len(colors)])\n",
108 | "plt.legend(labels)\n",
109 | "plt.title(\"Time measurement in non local means for Lena 514 * 514 \")\n",
110 | "plt.show() "
111 | ]
112 | },
113 | {
114 | "cell_type": "code",
115 | "execution_count": 17,
116 | "metadata": {},
117 | "outputs": [],
118 | "source": [
119 | "f1 = open(\"build/knn_gpu1.txt\", \"r\")\n",
120 | "f2 = open(\"build/knn_gpu2.txt\", \"r\")\n",
121 | "nlm1 = f1.readlines() + f2.readlines()\n",
122 | "ln = int(len(nlm1)/2)\n",
123 | "nlm = np.array([np.array([nlm1[i]]).astype(np.double) for i in range(len(nlm1))])\n",
124 | "nlm = nlm.reshape(2, ln)\n",
125 | "knn = nlm\n",
126 | "f1.close()"
127 | ]
128 | },
129 | {
130 | "cell_type": "code",
131 | "execution_count": 21,
132 | "metadata": {},
133 | "outputs": [
134 | {
135 | "data": {
136 | "image/png": "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\nfmRkJGvXrmXt2rW0aNGCp59+GoDevXvzzjvvsGrVKkaMGMHDDz98zrYXLlyY1Dzje7vhhhtSfU8O\nHTrEjBkzaNq0adK0zz//nJo1a9KhQwf+/NMZZnrz5s0UKVKE9u3bU6dOHfr37098fPxZ29q9ezeD\nBg3ivvvu46677uKRRx5J9bnHjx9Py5Ytkx6fPHmSyMhIrrvuOqZPn540/Y8//uCzzz4jMjKSli1b\nsmXLllS3awJr8cyjPDUoH21CvuazwRvZXr4RCRLG9vKN6PZhM+wUpkxwvrscXt8uuompfPmzm5cS\nb+XLp38bfiQ2WyxcuFCbNWuWNP2mm27SJUuWqKrq/PnztW3btqqq+vPPP+stt9yi1atX18qVK+ut\nt96qqqq1atXSrVu3Jq1ftGhRjY2N1XfeeUcvu+wyrVWrltaqVUsrV66szz33nN8sf//9t1asWFH7\n9u2r3377bVKTSffu3XXq1KlJy02ePFmbNGmicXFxeuTIEc2bN2/S9mvVqqVVqlS5qPck0ZkzZ7RF\nixb6xhtvJE3bv3+/njx5UlVVR48erY0bN1ZV1alTp2qhQoX0jz/+0DNnzmj79u113Lhxfrf74Ycf\n6rZt2zQhISHF5544caLWr18/6blUnSYqVdU//vhDy5cvr7///ruqOr/DESNGqKrq559/rg0aNPC7\nTWtiCryYLcf10lz7tRKb9NDUOV7HyRa4gCamnLcHMXw4hCc7+yE83JmeQfLkyZN0PyQkJOlxSEgI\ncXFxADz66KP07duXX375hffffz/Ni69Ule7duyd989+0aRNDhgzxu2zRokVZt24djRo1YvTo0UnN\nSb7Wr1/PkCFDmDx5MqGhoSQkJFCkSJGk7a9du5aNGzees96F7EH07t2bSpUq8fjjjydNK168eNL7\n0qtXL1atWgVA2bJlqV27NhUrViQsLIw77riD1atX+91ujx49Ur16ft68eQwfPpzo6OizfidlypQB\noGLFijRq1Ig1a9YkPXf79u0BaNeuHT///HOKr8kEzqljcXSot4OjZ/Lw5atbKNzhFq8j5Vg5r0B0\n6wZjxkD58iDi/BwzJtN3Vw8fPpz0QfXRRx8lTb/xxhuZMmUKAHPmzOHgwYMANG3alGnTprFv3z4A\n/v7777POzPG1f/9+EhISuPPOOxk2bNg5H7CHDh2iS5cufPzxx5Qs6YwfUqhQISpUqMDUqVMBpyCt\nW7funG03btz4rCKSePvhhx/8Zhk0aBCHDx/mzWTHcPbs2ZN0Pzo6mqpVqwJw7bXXcujQIWJjYwFY\nsGAB1apV87vt1KxZs4YHH3yQ6Ojos44lHDx4kFNuc+L+/ftZunRp0vbvuOMOFi5cCMB3331H5cqV\nz/t5zUVKSOCJa77jp4NV+PC+77m6fyuvE+Vo2e8gdXp06+Z5++WQIUPo2LEjRYsWpUmTJmzbtg2A\n5557ji5dujBx4kSuv/56SpUqRcGCBSlRogTDhg2jefPmJCQkkCtXLkaOHEn58uXP2fauXbvo2bMn\nCQkJALz00ktnzf/qq6/YsWMHDzzwQNK0tWvXEhUVRZ8+fRg2bBhnzpyhc+fO1KpV64JfY0xMDMOH\nD6dKlSrUrVsXgL59+9KrVy/efvttoqOjCQsLo1ixYkyYMAFwTkEdMWIETZs2TWxSPCtnevXv35+j\nR4/SsWNHAMqVK0d0dDQbN27kwQcfJCQkhISEBAYMGJBUIAYMGEC3bt144403KFCgwFmn3ppMoMqH\nLafw3qbO9L9hKR3Ht0x7HRNQ4jRNZR2RkZGafES5jRs3Jn0DzepOnTpFaGgoYWFh/Pjjj/Tp04e1\nqZ2VZTJddvp7CyarHv2QG9/two1ldzB7a2XCclm3JhlJRFapauT5rJMz9yCC2M6dO+nUqRMJCQnk\nzp2bsSmdemtMNrL/9Y9p/24TLgk/yuSVlaw4BAkrEEGmUqVKSQdN06N+/fpJbeqJJk6cSI0aNTI6\nmjEBETflCzo/VZq9IZexZJ5Q8tKcd2g0WAWsQIjIB0BrYJ+qVvczX4C3gNuA40APVfV/uopJ0bJl\ny7yOYMyFmz+fQV3+YD79Gf/uKSKvz+11IuMjkKV6AtAilfktgUrurTfwXgCzGGOCzYoVfN76A15J\n6M+DPU5yX588aa9jMlXACoSqLgb+TmWRtsDH7jUcPwFFROSyQOUxxgSR335jwy396HHqferXOc1b\no/N6ncj44WVjXxngT5/HMe60c4hIbxFZKSIrE8+PN8ZkUX/+yeGm7Wl39GPCi+VlWnRu8tjOQ1DK\nEkeDVHWMqkaqamTihV3GmCxo/34SbrmV7vte5Q+uYMrnYZQt63UokxIvC8Qu4HKfx2XdaQEXiPFt\nbTwI/xo1asRVV12V1CVH4pXgixcvpm7duoSFhTFt2rSk5deuXcv111/P1VdfTc2aNfnss89S3HZq\n40GkNt5EixYtKFKkCK1btz5rnW7dunHVVVdRvXp17rvvPs6cOXOxL9/4OnIEbruNl//owFdxrRkx\nQmjY0OtQJlXn23nT+dyACGB9CvNaAd8CAlwHLE/PNi+2s74ADQdh40GkoGHDhrpixYpzpm/btk3X\nrVun99xzz1kdCG7atEk3b96sqqq7du3SUqVK6cGDB89aNz3jQaQ23sS8efM0OjpaW7Vqddb0b775\nRhMSEjQhIUE7d+6so0aN8ru+ddZ3AU6eVG3aVGeFtFSRBO3SxRnrwWQegmk8CBGZBDQCSohIDPAc\nkMstSqOBmTinuP6Oc5prT/9bOj8eDwdh40GkU0REBEDS2AuJfPs/Kl26NJdccgmxsbEUKVIkaXp6\nxoNITdOmTVm0aNE502+77bak+/Xq1Utz7AqTTvHx0K0b2+b/QZf8v1G9ojB2rNMVmglugTyLqYuq\nXqaquVS1rKqOV9XRbnHALWqPqOoVqlpDVVemtc2MkAnDQdh4EMn07NmT2rVr88ILL/htDkrJ8uXL\nOX36NFdcccVZ09M7HoS/8SbS48yZM0ycOJEWLVI7S9ukiyr06cPxz2fSvswyNFcevvwS8uf3OphJ\nj2x3JXVa3/QjIsBfJ6jly4OfL5UX5Nprr+Wyy5wzdq+44gqaN28OQI0aNZJ6C42JieGuu+5iz549\nnD59mgoVKgCwZMkSvvzyS8BpKy9atCgA8+fPZ9WqVVx77bUAnDhxIsURzypWrMjWrVt59NFHadWq\nVdLzJ/fZZ5+xevVq5syZw9GjR/nhhx+SOrcDzrlCG/7tzTW9oqKiKFOmDEeOHOHOO+9k4sSJ3Hvv\nvWmut2fPHu655x4++uijc/YySpcuzdixY5kwYQI33XQTd9999znr33777XTp0oU8efLw/vvv0717\ndxYsWJCuzA8//DA333wzN910U/pepEnZoEHo2LE8VH016369hK+/hmT13gSxbFcg0jJ8OPTu7TQr\nJcrg4SDSPR7Ek08+SZs2bVi0aFGKYzskUnc8iOQ9s/qTOB7E7NmzGT16NFOmTOGDDz44a5nE8SAW\nL158zngQqVm4cCFPPPHEOdPDw8P9dvmd2KV5wYIF6dq1K8uXL0+zQPzzzz+0atWK4cOHc91116W4\nXI8ePVKcV7x48aT7vXr14j//+U+qz5lo6NChxMbG8v7776dreZOK11+HF19k1I2fMnFpHYYOBZ9W\nPJMFZInTXDNSkAwHkSPGg4iLi2P//v2A02zz9ddfU736Ob2unOX06dO0a9eOe++9lw4dOqS6bGpS\nGm8iNePGjWP27NlMmjTpnL0Wc54++gieeoqljQby+LLOtG4NgwZ5Hcqct/M9qu317aKHHA0Q3yFH\nfc+O8T2Lx3fe9OnTtUKFClq3bl19+umntWHDhqqqunfvXm3SpIleffXV2qtXLy1VqlTScJmTJ0/W\nWrVqaY0aNbRu3br6448/+s2ydu1arVOnTtLQoTNnzlTVf4ccnTBhghYsWPCs4UVVVbdu3aq33nqr\n1qxZU6tWrapDhw69qPfk6NGjWrduXa1Ro4ZWq1ZNH3vsMY2Li1NV1eXLl2uZMmU0PDxcixUrptWq\nVVNVZ4jQsLCws7KtWbPmvJ97wIABWq1aNa1Zs6Y2atRIN27cmDSvQYMGWqJECc2bN6+WKVNGZ82a\npaqqoaGhWrFixaTnTen1B8PfW1D76ivV0FDdfVMnLVUqQa+8UjXZiWjGA1zAWUw2HkSQsfEggl92\n+nvLcIsXQ/PmnK5elya5FrPm5zCWLYM0dhxNJrDxILIBGw/CZFlr18Ltt0OFCjxVez5Lx4cxebIV\nh6zMCkSQsfEgTJa0ZQvceisULszHvZfw7pP5eOopuOsur4OZi5FtCoSqIjnwyhsbDyJzZbUm2Uyx\nezc0bw4JCax58zse7FacRo3g5Ze9DmYuVrY4VSNv3rwcOHDA/nlNQKkqBw4cIG9e65o6yd9/O3sO\n+/dzYNIc2j9VgRIl4LPPICzbfP3MubLFr7Bs2bLExMRgXYGbQMubNy9lrftRx7Fj0Lo1bN5M/Nff\n0vW1OuzeDd9/Dylcw2mymGxRIHLlypV0JbIxJhOcPg0dOsCyZTB1KoMXNWHOHKc/s3r1vA5nMkq2\nKBDGmEyUkAA9esCsWTB2LF9Ke158ER54ANw+IE02YQXCGJN+qtCvH0yaBC+/zG8NetG9nrPX4PZD\nabIRKxDGmPR7/nl49114+mmO9PkP7epD3rwwbRo2bGg2ZAXCGJM+774LQ4ZAz57oK6/So6OwZQvM\nnQuXX57m2iYLsgJhjEnbpEnw6KPQti2MGcOrrwlffAH/+x80bux1OBMo2eI6CGNMAM2aBffeCw0b\nwuTJzF0YxrPPOldJ++n53WQjViCMMSn74Qdo3x5q1IDoaLb/lZcuXaBaNRg/3oYNze6sQBhj/Pvl\nF2jVCsqWhVmzOJGrEHfeCXFx8MUXNmxoTmDHIIwx59q2zelCIzwc5sxBS15Cn56wejXMmAGVKnkd\n0GQG24MwxjiiopxB20NCoHJlOHwY5syBiAhGj3YGiXvuOad3DZMz2B6EMcYpDr6DtcfFOb3trV3L\nD4evpl8/ZzzpwYO9jWkyV7YYUc4Yc5EiIsDPGOd/lY2kbvwKwsNhxQooWjTzo5mMYSPKGWMuzM6d\n50w6QxgdY17ncLhzpqsVh5zHjkEYY6BIkXMmPc0IlnAT48ZBzZoeZDKeswJhTE63aBEcOgShoUmT\nPqEbb9OPJ1pspEsX76IZb1mBMCYn277dGdehShWieswhIvRPQojnXj6mymWHeSW6qtcJjYesQBiT\nUx075vStFB9P1P3z6T2pCTviy6KEoISw41BhpkzxOqTxkhUIY3IiVWfQn/XrYfJkBr5zWdIZrolO\nnICBAz1JZ4KEncVkTE704ovOIA4jRsCtt/o7iQnwe3KTyUECugchIi1EZJOI/C4iA/zMLyciC0Vk\njYj8LCK3BTKPMQaIjoZBg+Duu+HJJ1FNuV+lcuUyN5oJLgErECISCowEWgLVgC4iUi3ZYoOAKapa\nB+gMjApUHmMMsGGDUxgiI2HMGBBh4EA4etS5cNpXeDgMH+5NTBMcArkHUQ/4XVW3quppYDLQNtky\nChRy7xcGdgcwjzE528GDzkHp8HD48kvIl49XXoGXXnJ62ZgwAcqXd7rwLl/eqR/dunkd2ngpkMcg\nygB/+jyOAeonW2YIMEdEHgXyA838bUhEegO9AcrZPq8x5y8+Hrp0cbrTWLgQypblvfdgwADo3BlG\njXIug7CCYHx5fRZTF2CCqpYFbgMmisg5mVR1jKpGqmpkyZIlMz2kMVneM8/A7NkwciTceCNRUfDI\nI07PrB9/fNY1csYkCWSB2AX4DmVe1p3m635gCoCq/gjkBUoEMJMxOU9UFLz2mlMRHniA6Gjo3h0a\nNYIpUyBXLq8DmmAVyAKxAqgkIhVEJDfOQejoZMvsBJoCiEhVnAIRG8BMxuQsK1dCr17OeNJvvMH8\n+dCpE1xzDXz1FeTL53VAE8wCViBUNQ7oC8wGNuKcrfSriDwvIm3cxZ4CHhCRdcAkoIdmtf7HjQlW\nf/0F7drBpZfC1Kn8tCoXbds6o8F9+y0ULOh1QBPsAnqhnKrOBGYmmzbY5/4G4MZAZjAmRzp1Cu68\nE/7+G5Yu5ec9JWnZEkqVcgaJK1bM64AmK7ArqY3JblShb1/44QeYMoUt+WvT/CYoUADmzYPLLvM6\noMkqrEAYk9289x6MGwcDB7Kzfkea3QQJCTB3rjNwnDHpZQXCmOxk0SLo1w9uv529jzzPLY3g8GHn\n0ocqVbwOZ7IaKxDGZBfbt0PHjnDllRx85xNubRlCTIxzzKFOHa/DmazICoQx2cGxY3DHHXDmDEc/\njaZVl0Js3AgzZsCNdhqIuUBWIIzJ6lShZ0/45RdOfvkt7f5TiWXLYOpUaN7c63AmK7MCYUxW99JL\nMHUqcS+9RpcPmzNvntPxXvv2XgczWZ3XfTEZYy7G11/DoEEkdOnGfRueYvp0eOcdpysNYy6WFQhj\nsqqNG6FrV7R2HR4tOIGJE4Vhw5xLIIzJCFYgjMmKDh1yxnbIl4+BNyxk1Jgw+veHZ5/1OpjJTqxA\nGJPVJI7tsH07r7T/iZdGFuLBB+GVV5zBfozJKFYgjMlqnn0WZs3ivY4LGDC6Al27OsM8WHEwGc0K\nhDFZyaefwquv8knTD3lkUgNuv905Y8kG/DGBYAXCmKxi1Sq4/36+qvYMPRZ1twF/TMBZgTAmK9i7\nF+64g/mF2tHp9+FERgpffQV583odzGRndqGcMcHu9Gm4805+3HcFbUMnctVVwsyZNuCPCTwrEMYE\nM3dsh3VLj3Bb/hVcdlmoDfhjMo0VCGOC2ejRbB67iObhqylQNDfz5jmjwhmTGaxAGBOsvvuOnY++\nRrO8P6H58zNvHpQv73Uok5OkeZBaRPqJSCFxjBeR1SJifUQaE0g7drC3fR+ahSzgn9wlmTNHuOoq\nr0OZnCY9ZzHdp6r/AM2BosA9wMsBTWVMTnbsGAdb30PzQ1PYFVaOmd8KtWt7HcrkROkpEInXZ94G\nTFTVX32mGWMykipH732Y29a/wm8h1Zj+VQg33OB1KJNTpadArBKROTgFYraIFAQSAhvLmJzp5LAR\n3PHFPawIqc/kKSHccovXiUxOlp6D1PcDtYGtqnpcRIoDPQMby5ic58xXM+k8uBLzacZHHyjt2nmd\nyOR0aRYIVU0AVvs8PgAcCGQoY3KahA2/cV/Hf/iKzrz7+mnu7Z7b60jGWFcbxnhNDx6i7w2r+eRM\nZ4b3P8QjT1hxMMHBCoQxXoqP59nIObx3uCv/6fInz7xSxOtExiRJ14VyIhIKXOq7vKruDFQoY3KK\nl5vO4eWtnXjo5l95OepqG9PBBJU0C4SIPAo8B+zl37OXFKgZwFzGZHuj7lvBM9+1pOuVyxm5sJ4V\nBxN00rNJJv4aAAAbj0lEQVQH0Q+4yj04bYy5QFFRMHAg7NwJxQqd4cDha2lTbAkT1tYjxBp7TRBK\nz5/ln8DhC9m4iLQQkU0i8ruIDEhhmU4iskFEfhWRTy/keYwJdlFR0Pu+OHbscDpoPXA4FyHE0+65\nGuTKbwelTXASVU19AZHxwFXAN8CpxOmq+noa64UCm4FbgBhgBdBFVTf4LFMJmAI0UdWDInKJqu5L\nbbuRkZG6cuXKVDMbE2wiShxlx4EC50wvX/wo2/efO92YjCYiq1Q18nzWSU8T0073ltu9pVc94HdV\n3eqGmwy0BTb4LPMAMFJVDwKkVRyMyap2Hgg/r+nGBIP0XCg39AK3XQaneSpRDFA/2TKVAURkKRAK\nDFHVWRf4fMYErSIc4iDnjvJTjp1ARKbnMSY9UiwQIvKmqj4uIjNwzlo6i6q2yaDnrwQ0AsoCi0Wk\nhqoeSpalN9AboFy5chnwtMZkDlUY8JBTHEKJI97nXy6cYwwv/jrwtncBjUlFansQE92fIy5w27uA\ny30el3Wn+YoBlqnqGWCbiGzGKRgrfBdS1THAGHCOQVxgHmMyVVwcPNB+PxNmlKBP2Biu1x/4v/gh\n7KQc5djJ8FxD6fZWM69jGpOiFAuEqq5yf353gdteAVQSkQo4haEz0DXZMtOBLsCHIlICp8lp6wU+\nnzFB4/hxuKvJPr5edglDCr/O4B9aImvyc8/ARs55ruXKwfDh0K2b11GNSVHAhhxV1TgR6QvMxjm+\n8IGq/ioizwMrVTXanddcRDYA8UB/u97CZHUHD8Lt9ffxw5YSvFdmGA8tvw9Kl4ZqVa0gmCwlzdNc\ng42d5mqC2a4Y5dZr9rNlXyGiqr9MhyWPQ+HCXscy5oJOc0339ZsiYufjGZOK336N54aqB9m5Lw/f\nNhlBh1XPWHEwWVqaBUJEbnCbgH5zH9cSkVEBT2ZMFrJ88Uka1D3GyaNn+O7eD2gy9xnIbVdIm6wt\nPXsQbwC34g4SpKrrgJsDGcqYrGT21H9o3FgpfDqWpQO/pc5Hj2OdK5nsIF1/xar6Z7JJ8QHIYkyW\nE/XWflp3ykdl3cTSMb9y5bAeXkcyJsOkq7M+EbkBUBHJJSJPAxsDnMuYoPdm/13c/XgJGoT+xKIZ\nRyn1QEZcO2pM8EhPgXgIeASn64xdQG33sTE5kio802U7T4woQ/t8M/l2WTEKt2rgdSxjMlx6+mLa\nD9jJ28bgXB39YLPf+eC7K3mw6BRGrrmB0PJlvY5lTECkZ0S5CsCjOD2K+Q45avvTJkc5cVzpfO3v\nRG+oxODyExiy5g6kqI0hbbKv9FxJPR0YD8zg3yFHjclRDu6Pp02t7SzdfQUj647n4R/uhjx5vI5l\nTEClp0CcVFXrbtLkWLv+OEmLuvvY9M/lTL5tIp1m9LTTWE2OkJ4C8ZaIPAfM4ewR5VYHLJUxQWLT\n8sPcevNxDpwqyrcPfkXT0d29jmRMpklPgagB3AM04d8mJnUfG5Ntrfh6L7fdkQuJD2XR8B+45tmO\nXkcyJlOlp0B0BCqq6ulAhzEmWMx5fxvt+1zCJcQy+6M9VLr3Vq8jGZPp0tOQuh6wUzVMjjHp/zbQ\n+qEyXBm6naVzj1Pp3uu9jmSMJ9KzB1EE+E1EVnD2MQg7zdVkO2/3WE2/j+pyc/gKopeVonD1y9Ne\nyZhsKj0F4rmApzDGY6owqPkyXpxXn3bFvuPTX2qQt3Qxr2MZ46n0XEl9oUOOGpMlxJ1O4KFrVjB+\nfX0eiJjLe780ILRAPq9jGeO5FI9BiMgS9+cREfnH53ZERP7JvIjGBM6JQ6foUHEV49fX5/8iv+X9\nLU2sOBjjSm0PIj+AqhbMpCzGZKpDOw7TptYOlhy+hnfazKXv9BYg4nUsY4JGamcxZa3Bqo05D7tX\n7eHmKnv56XAVJj28hL5f3WLFwZhkUtuDuEREnkxppqq+HoA8xgTc5pm/c2ub3OyPv4yZL/9Cs//a\nAInG+JNagQgFCgD2tcpkGyvHrqHlg5cjAgs/2UVkt2u8jmRM0EqtQOxR1eczLYkxATZ38Pe0f6E2\nJcIOM3uWUrlpFa8jGRPUUisQtudgso3JPWdz74TGVAnfyaxlxShd3a5xMCYtqR2kbpppKYwJlIQE\n3rklmq4TbuG6YltYvKW0FQdj0inFAqGqf2dmEGMymp46zf/ViuaxeW1oE/Ezs7dfRZHS4V7HMibL\nsFFPTLYS9fASIsJiCJEECuU9xbD1d3D/NWuYtrkW+Qqmp2cZY0wi+48x2UbUw0vo/V4djjvXeHKU\ngoRxmsbXHiMslx1SM+Z82R6EyTYGjolIKg6J4sjNwLER3gQyJouzAmGyh4QEdsaX9jsrpenGmNRZ\ngTBZnu6L5Y2rx6EpnJldLnR3JicyJnsIaIEQkRYisklEfheRAaksd6eIqIhEBjKPyX6OzP6BzhE/\n8uRvvbmm2Dbycfys+eEcY3jv7d6EMyaLC1iBEJFQYCTQEqgGdBGRan6WKwj0A5YFKovJhhIS2PjE\nGOq3KMK0E614+bFdrNhfkbF9VlM+NAYhgfKhMYzps4Zuoxp4ndaYLCmQZzHVA35X1a0AIjIZaAts\nSLbcC8ArQP8AZjHZSWws0255n57r+pEvTwJzp56kye1lAOg2qgHdRiUuWNa9GWMuRCCbmMoAf/o8\njnGnJRGRusDlqvpNahsSkd4islJEVsbGxmZ8UpNlxC1YzNMVPqfjukFcHXGc1VsK0eT2/GmvaIw5\nb54dpBaREOB14Km0llXVMaoaqaqRJUuWDHw4E3wSEvhrwJs0bar879hDPNwplu9+u5Syl9v1DcYE\nSiCbmHYBl/s8LutOS1QQqA4sEmegllJAtIi0UdWVAcxlspp9+1ja+kU6rvgPh0KLM/G9E9z9gH1R\nMCbQAlkgVgCVRKQCTmHoDHRNnKmqh4ESiY9FZBHwtBUH40sXLuLtO+bz9D+vEXHJcWbNyU3NWrbX\nYExmCFgTk6rGAX2B2cBGYIqq/ioiz4tIm0A9r8km4uM5Ouhlujb5i8f/eYHbGh1nxabCVhyMyUQB\n7YtJVWcCM5NNG5zCso0CmcVkIXv3sqndANr/+DS/SVVeHHyK/w4uTIhd1mlMprLO+kxwWbiQL9p/\nQo9Db5GnQC5mfyE0uyWP16mMyZHsO5kJDvHxxA1+nv80Wcmdh8ZTtXoYqzfko9kt1qRkjFdsD8J4\n76+/2NuxL52XPMIiGvPQ/Wd4c2Q4eWzHwRhPWYEw3po/nx87vk6Hg2P5O9clfDRWubd7Lq9TGWOw\nAmG8Eh+PDn2ekS/8zZNM5/Kyyo8zwqhd2+tgxphEdgzCZL49ezjW5HbueaESj/IOt7YUVv6c24qD\nMUHGCoTJXHPnsqV6O677/jU+lW688AJ89XUYRYt6HcwYk5w1MZnMERcHQ4cyfdh6uofMJaxQPmZN\nEZo39zqYMSYltgdhAm/3buKa3sozw8Jpx5dUrh3O6nVhVhyMCXK2B2ECa84c9nV9nC4HR7GARvTu\nDW+9FUrevF4HM8akxQqECYy4OBgyhGXD59EhbAGxYZfwwWjo2dPrYMaY9LICYTLerl1o5y6MXnI1\n/UKWUKZMCD98EULdul4HM8acDzsGYTLWrFkcr3U93X96iId5j1tahLFqtRUHY7IiKxAmY8TFwTPP\n8HvLvlx/bC6fxHdh6FCYMQOKFfM6nDHmQlgTk7l4MTHQpQszlhThntw/E5IvHzO/FFq08DqYMeZi\n2B6EOX9RURARASEhcOmlxF9VjUHLbqcNM7iiejirV1txMCY7sD0Ic36ioqB3bzh+HID9++LpyjTm\n0pz774d338VOYTUmm7A9CHN+Bg4k6nhbIthGCPGU4i8W0Iixxf7LuHFWHIzJTmwPwqTfyZNE7biB\n3ozlOPkBiCeEvJwg398xHoczxmQ024Mw6TN3LtSoQX9GJBWHRCfJx8DQVzwKZowJFCsQJnV//QVd\nu/J387t4NHYwe7jM72I748tkcjBjTKBZgTD+xcfDyJHEV67Ke1OLUzk8hlFH7qZAAf9jRJcrb2NH\nG5PdWIEw51q1Cq67jsV9P+MaXcnDce9Qo144a9YIo0dDePjZi4eHw/Dh3kQ1xgSOFQjzr8OH4bHH\n+PPa9nReP5CGLOZgsYpMnQoLFkDNmtCtG4wZA+XLg4jzc8wYZ7oxJnuxs5gMqMLUqZzoN4ARf93N\nS2FbUHIxZAj07y/n7DF062YFwZicwApETvfHH+jDj/DlnHCeyv092ylDx3bw2mvO3oExJueyApFT\nnToFr73Gry98Qb/4/zGfxtS4SlnwFjRu7HU4Y0wwsGMQOdHChRysfhP9/q8gtc6sYHXBhrz7Lqxe\nLVYcjDFJbA8iJ9m3j/gn+zM+Kg/PhsziYEhRHnxQeOEFKF7c63DGmGBjBSInSEiAceNY8tSXPHb0\nRdZQh5tviOftd4VatbwOZ4wJVgFtYhKRFiKySUR+F5EBfuY/KSIbRORnEZkvInZYNKOtW0dM5B10\nfbAANx39lv2XVeezz2DR4lArDsaYVAVsD0JEQoGRwC1ADLBCRKJVdYPPYmuASFU9LiJ9gFeBuwKV\nKUc5epSTA1/gf+/k5kWdRHyuvAweoPx3QK5zTls1xhh/AtnEVA/4XVW3AojIZKAtkFQgVHWhz/I/\nAXcHME/OoIp+OZ2ven/DkweeZRsVufP204x4O5SICK/DGWOykkA2MZUB/vR5HONOS8n9wLf+ZohI\nbxFZKSIrY2NjMzBiNrN9OxsaP8Ktd+an3YFxhFcoxfz5MC06txUHY8x5C4qD1CJyNxAJNPQ3X1XH\nAGMAIiMjNROjZQ2nT3No+EiGDg/jnfi3KZgvjrdfjKdP33DCguI3bIzJigL58bELuNzncVl32llE\npBkwEGioqqcCmCdbil/0PR92ncuze/qynxL07nacF94oQMmSXiczxmR1gWxiWgFUEpEKIpIb6AxE\n+y4gInWA94E2qrovgFmyn/37WdrqReo1DueBPc9zVbVQVq0OYfQnVhyMMRkjYAVCVeOAvsBsYCMw\nRVV/FZHnRaSNu9hrQAFgqoisFZHoFDZnEiUksOt/k7m7zEIazHyWvQWv5NMPTrJ4fXHq1PE6nDEm\nOwloC7WqzgRmJps22Od+s0A+f3ZzavWvvN5+CcN3dCNOcjGwdyzPvF6S/PnTXtcYY86X9cUUjKKi\nICICQkIgIgId/wHRd37E1dfk4dkdD9K87gE2bM7FsPetOBhjAsfOcQk2UVFE9ZzHwDOL2Ek5Ltux\nh+K99vMLtahaZDdzxh7mlg52wbkxJvCsQASZqH7L6H3mXY7j7Brspgy7Kc3deabwwb5O5MrlcUBj\nTI5hTUzB5MwZ+h8YkFQc/iV8f6qeFQdjTKayAhEETv3+J1Htp3FD/nXsobTfZXZSLpNTGWNyOisQ\nXklIYOdHC3m28lQur5SHu7/swIHcpSia57jfxcsV9z/dGGMCxQpEJkvYs5c5902mbYH5VOhxM69s\nac+NlWOZ89EeNv5TlnfGhxOeO+6sdcJzxzH8rQIeJTbG5FR2kDozqHLw66VMGLyV99ZezxY6UzLX\nQQa028SDr15JuSuvTlq0WzeAMAYOhJ07oVw5GD48zJ1ujDGZR1SzVt93kZGRunLlSq9jpM/Bg6wZ\nPpOR43Lz6eFWnCCcG0tv5eEn8nLno6XJk8frgMaYnEJEVqlq5PmsY3sQGU2Vk9+vYNqgtYxcUouf\ntBvhISe4p9FO+rxUntrXVfQ6oTHGpIsViIxy5Ajb347m/bdPMW7f7eynHpUL/8WbD8TQfWBZihS5\nyuuExhhzXqxAXKSEtT8zZ9BiRs2qyNfxXRCUtrV38PCQcJq2KYWI1wmNMebCWIG4ECdO8PeHX/Hh\nq7G8t6Mlf9CXS/MeZuA9u+k9tAyXl7NmJGNM1mcF4nxs3syq579h5NRLmHS6PSfJR4MKuxj27FHa\n31uY3LkLe53QGGMyjBWItJw+zcmpM5gyfAsjNzZmOU+QP+wk3W8/QJ/nS1OrdmrDbBtjTNZlBSIl\n27ez7bVpjJ6Ql/HHO3OAO7mqxAHe6vcP3R8tROHCVhiMMdlbzr2SOtmYC0RFQXw8CdFf822952hd\nYT1XjHqS/53oQ8Mb4pg3O56N+4rz2KBCFLaWJGNMDpAz9yCSjblQbsdOnrnnFY48sIn3TnRnK625\nNP8RBt1/hN79C1O2bCmvExtjTKbLkQUi+ZgLO4jgIR0FJ4SbqsYy/Nk42ncqSO7cHgc1xhgP5cgC\nMfDAk37HXLiM3Sze4L+7bWOMyWly5DGIlMZW+AtrSjLGmEQ5skCkNLaCjblgjDH/ypEFYvhbBWzM\nBWOMSUOOLBDdusGYD8IoXx5EoHx557GNuWCMMf/KkQepwSkSVhCMMSZlOXIPwhhjTNqsQBhjjPHL\nCoQxxhi/rEAYY4zxywqEMcYYv0RVvc5wXkQkFtiRgZssAezPwO1lpGDOBsGdz7JduGDOZ9ku3FWq\nWvB8Vshyp7mqasmM3J6IrFTVyIzcZkYJ5mwQ3Pks24UL5nyW7cKJyMrzXceamIwxxvhlBcIYY4xf\nViBgjNcBUhHM2SC481m2CxfM+SzbhTvvfFnuILUxxpjMYXsQxhhj/LICYYwxxq8cWSBE5HIRWSgi\nG0TkVxHp53Wm5EQkVETWiMjXXmdJTkSKiMg0EflNRDaKyPVeZ0okIk+4v9P1IjJJRPJ6nOcDEdkn\nIut9phUTkbkissX9WTSIsr3m/l5/FpEvRaSIF9lSyucz7ykRUREpEUzZRORR9/37VURe9SJbSvlE\npLaI/CQia0VkpYjUS2s7ObJAAHHAU6paDbgOeEREqnmcKbl+wEavQ6TgLWCWqlYBahEkOUWkDPAY\nEKmq1YFQoLO3qZgAtEg2bQAwX1UrAfPdx16YwLnZ5gLVVbUmsBl4JrND+ZjAufkQkcuB5sDOzA7k\nYwLJsolIY6AtUEtVrwZGeJAr0QTOfe9eBYaqam1gsPs4VTmyQKjqHlVd7d4/gvMBV8bbVP8SkbJA\nK2Cc11mSE5HCwM3AeABVPa2qh7xNdZYwIJ+IhAHhwG4vw6jqYuDvZJPbAh+59z8C7sjUUC5/2VR1\njqomDrf4E1A204P9m8XfewfwBvAfwLMzbFLI1gd4WVVPucvsy/RgrhTyKVDIvV+YdPxv5MgC4UtE\nIoA6wDJvk5zlTZx/gASvg/hRAYgFPnSbwMaJSH6vQwGo6i6cb207gT3AYVWd420qvy5V1T3u/b+A\nS70Mk4r7gG+9DuFLRNoCu1R1nddZ/KgM3CQiy0TkOxG51utAyTwOvCYif+L8n6S5d5ijC4SIFAA+\nBx5X1X+8zgMgIq2Bfaq6yussKQgD6gLvqWod4BjeNZGcxW3Lb4tTxEoD+UXkbm9TpU6d88yD7lxz\nERmI0xQb5XWWRCISDjyL0zwSjMKAYjjN1v2BKSIi3kY6Sx/gCVW9HHgCtxUgNTm2QIhILpziEKWq\nX3idx8eNQBsR2Q5MBpqIyCfeRjpLDBCjqol7XNNwCkYwaAZsU9VYVT0DfAHc4HEmf/aKyGUA7k/P\nmiL8EZEeQGugmwbXhVJX4BT/de7/R1lgtYiU8jTVv2KAL9SxHKcFwJOD6CnojvM/ATAVsIPU/rhV\nfTywUVVf9zqPL1V9RlXLqmoEzgHWBaoaNN+CVfUv4E8Rucqd1BTY4GEkXzuB60Qk3P0dNyVIDqAn\nE43zz4r78ysPs5xFRFrgNG+2UdXjXufxpaq/qOolqhrh/n/EAHXdv8lgMB1oDCAilYHcBFfvrruB\nhu79JsCWNNdQ1Rx3Axrg7Nb/DKx1b7d5nctPzkbA117n8JOrNrDSff+mA0W9zuSTbSjwG7AemAjk\n8TjPJJzjIWdwPtDuB4rjnL20BZgHFAuibL8Df/r8X4wOpvcu2fztQIlgyYZTED5x//ZWA02C6b1z\nP/dWAetwjrlek9Z2rKsNY4wxfuXIJiZjjDFpswJhjDHGLysQxhhj/LICYYwxxi8rEMYYY/yyAmGy\nDRFZJCIBHzReRB5ze7G96KuM3a5KUu0oUkQmiEgHP9MbBWNvvyb7CPM6gDHBQETC9N9O6tLyMNBM\nVWMu9nlVtdfFbiOjned7YbIx24MwmUpEItxv32PdPvPniEg+d17SHoCIlHC7U0BEeojIdHfshO0i\n0ldEnnQ7C/xJRIr5PMU9bn/36xP7uxeR/G7/+Mvdddr6bDdaRBbgXLiWPOuT7nbWi8jj7rTRQEXg\nWxF5ItnyPUTkCxGZ5Y718KrPvOYi8qOIrBaRqW4/YMlf8/0istnNOVZE3vXZ/M0i8oOIbE22N1FI\nRL4RkU0iMlpEQtxtdRGRX9zsr/jkOOpzv4OITHDvT3DXXwa8KiIN3fdxrfueFUzHr9dkN15d6We3\nnHkDInA6gavtPp4C3O3eX4QzlgM4fdhsd+/3wLnCtyBQEjgMPOTOewOns8XE9ce6928G1rv3X/R5\njiI44xzkd7cbg58rmYFrgF/c5QoAvwJ13Hnb8XMFr7u9rThdKecFdgCXu69lMZDfXe6/wGDf14zT\nueB2nM7ecgHfA++6y0zA6TsnBKgG/O5ObwScxClYoThjOXRwt7XTfa/CgAXAHe46R33ydgAm+DzH\n10Co+3gGcKN7vwAQ5vXfjt0y/2ZNTMYL21R1rXt/FU7RSMtCdcbuOCIih3E+wMD5EK/ps9wkcPrD\nF5FC4oyI1hynA8Sn3WXyAuXc+3NV1d+YAw2AL1X1GICIfAHcBKxJI+d8VT3srrMBKI9TlKoBS93O\nPXMDPyZbrx7wXWIWEZmK0310oumqmgBsEBHf7sGXq+pWd51Jbu4zwCJVjXWnR+EUzOlpZJ+qqvHu\n/aXA6+66X2gGNKeZrMcKhPHCKZ/78UA+934c/zZ7Jh8q1HedBJ/HCZz9d5y87xgFBLhTVTf5zhCR\n+jjdlWek5K8tzH3+uaraJYO269uFtL/Xmxrf+cnf46T3QlVfFpFvgNtwCtutqvpbesOa7MGOQZhg\nsh2naQec5o8LcReAiDTAGTDoMDAbeDSxb34RqZOO7XwP3OH2DJsfaOdOuxA/ATeKyJXu8+d3e/v0\ntQJoKCJFxRkN7850brueiFRwjz3cBSwBlrvbKiEioUAX4Dt3+b0iUtVdvl1KGxWRK9TpPfUVN1uV\ndOYx2YjtQZhgMgJnkJXewDcXuI2TIrIGpx3/PnfaCzij9P3sfjBuwxnvIEWquto9gLvcnTROVdNq\nXkppW7HijLEwSUTyuJMH4RwLSVxml4i86D7f3zg90h5Ox+ZXAO8CVwILcZrFEkRkgPtYgG9UNbFL\n8QE4xxpicXrkLZDCdh8XZ4zlBJzjL0E1spzJHNabqzFBQkQKqOpRdw/iS+ADVf3S61wm57ImJmOC\nxxARWYsznsA20j6obExA2R6EMcYYv2wPwhhjjF9WIIwxxvhlBcIYY4xfViCMMcb4ZQXCGGOMX/8P\n3hBPmWzjEIYAAAAASUVORK5CYII=\n",
137 | "text/plain": [
138 | ""
139 | ]
140 | },
141 | "metadata": {},
142 | "output_type": "display_data"
143 | }
144 | ],
145 | "source": [
146 | "import matplotlib.pyplot as plt\n",
147 | "colors =['ro-','bo-','go-','co-','yo-','ko-','wo-','mo-']\n",
148 | "ds = [256, 512]\n",
149 | "labels = ['Image_size = ' + str(ds[i]) +' * '+str(ds[i]) for i in range(len(ds))]\n",
150 | "#plt.yscale('log')\n",
151 | "#plt.xscale('log')\n",
152 | "\n",
153 | "plt.xlabel('number of neighbours')\n",
154 | "plt.ylabel('Time in s')\n",
155 | "#plt.ylim([1e-4, 1e4])\n",
156 | "plt.xlim([1, 18])\n",
157 | "neigh = [2 + 2 * i for i in range(7) ]\n",
158 | "for i in range(len(ds)):\n",
159 | " plt.plot(neigh, knn[i,:], colors[i% len(colors)])\n",
160 | "plt.legend(labels)\n",
161 | "plt.title(\"Time measurement for knn GPU\")\n",
162 | "plt.show() "
163 | ]
164 | },
165 | {
166 | "cell_type": "code",
167 | "execution_count": 22,
168 | "metadata": {},
169 | "outputs": [],
170 | "source": [
171 | "f1 = open(\"build/knn_shared_gpu1.txt\", \"r\")\n",
172 | "f2 = open(\"build/knn_shared_gpu2.txt\", \"r\")\n",
173 | "nlm1 = f1.readlines() + f2.readlines()\n",
174 | "ln = int(len(nlm1)/2)\n",
175 | "nlm = np.array([np.array([nlm1[i]]).astype(np.double) for i in range(len(nlm1))])\n",
176 | "nlm = nlm.reshape(2, ln)\n",
177 | "knn = nlm\n",
178 | "f1.close()"
179 | ]
180 | },
181 | {
182 | "cell_type": "code",
183 | "execution_count": 26,
184 | "metadata": {},
185 | "outputs": [
186 | {
187 | "data": {
188 | "image/png": "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\nKujO85ppVJWTWqGC6vffex3KBKPNmzfnfOWoKFVn51L6W1TUWWUoWbKkqqquWLFCzznnHN29e7cm\nJiZq5cqVdfTo0aqqOnHiRB02bJiqqh46dEhTUlJUVXX69Ok6fPhwVVUdPHiwPvXUU6qq+uGHHyqg\n+/fv182bN+s111yjJ0+eVFXVe++9V2fNmuU3y/r167Vjx45p04cPH1ZV1TvuuEPnz5+fbt0RI0bo\niBEjVFW1ffv2+vPPP6uq6po1a7Rdu3anbXv58uXauHHj026tWrXK8v05fPiwVq9eXbdt26aqqq+/\n/rqef/752rBhQ+3Vq5fGxcWpqurChQu1W7duet1112mTJk10xIgRmpycnG5bu3bt0oEDB+oTTzyh\ns2fP1nvuuSfL5x48eLA++eSTadPh4eHarFkzbdmypS5cuDBtfvny5XXcuHHarFkz7dq1a9p7kZG/\nvzdgveby89bzD/zc3oKmQGzYoFq+vO67oLHWqXFCS5dWXb/e61AmWOWqQMydqxoZmb44REY688+C\nb4Hw/XBu3bq1rl69WlVVP/nkE+3Zs6eqqm7atEk7deqkDRo00Nq1a2uXLl1UVbVx48b666+/pj2+\nXLlyun//fp08ebJecMEFaR/ItWvX1scff9xvlkOHDmmNGjV0yJAh+uGHH+qpU6dU9fQC8eabb2r7\n9u01OTlZjx07psWLF0/3oV+3bt2zek9SJSUladeuXfXFF19Mm3fgwAFNTExUVdWpU6emFaP58+dr\nmTJldNu2bZqUlKTXX3+9zpgxw+92X3/9df3tt9/SCq0/c+bM0ZYtW6Y9l6pqfHy8qqpu27ZNo6Ki\ndOvWrarq/A4nTJigqqr/+9//9IorrvC7TSsQXlq3TrVsWT1UtYE2vjhRS5RQXbXK61AmmOWqQKg6\nxSAqSlXE+XmWxUE1fYHo1q1b2vw2bdrounXrTlvWpk0bfffdd9Pmt2nTRlUzLxCTJk3SkSNH5jjP\nsWPHdMGCBdqzZ0/t37+/qqYvEN99953WrVtXf//9d1VVPXLkiJ5//vnZbvdMWhD9+/fXoUOHZro8\nOTlZy5Qpo6qqX375pV555ZVpy2bPnq333Xdf9i/Yj48//ljr1q2r+/bty3Qd3/ekTp06ae99SkpK\nWqaM8qpA2DGI3Fq7Fjp25HiZynQ7dx2btxZj4UJsNDiTt/r0ge3bISXF+dmnT75HOHLkCFXcg2mz\nZs1Km3/55Zczb948AJYuXcrhw4cB6NChAwsWLEg7cHro0KF0Z+b4OnDgACkpKfTq1Ytx48bx9ddf\np1v+xx9/0Lt3b2bPnk0l92yPMmXKUL16debPnw84X26//fbb07bdrl07Nm7ceNrtiy++8Jvlscce\n48iRI0zMcJB/z549afcXLVrExRdfDMCll17KH3/8wf79+wFYvnw59epl7Kg6e9988w133303ixYt\nSncs4fDhw5xwjzcdOHCAzz//PG371157LStWrADg008/pXbt2rl+3tyws5hy48svoWtXEstX5toq\nG/jqy+LMmwddungdzJi8N2bMGG688UbKlStH+/bt+e233wB4/PHH6d27N3PmzKFVq1acf/75lC5d\nmooVKzJu3Dg6d+5MSkoKRYoUYcqUKURFnT7My65du+jfvz8pKSkAPP300+mWv/vuu+zYsYO77ror\nbd7GjRuJjY3l3nvvZdy4cSQlJXHLLbfQuHHjM36N8fHxjB8/nrp163LJJZcAMGTIEAYOHMikSZNY\ntGgRERERlC9fnpiYGMA5BXXChAl06NAhda9Gupw59fDDD3P8+HFuvPFGwOmIdNGiRWzZsoW7776b\nsLAwUlJSGDlyZFqBGDlyJH369OHFF1+kVKlS6U69DQRxWh4FR/PmzXX9+vX5/8Sffw5du5J0XlVu\nrLmBd5dGEhNjA/6YnNmyZUvaN9CC7sSJE4SHhxMREcGXX37Jvffe6/eUTOMdf39vIrJBVZvnZjvW\ngsiJVavg6qtJqVyVfvU38O47kTYanAlZcXFx3HTTTaSkpFC0aFGmZ3ZthinwrEBkZ+VK6NYNvbAa\n97VYz3/nRPLUUzYanAldtWrV4ptvvsnx+i1btkzbp55qzpw5NGzYMK+jmTxmBSIrn3wC3buj0dV5\npO1aXn2lJCNH2mhwxuTGV1995XUEc4bsLKbMLF0K11wDNWsyvsdXTHilJPfdB0895XUwY4zJH1Yg\n/PnoI+jRA+rUYVLvL/m/Z0vRty9MnmyjwRljQoftYsro/ffh+uuhfn1e77+KYfeXstHgjDEhyT7y\nfC1aBNddBw0bMn/opwx8oJSNBmc8YcNBmGBgBSLVwoXQqxc0bcoHj6ykz92ladUKGw3O5LsADQdh\n40Fkom3bttSpUyet99jUK8FXrVrFJZdcQkREBAsWLEhbf+PGjbRq1Yr69evTqFGjtC66/clqPIis\n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\nTkSuQmCC\n",
189 | "text/plain": [
190 | ""
191 | ]
192 | },
193 | "metadata": {},
194 | "output_type": "display_data"
195 | }
196 | ],
197 | "source": [
198 | "import matplotlib.pyplot as plt\n",
199 | "colors =['ro-','bo-','go-','co-','yo-','ko-','wo-','mo-']\n",
200 | "ds = [256, 512]\n",
201 | "labels = ['Image_size = ' + str(ds[i]) +' * '+str(ds[i]) for i in range(len(ds))]\n",
202 | "#plt.yscale('log')\n",
203 | "#plt.xscale('log')\n",
204 | "\n",
205 | "plt.xlabel('number of neighbours')\n",
206 | "plt.ylabel('Time in s')\n",
207 | "#plt.ylim([1e-4, 1e4])\n",
208 | "plt.xlim([1, 18])\n",
209 | "neigh = [2 + 2 * i for i in range(5) ]\n",
210 | "for i in range(len(ds)):\n",
211 | " plt.plot(neigh, knn[i,:], colors[i% len(colors)])\n",
212 | "plt.legend(labels)\n",
213 | "plt.title(\"Time measurement for knn GPU Shared\")\n",
214 | "plt.show() "
215 | ]
216 | }
217 | ],
218 | "metadata": {
219 | "kernelspec": {
220 | "display_name": "Python 3",
221 | "language": "python",
222 | "name": "python3"
223 | },
224 | "language_info": {
225 | "codemirror_mode": {
226 | "name": "ipython",
227 | "version": 3
228 | },
229 | "file_extension": ".py",
230 | "mimetype": "text/x-python",
231 | "name": "python",
232 | "nbconvert_exporter": "python",
233 | "pygments_lexer": "ipython3",
234 | "version": "3.5.2"
235 | }
236 | },
237 | "nbformat": 4,
238 | "nbformat_minor": 2
239 | }
240 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/knn_gpu1.txt:
--------------------------------------------------------------------------------
1 | 0.0714389
2 | 0.15296
3 | 0.276538
4 | 0.426409
5 | 0.604893
6 | 0.851609
7 | 1.0691
8 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/knn_gpu2.txt:
--------------------------------------------------------------------------------
1 | 0.0690109
2 | 0.152418
3 | 0.269428
4 | 0.425482
5 | 0.607747
6 | 0.820584
7 | 1.07453
8 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/knn_shared_gpu1.txt:
--------------------------------------------------------------------------------
1 | 0.0892637
2 | 0.251953
3 | 0.326857
4 | 0.698519
5 | 0.903139
6 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/knn_shared_gpu2.txt:
--------------------------------------------------------------------------------
1 | 0.0747506
2 | 0.249488
3 | 0.325612
4 | 0.710729
5 | 0.885749
6 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/nlm_gpu1.txt:
--------------------------------------------------------------------------------
1 | 0.533035
2 | 1.0948
3 | 1.90652
4 | 3.03668
5 | 4.26079
6 | 1.12093
7 | 2.38252
8 | 4.18545
9 | 6.55601
10 | 9.402
11 | 1.97623
12 | 4.2383
13 | 7.47023
14 | 11.6288
15 | 16.8935
16 | 3.06912
17 | 6.64495
18 | 11.8065
19 | 18.4399
20 | 26.5307
21 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/nlm_gpu2.txt:
--------------------------------------------------------------------------------
1 | 0.49607
2 | 1.05929
3 | 1.88118
4 | 2.88862
5 | 4.25544
6 | 1.14961
7 | 2.46671
8 | 4.30679
9 | 6.66869
10 | 9.54662
11 | 2.10333
12 | 4.43098
13 | 7.67502
14 | 11.8182
15 | 16.9222
16 | 3.16858
17 | 6.80753
18 | 11.8896
19 | 18.4843
20 | 26.4238
21 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/results/conv_big_gpu1.txt:
--------------------------------------------------------------------------------
1 | 0.118503
2 | 0.122532
3 | 0.212835
4 | 0.323024
5 | 0.466296
6 | 0.63148
7 | 0.830037
8 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/results/conv_lenna_gpu1.txt:
--------------------------------------------------------------------------------
1 | 0.00524851
2 | 0.00529693
3 | 0.00937226
4 | 0.0143171
5 | 0.020448
6 | 0.0275685
7 | 0.0359575
8 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/results/conv_shared_big_gpu1.txt:
--------------------------------------------------------------------------------
1 | 0.082362
2 | 0.0627674
3 | 0.0905397
4 | 0.193022
5 | 0.244941
6 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/results/conv_shared_lenna_gpu1.txt:
--------------------------------------------------------------------------------
1 | 0.00508256
2 | 0.00803926
3 | 0.0125283
4 | 0.027977
5 | 0.0387666
6 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/results/edge_gpu.txt:
--------------------------------------------------------------------------------
1 | 0.00482083
2 | 0.0125592
3 | 0.0415233
4 | 0.0934847
5 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/results/knn_big_gpu1.txt:
--------------------------------------------------------------------------------
1 | 1.5809
2 | 3.39129
3 | 6.015
4 | 9.32617
5 | 13.4036
6 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/results/knn_lenna_gpu1.txt:
--------------------------------------------------------------------------------
1 | 0.0713735
2 | 0.152609
3 | 0.277733
4 | 0.419318
5 | 0.604389
6 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/results/knn_shared_big_gpu1.txt:
--------------------------------------------------------------------------------
1 | 1.62004
2 | 5.43796
3 | 7.02315
4 | 15.1464
5 | 19.234
6 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/results/knn_shared_lenna_gpu1.txt:
--------------------------------------------------------------------------------
1 | 0.078104
2 | 0.253728
3 | 0.327255
4 | 0.699514
5 | 0.890953
6 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/results/nlm_lenna514_gpu1.txt:
--------------------------------------------------------------------------------
1 | 1.41521
2 | 3.01695
3 | 5.2744
4 | 8.13701
5 | 11.6536
6 | 3.13874
7 | 6.74186
8 | 11.7844
9 | 18.2493
10 | 26.1668
11 | 5.572
12 | 11.9504
13 | 20.8992
14 | 32.3969
15 | 46.4603
16 | 8.69083
17 | 18.7044
18 | 32.6739
19 | 50.5164
20 | 72.6123
21 |
--------------------------------------------------------------------------------
/src/gpu/bench/build/results/nlm_lenna_gpu1.txt:
--------------------------------------------------------------------------------
1 | 0.513633
2 | 1.07415
3 | 1.87087
4 | 2.94911
5 | 4.25799
6 | 1.12163
7 | 2.37158
8 | 4.14952
9 | 6.43583
10 | 9.76906
11 | 1.97959
12 | 4.20468
13 | 7.37651
14 | 11.3691
15 | 16.4323
16 | 3.05361
17 | 6.55269
18 | 11.5116
19 | 18.4537
20 | 26.3633
21 |
--------------------------------------------------------------------------------
/src/gpu/bench/timer.hh:
--------------------------------------------------------------------------------
1 | #ifndef TIMER_HH_
2 | #define TIMER_HH_
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | using std::chrono::duration_cast;
9 | using std::chrono::nanoseconds;
10 | using std::chrono::steady_clock;
11 | struct scoped_timer {
12 | scoped_timer(double & s, std::ofstream & f) : seconds(s), t0(steady_clock::now()),myfile(f) {
13 | }
14 |
15 | ~scoped_timer()
16 | {
17 | seconds = std::chrono::duration_cast
18 | (steady_clock::now() - t0).count();
19 | myfile << seconds << " s" << std::endl;
20 | }
21 | double & seconds;
22 | steady_clock::time_point t0;
23 | std::ofstream & myfile;
24 |
25 | };
26 |
27 | #endif
28 |
--------------------------------------------------------------------------------
/src/gpu/kernel.cu:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "opencv2/core/core.hpp"
7 | #include "opencv2/imgproc/imgproc.hpp"
8 | #include "opencv2/highgui/highgui.hpp"
9 | #include "kernel.cuh"
10 |
11 | #define TILE_WIDTH 16
12 | #define TILE_HEIGHT 16
13 |
14 |
15 | __device__ int mask1[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
16 | __device__ int mask2[3][3] = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
17 |
18 | // Kernel function for the canny edge detector
19 | // Computes the gradient of edges and fixes broken edges
20 | __global__ void hysterysis(Rgb *device_img, int* changed, int width, int height, double t)
21 | {
22 | int x = blockIdx.x * blockDim.x + threadIdx.x;
23 | int y = blockIdx.y * blockDim.y + threadIdx.y;
24 | if (x >= width or y >= height)
25 | return;
26 | if (device_img[x + y * width].r == 255)
27 | return;
28 |
29 | double curr_dir = device_img[x + y * width].b;
30 | double curr_grad = device_img[x + y * width].g;
31 | changed[0] = 0;
32 | if (22.5 <= curr_dir and curr_dir < 67.5
33 | and (x - 1) >= 0
34 | and (y + 1) < height
35 | and (x + 1) < width
36 | and (y - 1) >= 0)
37 | {
38 | double dir1 = device_img[(x - 1) + (y + 1) * width].b;
39 | double dir2 = device_img[(x + 1) + (y - 1) * width].b;
40 | if (((22.5 <= dir1 and dir1 < 67.5) or (22.5 <= dir2 and dir2 < 67.5)) and curr_grad > t)
41 | {
42 | device_img[x + y * width].r = 255;
43 | changed[0] = 1;
44 | }
45 | }
46 | else if (67.5 <= curr_dir and curr_dir < 112.5
47 | and (x - 1) >= 0
48 | and (x + 1) < width)
49 | {
50 | double dir1 = device_img[(x - 1) + y * width].b;
51 | double dir2 = device_img[(x + 1) + y * width].b;
52 | if (((67.5 <= dir1 and dir1 < 112.5) or (67.5 < dir2 and dir2 < 112.5)) and curr_grad > t)
53 | {
54 | device_img[x + y * width].r = 255;
55 | changed[0] = 1;
56 | }
57 | }
58 | else if (112.5 <= curr_dir and curr_dir < 157.5
59 | and (x - 1) >= 0
60 | and (y - 1) >= 0
61 | and (x + 1) < width
62 | and (y + 1) < height)
63 | {
64 | double dir1 = device_img[(x - 1) + (y - 1) * width].b;
65 | double dir2 = device_img[(x + 1) + (y + 1) * width].b;
66 | if (((112.5 <= dir1 and dir1 < 157.5) or (112.5 <= dir2 and dir2 < 157.5)) and curr_grad > t)
67 | {
68 | device_img[x + y * width].r = 255;
69 | changed[0] = 1;
70 | }
71 | }
72 | else if (((0 <= curr_dir and curr_dir < 22.5)
73 | or (157.5 <= curr_dir and curr_dir <= 180.0))
74 | and (y - 1) >= 0
75 | and (y + 1) < height)
76 | {
77 | double dir1 = device_img[x + (y - 1) * width].b;
78 | double dir2 = device_img[x + (y + 1) * width].b;
79 | if ((((0 <= dir1 and dir1 < 22.5) and (157.5 <= dir1 and dir1 <= 180.5))
80 | or ((0 <= dir2 and dir2 < 22.5) and (157.5 <= dir2 and dir2 <= 180.5))) and curr_grad > t)
81 | {
82 | device_img[x + y * width].r = 255;
83 | changed[0] = 1;
84 | }
85 | }
86 | }
87 |
88 | // Classifies the gradient direction for each edges
89 | __global__ void non_max_suppr(Rgb *device_img, double* img, int width, int height, double thresh)
90 | {
91 | int x = blockIdx.x * blockDim.x + threadIdx.x;
92 | int y = blockIdx.y * blockDim.y + threadIdx.y;
93 | if (x >= width or y >= height)
94 | return;
95 | double curr_dir = device_img[x + y * width].b;
96 | double curr_grad = device_img[x + y * width].g;
97 | if (22.5 <= curr_dir and curr_dir < 67.5
98 | and (x - 1) >= 0
99 | and (y + 1) < height
100 | and (x + 1) < width
101 | and (y - 1) >= 0)
102 | {
103 | double val1 = device_img[(x - 1) + (y + 1) * width].g;
104 | double val2 = device_img[(x + 1) + (y - 1) * width].g;
105 | if (val1 < curr_grad and val2 < curr_grad and curr_grad > thresh)
106 | device_img[x + y * width].r = 255;
107 | else
108 | device_img[x + y * width].r = 0;
109 | }
110 | else if (67.5 <= curr_dir and curr_dir < 112.5
111 | and (x - 1) >= 0
112 | and (x + 1) < width)
113 | {
114 | double val1 = device_img[(x - 1) + y * width].g;
115 | double val2 = device_img[(x + 1) + y * width].g;
116 | if (val1 < curr_grad and val2 < curr_grad and curr_grad > thresh)
117 | device_img[x + y * width].r = 255;
118 | else
119 | device_img[x + y * width].r = 0;
120 | }
121 | else if (112.5 <= curr_dir and curr_dir < 157.5
122 | and (x - 1) >= 0
123 | and (y - 1) >= 0
124 | and (x + 1) < width
125 | and (y + 1) < height)
126 | {
127 | double val1 = device_img[(x - 1) + (y - 1) * width].g;
128 | double val2 = device_img[(x + 1) + (y + 1) * width].g;
129 | if (val1 < curr_grad and val2 < curr_grad and curr_grad > thresh)
130 | device_img[x + y * width].r = 255;
131 | else
132 | device_img[x + y * width].r = 0;
133 | }
134 | else if (((0 <= curr_dir and curr_dir < 22.5)
135 | or (157.5 <= curr_dir and curr_dir <= 180.0))
136 | and (y - 1) >= 0
137 | and (y + 1) < height)
138 | {
139 | double val1 = device_img[x + (y - 1) * width].g;
140 | double val2 = device_img[x + (y + 1) * width].g;
141 | if (val1 < curr_grad and val2 < curr_grad and curr_grad > thresh)
142 | device_img[x + y * width].r = 255;
143 | else
144 | device_img[x + y * width].r = 0;
145 | }
146 | }
147 |
148 | // Computes a sobel convolution and compute the gradient direction
149 | __global__ void sobel_conv(Rgb *device_img, double* img, int width, int height, int conv_size)
150 | {
151 |
152 | int x = blockIdx.x * blockDim.x + threadIdx.x;
153 | int y = blockIdx.y * blockDim.y + threadIdx.y;
154 | if (x >= width or y >= height)
155 | return;
156 | double sum1 = 0.0;
157 | double sum2 = 0.0;
158 | int u = 0;
159 | int v = 0;
160 | double cnt1 = 0;
161 | double cnt2 = 0;
162 | for (int j = y - conv_size; j <= y + conv_size; j++)
163 | {
164 | for (int i = x - conv_size; i <= x + conv_size; i++)
165 | {
166 | if (i >= 0 and j >= 0 and i < width and j < height)
167 | {
168 | int weight1 = mask1[u][v];
169 | int weight2 = mask2[u][v];
170 | auto pix = img[i + j * width];
171 | sum1 += pix * weight1;
172 | sum2 += pix * weight2;
173 | cnt1 += abs(weight1);
174 | cnt2 += abs(weight2);
175 | }
176 | v++;
177 | }
178 | u++;
179 | v = 0;
180 | }
181 | double g = std::sqrt(std::pow(sum1, 2) + std::pow(sum2, 2));
182 | double d = atan2(sum2, sum1);
183 | d = (d > 0 ? d : (2 * M_PI + d)) * 360 / (2 * M_PI);
184 | device_img[x + y * width].g = g;
185 | device_img[x + y * width].b = d;
186 |
187 | }
188 |
189 | // Applies the convolution operator on the image
190 | __device__ void conv(Rgb *image, Rgb& rgb, int width, int height, int x1, int y1, int x2, int y2, int conv_size)
191 | {
192 | int cnt = 0;
193 | for (int j1 = y1 - conv_size; j1 < y1 + conv_size; j1++)
194 | for (int i1 = x1 - conv_size; i1 < x1 + conv_size; i1++)
195 | {
196 | int i2 = i1 - x1 + x2;
197 | int j2 = j1 - y1 + y2;
198 | if (i1 >= 0 and j1 >= 0 and
199 | j2 >= 0 and i2 >= 0 and
200 | i1 < height and j1 < width and
201 | i2 < height and j2 < width)
202 | {
203 | cnt++;
204 | auto pix1 = image[i1 * width + j1];
205 | auto pix2 = image[i2 * width + j2];
206 | rgb.r += std::pow(std::abs(pix1.r - pix2.r), 2);
207 | rgb.g += std::pow(std::abs(pix1.g - pix2.g), 2);
208 | rgb.b += std::pow(std::abs(pix1.b - pix2.b), 2);
209 | }
210 | }
211 | if (cnt > 0) {
212 | rgb.r /= cnt;
213 | rgb.g /= cnt;
214 | rgb.b /= cnt;
215 | }
216 | }
217 |
218 | // Applies a gaussian mask to the image
219 | __device__ void gauss_conv_nlm(Rgb *image, Rgb& res, int x, int y, int width, int height, int conv_size, int block_radius, double h_param)
220 | {
221 | auto cnt = Rgb(0.0, 0.0, 0.0);
222 | for (int j = y - conv_size; j < y + conv_size; j++)
223 | {
224 | for (int i = x - conv_size; i < x + conv_size; i++)
225 | {
226 | if (i >= 0 and j >= 0 and i < width and j < height)
227 | {
228 | auto u = Rgb(0, 0, 0);
229 | conv(image, u, width, height, y, x, j, i, block_radius);
230 | auto uy = image[j * width + i];
231 | double c1 = std::exp(-(std::pow(std::abs(i + j - (x + y)), 2)) / (double)std::pow(conv_size, 2));
232 | double h_div = std::pow(h_param, 2);
233 |
234 | auto c2 = Rgb(std::exp(-u.r / h_div),
235 | std::exp(-u.g / h_div),
236 | std::exp(-u.b / h_div));
237 |
238 | res.r += uy.r * c1 * c2.r;
239 | res.g += uy.g * c1 * c2.g;
240 | res.b += uy.b * c1 * c2.b;
241 |
242 | cnt.r += c1 * c2.r;
243 | cnt.g += c1 * c2.g;
244 | cnt.b += c1 * c2.b;
245 | }
246 | }
247 | }
248 | if (cnt.r != 0 and cnt.g != 0 and cnt.b != 0)
249 | {
250 | res.r /= cnt.r;
251 | res.g /= cnt.g;
252 | res.b /= cnt.b;
253 | }
254 | }
255 |
256 | // Non Local-Means kernel function
257 | __global__ void nlm(Rgb* device_img, Rgb* img, int width, int height, int conv_size, int block_radius, double h_param)
258 | {
259 | int x = blockIdx.x * blockDim.x + threadIdx.x;
260 | int y = blockIdx.y * blockDim.y + threadIdx.y;
261 | if (x >= width or y >= height)
262 | return;
263 | auto res = Rgb(0.0, 0.0, 0.0);
264 | gauss_conv_nlm(img, res, x, y, width, height, conv_size, block_radius, h_param);
265 | device_img[y * width + x].r = res.r;
266 | device_img[y * width + x].g = res.g;
267 | device_img[y * width + x].b = res.b;
268 | }
269 |
270 | // Shared knn kernel function
271 | // Uses the K-Nearest Neighbors algorithm for de-noising an image
272 | // Uses the shared memory optimization
273 | __global__ void shared_knn(Rgb* device_img, Rgb* img, int width, int height, int strel_size, double h_param)
274 | {
275 | int r = strel_size / 2;
276 | int block_w = TILE_WIDTH + 2 * r;
277 | extern __shared__ Rgb fast_acc_mat[];
278 | int ty = threadIdx.y;
279 | int tx = threadIdx.x;
280 | int row_o = blockIdx.y * TILE_WIDTH + ty;
281 | int col_o = blockIdx.x * TILE_HEIGHT + tx;
282 | int row_i = row_o - r;
283 | int col_i = col_o - r;
284 |
285 | if ((row_i >= 0) && (row_i < height) && (col_i >= 0) && (col_i < width))
286 | {
287 | auto elt = img[row_i * width + col_i];
288 | fast_acc_mat[ty * block_w + tx] = Rgb(elt.r, elt.g, elt.b);
289 | }
290 | else
291 | fast_acc_mat[ty * block_w + tx] = Rgb(0, 0, 0);
292 | __syncthreads();
293 |
294 | if (row_o >= 0 and col_o >= 0 and row_o < height and col_o < width)
295 | {
296 | if (ty < TILE_HEIGHT && tx < TILE_WIDTH)
297 | {
298 | auto sum = Rgb(0, 0, 0);
299 | auto cnt = Rgb(0, 0, 0);
300 | auto ux = img[row_o * width + col_o];
301 | for (int i = 0; i < strel_size; i++)
302 | {
303 | for (int j = 0; j < strel_size; j++)
304 | {
305 | auto uy = fast_acc_mat[(i + ty) * block_w + j + tx];
306 | double h_div = std::pow(h_param, 2);
307 | double c1 = std::exp(-(std::pow(std::abs(col_i + row_i + i + j - (row_o + col_o)), 2)) / (double)std::pow(r, 2));
308 | auto c2 = Rgb(std::exp(-(std::pow(std::abs(uy.r - ux.r), 2)) / h_div),
309 | std::exp(-(std::pow(std::abs(uy.g - ux.g), 2)) / h_div),
310 | std::exp(-(std::pow(std::abs(uy.b - ux.b), 2)) / h_div));
311 |
312 | sum.r += uy.r * c1 * c2.r;
313 | sum.g += uy.g * c1 * c2.g;
314 | sum.b += uy.b * c1 * c2.b;
315 |
316 | cnt.r += c1 * c2.r;
317 | cnt.g += c1 * c2.g;
318 | cnt.b += c1 * c2.b;
319 | }
320 | }
321 | sum.r /= cnt.r;
322 | sum.g /= cnt.g;
323 | sum.b /= cnt.b;
324 | device_img[row_o * width + col_o] = sum;
325 | }
326 | }
327 | }
328 |
329 | // Shared knn kernel function
330 | // Uses the K-Nearest Neighbors algorithm for de-noising an image
331 | __device__ void gauss_conv(Rgb *image, Rgb& res, int x, int y, int width, int height, int conv_size, double h_param)
332 | {
333 | auto cnt = Rgb(0.0, 0.0, 0.0);
334 | for (int j = y - conv_size; j < y + conv_size; j++)
335 | {
336 | for (int i = x - conv_size; i < x + conv_size; i++)
337 | {
338 | if (i >= 0 and j >= 0 and i < width and j < height)
339 | {
340 | auto ux = image[y * width + x];
341 | auto uy = image[j * width + i];
342 | double c1 = std::exp(-(std::pow(std::abs(i + j - (x + y)), 2)) / (double)std::pow(conv_size, 2));
343 | double h_div = std::pow(h_param, 2);
344 |
345 | auto c2 = Rgb(std::exp(-(std::pow(std::abs(uy.r - ux.r), 2)) / h_div),
346 | std::exp(-(std::pow(std::abs(uy.g - ux.g), 2)) / h_div),
347 | std::exp(-(std::pow(std::abs(uy.b - ux.b), 2)) / h_div));
348 |
349 | res.r += uy.r * c1 * c2.r;
350 | res.g += uy.g * c1 * c2.g;
351 | res.b += uy.b * c1 * c2.b;
352 |
353 | cnt.r += c1 * c2.r;
354 | cnt.g += c1 * c2.g;
355 | cnt.b += c1 * c2.b;
356 | }
357 | }
358 | }
359 | if (cnt.r != 0 and cnt.g != 0 and cnt.b != 0)
360 | {
361 | res.r /= cnt.r;
362 | res.g /= cnt.g;
363 | res.b /= cnt.b;
364 | }
365 | }
366 |
367 | // Hat function of the K-Nearest Neigbhors algorithm
368 | __global__ void knn(Rgb* device_img, Rgb* img, int width, int height, int conv_size, double h_param)
369 | {
370 | int x = blockIdx.x * blockDim.x + threadIdx.x;
371 | int y = blockIdx.y * blockDim.y + threadIdx.y;
372 | if (x >= width or y >= height)
373 | return;
374 | auto res = Rgb(0.0, 0.0, 0.0);
375 | gauss_conv(img, res, x, y, width, height, conv_size, h_param);
376 | device_img[y * width + x].r = res.r;
377 | device_img[y * width + x].g = res.g;
378 | device_img[y * width + x].b = res.b;
379 | }
380 |
381 | // Hat function of the K-Nearest Neigbhors algorithm
382 | __global__ void kernel_shared_conv(Rgb* device_img, Rgb* img, int width, int height, int strel_size)
383 | {
384 | int r = strel_size / 2;
385 | int block_w = TILE_WIDTH + 2 * r;
386 | extern __shared__ Rgb fast_acc_mat[];
387 | int ty = threadIdx.y;
388 | int tx = threadIdx.x;
389 | int row_o = blockIdx.y * TILE_WIDTH + ty;
390 | int col_o = blockIdx.x * TILE_HEIGHT + tx;
391 | int row_i = row_o - r;
392 | int col_i = col_o - r;
393 |
394 | if ((row_i >= 0) && (row_i < height) && (col_i >= 0) && (col_i < width))
395 | {
396 | auto elt = img[row_i * width + col_i];
397 | fast_acc_mat[ty * block_w + tx] = Rgb(elt.r, elt.g, elt.b);
398 | }
399 | else
400 | fast_acc_mat[ty * block_w + tx] = Rgb(0, 0, 0);
401 | __syncthreads();
402 |
403 | if (ty < TILE_HEIGHT && tx < TILE_WIDTH)
404 | {
405 | auto sum = Rgb(0, 0, 0);
406 | int cnt = 0;
407 | for (int i = 0; i < strel_size; i++)
408 | {
409 | for (int j = 0; j < strel_size; j++)
410 | {
411 | cnt++;
412 | sum.r += fast_acc_mat[(i + ty) * block_w + j + tx].r;
413 | sum.g += fast_acc_mat[(i + ty) * block_w + j + tx].g;
414 | sum.b += fast_acc_mat[(i + ty) * block_w + j + tx].b;
415 | }
416 | }
417 | if (row_o < height && col_o < width)
418 | {
419 | sum.r /= cnt;
420 | sum.g /= cnt;
421 | sum.b /= cnt;
422 | device_img[row_o * width + col_o] = sum;
423 | }
424 | }
425 | }
426 |
427 | // Hat function for the convolution algorithm
428 | __global__ void kernel_conv(Rgb* device_img, Rgb* img, int rows, int cols, int conv_size)
429 | {
430 | int cnt = 0;
431 | cnt = 0;
432 | int x = blockIdx.x * blockDim.x + threadIdx.x;
433 | int y = blockIdx.y * blockDim.y + threadIdx.y;
434 |
435 | if (x >= rows || y >= cols)
436 | return;
437 |
438 | for (int i = y - conv_size; i < y + conv_size && i < cols; i++)
439 | for (int j = x - conv_size; j < x + conv_size && j < rows; j++)
440 | {
441 | if (i >= 0 and j >= 0)
442 | {
443 | cnt++;
444 | device_img[x + y * rows].r += img[j + i * rows].r;
445 | device_img[x + y * rows].g += img[j + i * rows].g;
446 | device_img[x + y * rows].b += img[j + i * rows].b;
447 | }
448 | }
449 | if (cnt > 0)
450 | {
451 | device_img[x + y * rows].r /= cnt;
452 | device_img[x + y * rows].g /= cnt;
453 | device_img[x + y * rows].b /= cnt;
454 | }
455 | }
456 |
457 | // Pixelizes an image
458 | __global__ void kernel_pixelize(Rgb* device_img, Rgb* img, int rows, int cols, int pix_size)
459 | {
460 | extern __shared__ Rgb ds_img[];
461 | int bx = blockIdx.x;
462 | int by = blockIdx.y;
463 | int tx = threadIdx.x;
464 | int ty = threadIdx.y;
465 | int dimx = blockDim.x;
466 | int dimy = blockDim.y;
467 | int x = bx * dimx + tx;
468 | int y = by * dimy + ty;
469 | int cnt = 0;
470 |
471 | if (x >= rows || y >= cols)
472 | return;
473 |
474 | for (int u = 0; u < rows / pix_size + 1; u++)
475 | {
476 | for (int v = 0; v < cols / pix_size + 1; v++)
477 | {
478 | if (u == bx and v == by)
479 | {
480 | auto elt = img[x + y * rows];
481 | ds_img[ty * pix_size + tx] = Rgb(elt.r, elt.g, elt.b);
482 | __syncthreads();
483 |
484 | for (int i = y - pix_size; i < y + pix_size && i < cols; i++)
485 | {
486 | for (int j = x - pix_size; j < x + pix_size && j < rows; j++)
487 | {
488 | if (i >= 0 and j >= 0
489 | and i >= v * pix_size
490 | and i < (v + 1) * pix_size
491 | and j >= u * pix_size
492 | and j < (u + 1) * pix_size)
493 | {
494 | cnt++;
495 | int ds_x = j - u * pix_size;
496 | int ds_y = i - v * pix_size;
497 | auto elt = ds_img[ds_y * pix_size + ds_x];
498 | device_img[x + y * rows].r += elt.r;
499 | device_img[x + y * rows].g += elt.g;
500 | device_img[x + y * rows].b += elt.b;
501 | }
502 | }
503 | }
504 | __syncthreads();
505 | }
506 | }
507 | }
508 | device_img[x + y * rows].r /= cnt;
509 | device_img[x + y * rows].g /= cnt;
510 | device_img[x + y * rows].b /= cnt;
511 | }
512 |
513 |
--------------------------------------------------------------------------------
/src/gpu/kernel.cuh:
--------------------------------------------------------------------------------
1 | #include "opencv2/core/core.hpp"
2 | #include "opencv2/imgproc/imgproc.hpp"
3 | #include "opencv2/highgui/highgui.hpp"
4 |
5 |
6 | struct Rgb {
7 | __host__ __device__ Rgb() {}
8 | __host__ __device__ Rgb(double x, double y, double z) : r(x), g(y), b(z) {}
9 | __host__ __device__ Rgb(cv::Vec3b v) : r(v[0]), g(v[1]), b(v[2]) {}
10 | __host__ __device__ void div(int x)
11 | {
12 | r /= x;
13 | g /= x;
14 | b /= x;
15 | }
16 | double r;
17 | double g;
18 | double b;
19 | };
20 | __global__ void hysterysis(Rgb *device_img, int* changed, int width, int height, double t);
21 | __global__ void non_max_suppr(Rgb *device_img, double* img, int width, int height, double otsu_threshold);
22 | __global__ void sobel_conv(Rgb *device_img, double* img, int width, int height, int conv_size);
23 | __global__ void shared_knn(Rgb* device_img, Rgb* img, int width, int height, int conv_size, double h_param);
24 | __global__ void nlm(Rgb* device_img, Rgb* img, int width, int height, int conv_size, int block_radius, double h_param);
25 | __global__ void knn(Rgb* device_img, Rgb* img, int width, int height, int conv_size, double h_param);
26 | __global__ void kernel_shared_conv(Rgb* device_img, Rgb* img, int width, int height, int strel_size);
27 | __global__ void kernel_conv(Rgb* device_img, Rgb* img, int rows, int cols, int conv_size);
28 | __global__ void kernel_pixelize(Rgb* device_img, Rgb* img, int rows, int cols, int conv_size);
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/gpu/kernelcall.cu:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include "opencv2/core/core.hpp"
6 | #include "opencv2/imgproc/imgproc.hpp"
7 | #include "opencv2/highgui/highgui.hpp"
8 | #include "kernel.cuh"
9 |
10 | #define TILE_WIDTH 16
11 | #define TILE_HEIGHT 16
12 |
13 | // Transfers the image from GPU to CPU greyed out
14 | void device_to_img_grey(Rgb *device_img, cv::Mat& img)
15 | {
16 | int width = img.rows;
17 | int height = img.cols;
18 | for (int i = 0; i < height; i++)
19 | for (int j = 0; j < width; j++)
20 | img.at(j, i) = device_img[j + i * width].r;
21 | }
22 |
23 | // Transfers the image from GPU to CPU
24 | void device_to_img(Rgb *device_img, cv::Mat& img)
25 | {
26 | int width = img.rows;
27 | int height = img.cols;
28 | for (int i = 0; i < height; i++)
29 | for (int j = 0; j < width; j++)
30 | {
31 | img.at(j, i)[0] = device_img[j + i * width].r;
32 | img.at(j, i)[1] = device_img[j + i * width].g;
33 | img.at(j, i)[2] = device_img[j + i * width].b;
34 | }
35 | }
36 |
37 | // Pushed an image from the CPU to GPU greyed out
38 | double *img_to_device_grey(cv::Mat img)
39 | {
40 | double *device_img;
41 | int width = img.rows;
42 | int height = img.cols;
43 | cudaMallocManaged(&device_img, width * height * sizeof (double));
44 |
45 | for (int i = 0; i < height; i++)
46 | for (int j = 0; j < width; j++)
47 | device_img[j + i * width] = img.at(j, i);
48 |
49 | return device_img;
50 | }
51 |
52 | // Pushed an image from the CPU to GPU
53 | Rgb *img_to_device(cv::Mat img)
54 | {
55 | Rgb *device_img;
56 | int width = img.rows;
57 | int height = img.cols;
58 | cudaMallocManaged(&device_img, width * height * sizeof (Rgb));
59 |
60 | for (int i = 0; i < height; i++)
61 | for (int j = 0; j < width; j++)
62 | device_img[j + i * width] = Rgb(img.at(j, i));
63 |
64 | return device_img;
65 | }
66 |
67 | // Creates an empty grey image on the CPU
68 | double *empty_img_device_grey(cv::Mat img)
69 | {
70 | double *device_img;
71 | int width = img.rows;
72 | int height = img.cols;
73 | cudaMallocManaged(&device_img, width * height * sizeof (double));
74 |
75 | for (int i = 0; i < height; i++)
76 | for (int j = 0; j < width; j++)
77 | device_img[j + i * width] = 0.0;
78 | return device_img;
79 | }
80 |
81 | // Allocates an empty image on the GPU
82 | Rgb *empty_img_device(cv::Mat img)
83 | {
84 | Rgb *device_img;
85 | int width = img.rows;
86 | int height = img.cols;
87 | cudaMallocManaged(&device_img, width * height * sizeof (Rgb));
88 |
89 | for (int i = 0; i < height; i++)
90 | for (int j = 0; j < width; j++)
91 | device_img[j + i * width] = Rgb(0.0, 0.0, 0.0);
92 |
93 | return device_img;
94 | }
95 |
96 | // Implementation of the convolution algorithm with a shared memory optimization
97 | void kernel_shared_conv_host(Rgb* device_img, Rgb* img, int width, int height, int r)
98 | {
99 | int strel_size = 2 * r + r % 2;
100 | if (strel_size <= 0 or strel_size > 16)
101 | {
102 | std::cout << "\nerror: parameter must be between 1 and 16 due to shared memory constaint.\n" << std::endl;
103 | assert(strel_size > 0 and strel_size < 16);
104 | return;
105 | }
106 |
107 | // Creation of the gpu unit grid
108 | int block_w = TILE_WIDTH + 2 * r;
109 | dim3 blockSize = dim3(block_w, block_w);
110 | int bx = (width / TILE_WIDTH - 1) + blockSize.x;
111 | int by = (height / TILE_HEIGHT - 1) + blockSize.y;
112 | dim3 gridSize = dim3(bx, by);
113 |
114 | // Call the kernel shared_conv
115 | kernel_shared_conv<<>>(device_img, img, width, height, strel_size);
116 | }
117 |
118 | // Implementation of the convolution algorith
119 | void kernel_conv_host(Rgb* device_img, Rgb* img, int width, int height, int conv_size)
120 | {
121 | if (conv_size <= 0)
122 | {
123 | std::cout << "\nerror: parameter must be strictly greater than 0.\n" << std::endl;
124 | assert(conv_size > 0);
125 | return;
126 | }
127 |
128 | // Creation of the gpu unit grid
129 | dim3 blockSize = dim3(TILE_WIDTH, TILE_WIDTH);
130 | int bx = (width + blockSize.x - 1) / blockSize.x;
131 | int by = (height + blockSize.y - 1) / blockSize.y;
132 | dim3 gridSize = dim3(bx, by);
133 |
134 | // Calls the kernel conv
135 | kernel_conv<<>>(device_img, img, width, height, conv_size);
136 | }
137 |
138 | void kernel_pixelize_host(Rgb* device_img, Rgb* img, int width, int height, int pix_size)
139 | {
140 | if (pix_size <= 1 or pix_size > 32)
141 | {
142 | std::cout << "\nerror: parameter must be between 2 and 32 included.\n" << std::endl;
143 | assert(pix_size > 1 and pix_size < 33);
144 | return;
145 | }
146 |
147 | // Creation of the gpu unit grid
148 | dim3 blockSize = dim3(pix_size, pix_size);
149 | int bx = (width + blockSize.x - 1) / blockSize.x;
150 | int by = (height + blockSize.y - 1) / blockSize.y;
151 | dim3 gridSize = dim3(bx, by);
152 |
153 | // Call to the pixelize kernel
154 | kernel_pixelize<<>>(device_img, img, width, height, pix_size);
155 | }
156 |
157 | // Implementation of the K-Nearest Neighbors algorithm for de-noising a image
158 | void kernel_knn_host(Rgb* device_img, Rgb* img, int width, int height, int conv_size, double h_param)
159 | {
160 | // Creation of the gpu unit grid
161 | dim3 blockSize = dim3(TILE_WIDTH, TILE_WIDTH);
162 | int bx = (width + blockSize.x - 1) / blockSize.x;
163 | int by = (height + blockSize.y - 1) / blockSize.y;
164 | dim3 gridSize = dim3(bx, by);
165 |
166 | // Call to the knn kernel
167 | knn<<>>(device_img, img, width, height, conv_size, h_param);
168 | }
169 |
170 | // Implementation of the K-Nearest Neighbors algorithm for de-noising an image
171 | // with a shared memory optimization
172 | void kernel_shared_knn_host(Rgb* device_img, Rgb* img, int width, int height, int r, double h_param)
173 | {
174 | int strel_size = 2 * r + r % 2;
175 | if (strel_size <= 0 or strel_size > 16)
176 | {
177 | std::cout << "\nerror: parameter must be between 1 and 16 due to shared memory constaint.\n" << std::endl;
178 | assert(strel_size > 0 and strel_size < 16);
179 | return;
180 | }
181 | // Creation of the gpu unit grid
182 | int block_w = TILE_WIDTH + 2 * r;
183 | dim3 blockSize = dim3(block_w, block_w);
184 | int bx = (width / TILE_WIDTH - 1) + blockSize.x;
185 | int by = (height / TILE_HEIGHT - 1) + blockSize.y;
186 | dim3 gridSize = dim3(bx, by);
187 |
188 | // Call to the shared knn kernel
189 | shared_knn<<>>(device_img, img, width, height, strel_size, h_param);
190 | }
191 |
192 | // Implementation of the Non-Local Means algorithm for de-noising an image
193 | void kernel_nlm_host(Rgb* device_img, Rgb* img, int width, int height, int conv_size, int block_radius, double h_param)
194 | {
195 | // Creation of the gpu unit grid
196 | dim3 blockSize = dim3(TILE_WIDTH, TILE_WIDTH);
197 | int bx = (width + blockSize.x - 1) / blockSize.x;
198 | int by = (height + blockSize.y - 1) / blockSize.y;
199 | dim3 gridSize = dim3(bx, by);
200 |
201 | // Call to the nlm, kernel
202 | nlm<<>>(device_img, img, width, height, conv_size, block_radius, h_param);
203 | }
204 |
205 | // Implementation of the Canny Edge detection algorithm
206 | void kernel_nlm_host(Rgb* device_img, Rgb* img, int width, int height, int conv_size, int block_radius, double h_param)
207 | void kernel_edge_detect(Rgb* device_img, double* img, int width, int height, int conv_size, double otsu_threshold)
208 | {
209 | // Creation of the gpu unit grid
210 | dim3 blockSize = dim3(TILE_WIDTH, TILE_WIDTH);
211 | int bx = (width + blockSize.x - 1) / blockSize.x;
212 | int by = (height + blockSize.y - 1) / blockSize.y;
213 | dim3 gridSize = dim3(bx, by);
214 |
215 | // Preprocessing
216 | // Apply a convolution on the image using the Sobel kernel
217 | sobel_conv<<>>(device_img, img, width, height, conv_size);
218 | cudaDeviceSynchronize();
219 |
220 | non_max_suppr<<>>(device_img, img, width, height, otsu_threshold);
221 | cudaDeviceSynchronize();
222 |
223 | // Run the hysterysis algorithm, stops when the image is unchanged
224 | int *changed_device;
225 | int *changed_host;
226 | cudaMallocManaged(&changed_device, 1 * sizeof (int));
227 | hysterysis<<>>(device_img, changed_device, width, height, otsu_threshold * 0.5);
228 | cudaDeviceSynchronize();
229 | cudaMemcpy(changed_host, changed_device, sizeof (int), cudaMemcpyDeviceToHost);
230 |
231 | while (changed_host)
232 | {
233 | hysterysis<<>>(device_img, changed_device, width, height, otsu_threshold * 0.5);
234 | cudaDeviceSynchronize();
235 | cudaMemcpy(changed_host, changed_device, sizeof (int), cudaMemcpyDeviceToHost);
236 | }
237 | }
238 |
239 |
240 |
--------------------------------------------------------------------------------
/src/gpu/kernelcall.cuh:
--------------------------------------------------------------------------------
1 | #include "kernel.cuh"
2 | #include "opencv2/core/core.hpp"
3 | #include "opencv2/imgproc/imgproc.hpp"
4 | #include "opencv2/highgui/highgui.hpp"
5 | #include
6 | #include
7 |
8 |
9 | Rgb *img_to_device(cv::Mat img);
10 | Rgb *empty_img_device(cv::Mat img);
11 | void device_to_img(Rgb *device_img, cv::Mat& img);
12 |
13 | double *img_to_device_grey(cv::Mat img);
14 | double *empty_img_device_grey(cv::Mat img);
15 | void device_to_img_grey(Rgb *device_img, cv::Mat& img);
16 |
17 | void kernel_edge_detect(Rgb* device_img, double* img, int width, int height, int conv_size, double otsu_threshold);
18 | void kernel_shared_knn_host(Rgb* device_img, Rgb* img, int width, int height, int r, double h_param);
19 | void kernel_knn_host(Rgb* device_img, Rgb* img, int width, int height, int conv_size, double h_param);
20 | void kernel_nlm_host(Rgb* device_img, Rgb* img, int width, int height, int conv_size, int block_size, double h_param);
21 | void kernel_shared_conv_host(Rgb* device_img, Rgb* img, int width, int height, int strel_size);
22 | void kernel_conv_host(Rgb* device_img, Rgb* img, int rows, int cols, int conv_size);
23 | void kernel_pixelize_host(Rgb* device_img, Rgb* img, int rows, int cols, int conv_size);
24 | void kernel_shared_conv_host(Rgb* device_img, Rgb* img, int width, int height, int conv_size);
25 | void kernel_shared_knn_host(Rgb* device_img, Rgb* img, int width, int height, int conv_size, double h_param);
26 |
--------------------------------------------------------------------------------
/src/gpu/main.cu:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include "opencv2/core/core.hpp"
6 | #include "opencv2/imgproc/imgproc.hpp"
7 | #include "opencv2/highgui/highgui.hpp"
8 | #include "kernelcall.cuh"
9 |
10 | #define TILE_WIDTH 16
11 | #define TILE_HEIGHT 16
12 | #define STREL_SIZE 5
13 | #define R (STREL_SIZE / 2)
14 | #define BLOCK_W (TILE_WIDTH + (2 * R))
15 | #define BLOCK_H (TILE_HEIGHT + (2 * R))
16 |
17 |
18 | int main(int argc, char** argv)
19 | {
20 | if (argc < 3)
21 | {
22 | std::cout << "usage: main " << std::endl;
23 | return 1;
24 | }
25 | std::string func_name = argv[2];
26 | if (func_name == "knn" && argc < 5)
27 | {
28 | std::cout << "usage: main knn " << std::endl;
29 | return 1;
30 | }
31 | if (func_name == "nlm" && argc < 6)
32 | {
33 | std::cout << "usage: main knn " << std::endl;
34 | return 1;
35 | }
36 | cv::Mat image;
37 | image = cv::imread(argv[1], CV_LOAD_IMAGE_UNCHANGED);
38 | if (!image.data)
39 | {
40 | std::cout << "Could not open or find the image" << std::endl;
41 | return 1;
42 | }
43 |
44 | int width = image.rows;
45 | int height = image.cols;
46 |
47 | Rgb* device_dst;
48 | Rgb* device_img;
49 | Rgb* out;
50 | Rgb* device_dst_grey;
51 | double* device_img_grey;
52 | Rgb* out_grey;
53 |
54 | cv::Mat grey_img;
55 | device_dst = empty_img_device(image);
56 | device_img = img_to_device(image);
57 | out = (Rgb*)malloc(width * height * sizeof (Rgb));
58 |
59 | if (func_name == "pixelize")
60 | kernel_pixelize_host(device_dst, device_img, width, height, std::stoi(argv[3]));
61 | else if (func_name == "conv")
62 | kernel_conv_host(device_dst, device_img, width, height, std::stoi(argv[3]));
63 | else if (func_name == "shared_conv")
64 | kernel_shared_conv_host(device_dst, device_img, width, height, std::stoi(argv[3]));
65 | else if (func_name == "knn")
66 | kernel_knn_host(device_dst, device_img, width, height, std::stoi(argv[3]), std::stod(argv[4]));
67 | else if (func_name == "shared_knn")
68 | kernel_shared_knn_host(device_dst, device_img, width, height, std::stoi(argv[3]), std::stod(argv[4]));
69 | else if (func_name == "nlm")
70 | kernel_nlm_host(device_dst, device_img, width, height, std::stoi(argv[3]), std::stoi(argv[4]), std::stod(argv[5]));
71 | else if (func_name == "edge_detect")
72 | {
73 | cv::cvtColor(image, grey_img, cv::COLOR_BGR2GRAY);
74 | device_dst_grey = empty_img_device(grey_img);
75 | device_img_grey = img_to_device_grey(grey_img);
76 | out_grey = (Rgb*)malloc(width * height * sizeof (Rgb));
77 | cv::Mat dst;
78 | double otsu_threshold = cv::threshold(grey_img, dst, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
79 | kernel_edge_detect(device_dst_grey, device_img_grey, width, height, 1, otsu_threshold);
80 | }
81 | else
82 | {
83 | std::cout << "error: function name '" << func_name << "' is not known." << std::endl;
84 | cudaFree(device_dst);
85 | cudaFree(device_img);
86 | free(out);
87 | return 1;
88 | }
89 |
90 | cudaDeviceSynchronize();
91 | cv::namedWindow("Display Window", CV_WINDOW_AUTOSIZE);
92 | if (func_name != "edge_detect")
93 | {
94 | cudaMemcpy(out, device_dst, height * width * sizeof (Rgb), cudaMemcpyDeviceToHost);
95 |
96 | device_to_img(out, image);
97 |
98 | cudaFree(device_dst);
99 | cudaFree(device_img);
100 | free(out);
101 | cv::imshow("Display Window", image);
102 | }
103 | else
104 | {
105 | cudaMemcpy(out_grey, device_dst_grey, height * width * sizeof (Rgb), cudaMemcpyDeviceToHost);
106 |
107 | device_to_img_grey(out_grey, grey_img);
108 |
109 | cudaFree(device_dst_grey);
110 | cudaFree(device_img_grey);
111 | free(out_grey);
112 | cv::imshow("Display Window", grey_img);
113 | }
114 | cv::waitKey(0);
115 | grey_img.release();
116 | image.release();
117 | return 0;
118 | }
119 |
--------------------------------------------------------------------------------
/src/utils/utils.cpp:
--------------------------------------------------------------------------------
1 | #include "utils.hh"
2 | #include
3 | #include
4 |
5 |
6 | Mat& subDFT(Mat& I)
7 | {
8 | CV_Assert(I.depth() == CV_8U);
9 | const int channels = I.channels();
10 | int nRows = I.rows;
11 | int nCols = I.cols;
12 | const std::complex i(0, 1);
13 | const double pi = std::acos(-1);
14 | switch(channels)
15 | {
16 | case 1:
17 | {
18 | Mat I2(nrows, 3,CV_32FC3, 1);
19 | for (int r = 0; i < nRows; ++r)
20 | {
21 | for (int c = 0; c < nCols; ++c)
22 | {
23 | auto pix = I.at(i,j);
24 | I2.at(i,j) = std::exp(-2 * i * pi )
25 | }
26 | }
27 | break;
28 | }
29 | case 3:
30 | {
31 | Mat I2(nrows, 3,CV_32FC3, Scalar(1,1,1));
32 | for (int i = 0; i < nRows; ++i)
33 | {
34 | cv::Vec3b* pixel = I.ptr(i); // point to first pixel in row
35 | cv::Vec3f* pixeld = I.ptr(i); // point to first pixel in row
36 | for (int j = 0; j < nCols; ++j)
37 | {
38 | }
39 | }
40 | }
41 | }
42 | return I;
43 | }
--------------------------------------------------------------------------------
/src/utils/utils.hh:
--------------------------------------------------------------------------------
1 | #pragma once
2 | using namespace cv;
3 |
4 | #include "opencv2/core/core.hpp"
5 | #include "opencv2/imgproc/imgproc.hpp"
6 | #include "opencv2/highgui/highgui.hpp"
7 |
8 |
9 | Mat& DFT(Mat& I2);
--------------------------------------------------------------------------------