Skip to content

Commit 51dcd4c

Browse files
committed
Done with project.
0 parents  commit 51dcd4c

36 files changed

+6277
-0
lines changed

Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
3+
CC=g++
4+
CFLAGS= -std=gnu++11 -O2 -Iinclude -I/usr/include/python3.10
5+
LDFLAGS= -lpython3.10
6+
DEBUG=-g
7+
SRC_DIR=src
8+
INCLUDE_DIR=include
9+
BIN_DIR=bin
10+
OUTPUT_DIR=output
11+
12+
all: $(BIN_DIR) main
13+
14+
main: $(BIN_DIR)/Convolution.o $(BIN_DIR)/Filter.o $(SRC_DIR)/main.cpp
15+
$(CC) $(CFLAGS) $(SRC_DIR)/main.cpp $(BIN_DIR)/Convolution.o $(BIN_DIR)/Filter.o -o $(OUTPUT_DIR)/main $(LDFLAGS)
16+
17+
$(BIN_DIR)/Convolution.o: $(SRC_DIR)/Convolution.cpp $(INCLUDE_DIR)/Convolution.hpp $(BIN_DIR)/Filter.o
18+
$(CC) $(CFLAGS) -c $(SRC_DIR)/Convolution.cpp -o $(BIN_DIR)/Convolution.o
19+
20+
$(BIN_DIR)/Filter.o: $(SRC_DIR)/Filter.cpp $(INCLUDE_DIR)/Filter.hpp
21+
$(CC) $(CFLAGS) -c $(SRC_DIR)/Filter.cpp -o $(BIN_DIR)/Filter.o
22+
23+
$(BIN_DIR):
24+
mkdir -p $(BIN_DIR)
25+
26+
clean:
27+
rm -rf $(BIN_DIR) $(OUTPUT_DIR)/main
28+

README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Convolutional filter(s) for images
2+
3+
* Images are stored in folder images/. All images should be of the same size.
4+
5+
* Python Imaging Library (PIL) was used to convert images into RGB matrices and to convert filtered matrices back to images. PIL was preferred over other C++ libraries due to ease of use.
6+
7+
## Code structure
8+
* filter.hpp defines a 3-D convolutinal kernel class with a bias term. It contains some helper functions to allocate memory to tensors and to normalize them.
9+
* Convolution.hpp defines a convolutional layer. One can set the stride and zero-padding of the filter in this. Also, dimensions of the output layer are calculated automatically.
10+
* conv2d method takes as argument a 3-D data volume and a list of filters (one filter generates one activation map). For example, applying a 3 x 3 x 3 filter on a 512 x 512 x 3 image (with 1 zero padding and 1 stride) will generate an 2-D output layer of 512 x 512. See example (taken from course [cs231n](http://cs231n.stanford.edu/syllabus.html)).
11+
![One filter](./images/one_map.png)
12+
* List of filters would make the output layer. Shape of output layer as well as the data block is returned by the function conv2d.
13+
![Many filter](./images/multi_map.png)
14+
* main.cpp runs some example filters on a batch of 3 images. It generates 3 filters, one as an edge detector for each color channel (see push\_filter). Then defines
15+
a convolution layer with given params and applies the layer to each of the images. It then writes the output to a different file.
16+
*Inside helpers directory, we have make\_mats.py and load\_img.py that are used to generate images\-matrices and vice versa.
17+
18+
```cpp
19+
for (int id = 0; id < num_images; ++id) {
20+
...
21+
auto output = clayer.conv2d(input, filters);
22+
...
23+
}
24+
```
25+
26+
## Steps to run
27+
* First and foremost install the required libraries:
28+
29+
```bash
30+
sudo apt update
31+
sudo apt install python3-pip
32+
pip install -r requirements.txt
33+
```
34+
35+
* Compile using make. Build file for convolutional filter demo program is 'main'. See run.sh for a complete run
36+
37+
* The ```run.sh``` file allows us to run both ```makefile``` and the rest of python commands that's going to execute the overall project.
38+
To run the project:
39+
1. First of all make sure that ```run.sh``` has excutable permissions:
40+
41+
```bash
42+
chmod +x run.sh
43+
```
44+
2. Run the ```run.sh``` file:
45+
46+
47+
```bash
48+
./run.sh
49+
```
50+
51+
```bash
52+
rm *.o main
53+
g++ -std=gnu++11 -O2 filter.hpp -o filter.o
54+
g++ -std=gnu++11 -O2 conv2d_layer.hpp -o conv2d_layer.o
55+
g++ -std=gnu++11 -O2 main.cpp -o main
56+
```
57+
58+
* List of images to use is in file make\_mats.py. In the demo it uses a batch of 3 512 \* 512 \* 3 (color) images.
59+
60+
```bash
61+
python3 make_mats.py img_mats/out.dat
62+
```
63+
64+
* Run the convolutional filter (read from standard input)
65+
66+
```bash
67+
./main img_mats/out.dat img_mats/filter_out.dat < filter.txt
68+
```
69+
70+
* Make output images from matrices
71+
72+
```bash
73+
python3 load_img.py img_mats/filter_out.dat out_mats
74+
```
75+
76+
## Results
77+
78+
You can checkout the image results in the out_mats directory. The output images are generated by applying a filter to the input images.
79+
The filter is defined in filter.txt, which is the given kernel with 1/273 normalization factor. So the Matrix is stored on the file after normalization.
80+
81+
The output images are stored in out_mats inside output directory.
82+

backup.cpp

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#include <iostream>
2+
#include <fstream>
3+
#include <cstdio>
4+
#include <vector>
5+
#include "Filter.hpp"
6+
#include "Convolution.hpp"
7+
8+
using namespace std;
9+
10+
int w_size, bias; // kernel window size
11+
double** kernel; // image kernel
12+
vector<Filter*> filters;
13+
14+
void push_filter(int idx) {
15+
// make edge detector for color idx
16+
double ***ed = get_tensor(w_size, w_size, 3);
17+
for (int i = 0; i < w_size; ++i) {
18+
for (int j = 0; j < w_size; ++j) {
19+
ed[i][j][idx] = kernel[i][j];
20+
}
21+
}
22+
Filter *f = new Filter(ed, w_size, 3, bias);
23+
f->normalize();
24+
filters.push_back(f);
25+
}
26+
27+
int main(int argc, char *argv[]) {
28+
29+
// input the kernel matrix
30+
cin >> w_size >> bias;
31+
kernel = new double*[w_size];
32+
for (int i = 0; i < w_size; i++) {
33+
kernel[i] = new double[w_size];
34+
for (int j = 0; j < w_size; j++) {
35+
cin >> kernel[i][j];
36+
}
37+
}
38+
39+
push_filter(0); // R
40+
push_filter(1); // G
41+
push_filter(2); // B
42+
if (argc < 3) {
43+
cerr << "Usage <input_data file name> <output file name>" << endl;
44+
return 1;
45+
}
46+
// output file will be written in the same format as input file
47+
ifstream ifile (argv[1]);
48+
ofstream ofile (argv[2]);
49+
if (!ofile.is_open()) {
50+
cerr << "Unable to open " << argv[1] << endl;
51+
return 1;
52+
}
53+
if (!ifile.is_open()) {
54+
cerr << "Unable to open " << argv[0] << endl;
55+
return 1;
56+
}
57+
58+
int width, num_images, height, depth;
59+
int stride = 1, padding = 1;
60+
ifile >> num_images >> width >> height >> depth;
61+
ofile << num_images << " ";
62+
cerr << num_images << " ";
63+
64+
Convolution clayer(width, height, depth, w_size, stride, padding, filters.size());
65+
double ***input;
66+
input = get_tensor(width, height, depth);
67+
68+
for (int id = 0; id < num_images; ++id) {
69+
70+
// read one image
71+
for (int i = 0; i < width; ++i) {
72+
for (int j = 0; j < height; ++j) {
73+
for (int k = 0; k < depth; ++k) {
74+
ifile >> input[i][j][k];
75+
if (ifile.peek() == ',') ifile.ignore();
76+
}
77+
}
78+
}
79+
auto output = clayer.conv2d(input, filters);
80+
double ***out_volume = get<3>(output);
81+
int o_width = get<0>(output), o_height = get<1>(output), o_depth = get<2>(output);
82+
if (id == 0) {
83+
// print image dimensions only the first time
84+
ofile << o_width << " " << o_height << " " << o_depth << "\n";
85+
cerr << o_width << " " << o_height << " " << o_depth << "\n";
86+
}
87+
88+
for (int i = 0; i < o_width; ++i) {
89+
for (int j = 0; j < o_height; ++j) {
90+
ofile << out_volume[i][j][0] << "," << out_volume[i][j][1]
91+
<< "," << out_volume[i][j][2] << " ";
92+
}
93+
ofile << "\n";
94+
}
95+
ofile << "\n";
96+
}
97+
ifile.close();
98+
ofile.close();
99+
100+
return 0;
101+
}

bin/Convolution.o

3.04 KB
Binary file not shown.

bin/Filter.o

3.54 KB
Binary file not shown.

helpers/load_img.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/python
2+
3+
from PIL import Image
4+
import pylab
5+
import sys
6+
import numpy as np
7+
8+
if __name__ == "__main__":
9+
10+
f = open(sys.argv[1] ,"r")
11+
shape = list(map(int, f.readline().split()))
12+
for idx in range(shape[0]):
13+
im = np.empty(shape=(shape[1], shape[2], shape[3]))
14+
for i in range(shape[1]):
15+
row = f.readline().split()
16+
assert(len(row) == shape[2]) # 300
17+
for j in range(shape[2]):
18+
im[i][j] = list(map(float, row[j].split(",")))
19+
result = Image.fromarray(im.astype(np.uint8))
20+
result.save(sys.argv[2] + "/" + str(idx) + ".bmp")
21+
f.readline() # empty line

helpers/make_mats.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/python
2+
3+
import numpy as np
4+
from PIL import Image
5+
import pylab
6+
import sys
7+
import os
8+
9+
dirname = 'input/'
10+
#colored = ["airplane.png", "lena.png", "fruits.png"] should be 300 * 300 * 3 = (300, 300, 3)
11+
#gray = ["cloud.png", "horse.png", "moon.png", "view.png"] # should be 300 * 300 * 1 = (300, 300)
12+
13+
def append_matrix(filepath, outf):
14+
img = Image.open(filepath)
15+
im = np.asarray(img, dtype='float64')
16+
#print(im.shape)
17+
for i in range(im.shape[0]):
18+
for j in range(im.shape[1]):
19+
outf.write("%f,%f,%f " %(im[i][j][0], im[i][j][1], im[i][j][2]))
20+
outf.write('\n')
21+
outf.write('\n')
22+
23+
if __name__ == "__main__":
24+
f = open(sys.argv[1], "w")
25+
26+
# 3 for color, 1 for grey
27+
#depth = 3
28+
depth = 3
29+
30+
foldername = dirname + 'grey/' # grey for grey folder(which actually contains colored images)
31+
32+
filenames = os.listdir(foldername)
33+
f.write("%d 300 300 %d\n" % (len(filenames), depth)) # image tensor dimensions
34+
35+
for filename in filenames:
36+
append_matrix(foldername + filename, f)

include/Convolution.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#ifndef CONVOLUTION_H
2+
#define CONVOLUTION_H
3+
4+
#include <vector>
5+
#include <tuple>
6+
#include "Filter.hpp"
7+
8+
class Convolution {
9+
int in_width, in_height, in_depth,
10+
n_filters, window, stride, padding,
11+
out_width, out_height, out_depth;
12+
13+
public:
14+
Convolution(int _width, int _height, int _depth, int _window, int _stride = 1, int _padding = 0, int n_filters = 1);
15+
16+
~Convolution();
17+
18+
// Applies convolutional filters in filters to input volume x
19+
// x treated as in_width * in_height * in_depth
20+
// assumed n_filters elements in filters vector
21+
// returns shape of the output volume and pointer to the memory block
22+
std::tuple<int, int, int, double ***> conv2d(double ***x, const std::vector<Filter*> &filters);
23+
};
24+
25+
#endif

include/Filter.hpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#ifndef FILTER_HPP
2+
#define FILTER_HPP
3+
4+
#include <cmath>
5+
6+
// allocate memory for a tensor
7+
double ***get_tensor(int x, int y, int z);
8+
9+
class Filter {
10+
public:
11+
double ***w, b; // kernel matrix, bias term
12+
int window, depth;
13+
14+
Filter(int _window, int _depth);
15+
Filter(double ***_w, int _window, int _depth, int _b = 0);
16+
~Filter();
17+
18+
// normalize the tensor
19+
void normalize();
20+
};
21+
22+
#endif

0 commit comments

Comments
 (0)