├── Basic.py
├── CannyOperator.py
├── CloseAreaDetect.py
├── Edge.py
├── README.md
├── RegionGrow.py
└── img
├── Basic
├── Gussian.jpg
├── closing.jpg
├── compress.jpg
├── gradient.jpg
├── mean.jpg
├── opening.jpg
└── source.jpg
└── CloseAreaDetect
├── gray.jpg
├── result.jpg
└── source.jpg
/Basic.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import numpy as np
3 |
4 | class Basic():
5 |
6 | def __init__(self,
7 | IMG_PATH = "G:/Deecamp/1.jpg",
8 | Save_PATH = "G:/Deecamp/1/gradient.jpg"
9 | ):
10 | self.IMG_PATH = IMG_PATH
11 | self.Save_PATH = Save_PATH
12 |
13 |
14 | def CompressChannel(self, img):
15 | # 1.Compress the value of each channel from 256 kinds to 16 kinds.
16 | # Disadvantages: There will be obvious edges in the continuous color block.
17 | for i in range(len(img)):
18 | for j in range(len(img[0])):
19 | for k in range(len(img[0][0])):
20 | img[i][j][k] = int(img[i][j][k] / 16) * 16
21 | cv2.namedWindow('compress image', cv2.WINDOW_NORMAL)
22 | cv2.imshow('compress image', img)
23 | cv2.waitKey(0)
24 | return img
25 |
26 |
27 | def AdaThreshold(self, img, returnImg = "mean"):
28 | # 2.Set an adaptive threshold for images with uneven brightness distribution.
29 | if (len(img.shape)==3):
30 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
31 | # Median filtering
32 | img = cv2.medianBlur(img,5)
33 |
34 | # Parameters in function: input image, max threshold, adaptive Method(mean/Gussian), threshold type,
35 | # block size(odd number), constant(value = value - C)
36 | # NOTE: this function has 2 output images, so choose one to return.
37 | if (returnImg == "mean"):
38 | img1 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_MEAN_C,\
39 | cv2.THRESH_BINARY,11,2)
40 | print("2")
41 | cv2.namedWindow('mean', cv2.WINDOW_NORMAL)
42 | cv2.imshow('mean', img1)
43 | cv2.waitKey(0)
44 | return img1
45 | if (returnImg == "Gussian"):
46 | img2 = cv2.adaptiveThreshold(img,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,\
47 | cv2.THRESH_BINARY,11,2)
48 | cv2.namedWindow('Gussian', cv2.WINDOW_NORMAL)
49 | cv2.imshow('Gussian', img2)
50 | cv2.waitKey(0)
51 | return img2
52 |
53 |
54 | def Opening(self, img, ErodeIter = 1, DilateIter = 1, KernelSize = 5):
55 | # 3.We always erode --> dilate to remove samll background-objects/noises, fill some edges.
56 | # Sometimes we should set iterations instead of using 'cv2.morphologyEx', same as below.
57 | # Be careful of the size of the kernel, same as below.
58 | if (len(img.shape)==3):
59 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
60 | cv2.namedWindow("source image", cv2.WINDOW_NORMAL)
61 | cv2.imshow("source image", img)
62 | cv2.waitKey(0)
63 | kernel = np.ones((KernelSize, KernelSize), np.uint8)
64 |
65 | for i in range(ErodeIter):
66 | img = cv2.erode(img, kernel)
67 | cv2.namedWindow("erosion" + str(i), cv2.WINDOW_NORMAL)
68 | cv2.imshow("erosion" + str(i), img)
69 | cv2.waitKey(0)
70 |
71 | for i in range(DilateIter):
72 | img = cv2.dilate(img, kernel)
73 | cv2.namedWindow("dilation" + str(i), cv2.WINDOW_NORMAL)
74 | cv2.imshow("dilation" + str(i), img)
75 | cv2.waitKey(0)
76 |
77 | # opening = cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel)
78 | return img
79 |
80 |
81 | def Closing(self, img, ErodeIter = 1, DilateIter = 1, KernelSize = 5):
82 | # 4.We always dilate --> erode to splice break edges.
83 | if (len(img.shape)==3):
84 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
85 | cv2.namedWindow("source image", cv2.WINDOW_NORMAL)
86 | cv2.imshow("source image", img)
87 | cv2.waitKey(0)
88 | kernel = np.ones((KernelSize, KernelSize), np.uint8)
89 |
90 | for i in range(DilateIter):
91 | img = cv2.dilate(img, kernel)
92 | cv2.namedWindow("dilation" + str(i), cv2.WINDOW_NORMAL)
93 | cv2.imshow("dilation" + str(i), img)
94 | cv2.waitKey(0)
95 |
96 | for i in range(ErodeIter):
97 | img = cv2.erode(img, kernel)
98 | cv2.namedWindow("erosion" + str(i), cv2.WINDOW_NORMAL)
99 | cv2.imshow("erosion" + str(i), img)
100 | cv2.waitKey(0)
101 |
102 | # closing = cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel)
103 | return img
104 |
105 |
106 | def Gradient(self, img, KernelSize = 3, SaveImage = False):
107 | # 5. gradient = dilation - erosion, which is some kind of edge.
108 | if (len(img.shape)==3):
109 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
110 | kernel = np.ones((KernelSize, KernelSize), np.uint8)
111 | # img = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
112 | img = cv2.dilate(img, kernel) - cv2.erode(img, kernel)
113 | if(SaveImage):
114 | cv2.imwrite(self.Save_PATH, img)
115 | cv2.namedWindow("gradient", cv2.WINDOW_NORMAL)
116 | cv2.imshow("gradient", img)
117 | cv2.waitKey(0)
118 | cv2.destroyAllWindows()
119 | return img
120 |
121 |
122 | # test code
123 | if __name__=="__main__":
124 | op = Basic()
125 | img = cv2.imread(op.IMG_PATH)
126 | img = op.CompressChannel(img)
127 | # img = op.AdaThreshold(img)
128 | # img = op.Opening(img)
129 | # img = op.Closing(img)
130 | img = op.Gradient(img, SaveImage = True)
--------------------------------------------------------------------------------
/CannyOperator.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import numpy as np
3 |
4 | class ImageChange():
5 |
6 | def __init__(self,
7 | IMG_PATH = "G:/Deecamp/1.jpg",
8 | Static_Low_Threshold = 30,
9 | Static_High_Threshold = 80,
10 | Dynamic_Low_Threshold = 0,
11 | Dynamic_High_Threshold = 0,
12 | ):
13 | self.IMG_PATH = IMG_PATH
14 | self.Static_Low_Threshold = Static_Low_Threshold
15 | self.Static_High_Threshold = Static_High_Threshold
16 | self.Dynamic_Low_Threshold = Dynamic_Low_Threshold
17 | self.Dynamic_High_Threshold = Dynamic_High_Threshold
18 |
19 |
20 | '''static Canny operator'''
21 | def StaticCanny(self, img):
22 | # Canny needs grayscale images
23 | if (len(img.shape)==3):
24 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
25 | # Gaussian smoothing for noise reduction
26 | # aperture size = 3
27 | img = cv2.GaussianBlur(img,(3,3), 0)
28 | canny = cv2.Canny(img, self.Static_Low_Threshold, self.Static_High_Threshold)
29 |
30 | cv2.imshow('Canny', canny)
31 | cv2.waitKey(0)
32 | cv2.destroyAllWindows()
33 | return img
34 |
35 |
36 | '''Use canny to change the image while high threshold is 3*low_threshold.'''
37 | def DynamicCannyBase(self, low_threshold):
38 | img = cv2.imread(self.IMG_PATH)
39 | gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
40 | canny = cv2.GaussianBlur(gray_img, (3,3), 0)
41 | # low_threshold comes from the function 'DynamicThresholdTrackbar'.
42 | canny = cv2.Canny(canny, low_threshold, 3*low_threshold)
43 | # add some colours to edges from 'canny'image.
44 | new_canny = cv2.bitwise_and(img, img, mask = canny)
45 | cv2.imshow('canny demo', new_canny)
46 | # if cv2.waitKey(0) == 27:
47 | # cv2.destroyAllWindows()
48 |
49 |
50 | '''Get dynamic threshold(low) for dynamic canny function.'''
51 | def DynamicThresholdTrackbar(self):
52 | # Create a window.
53 | cv2.namedWindow('Dynamic Canny')
54 | # Create trackbars for low threshold and return it for fuction 'DynamicCanny'.
55 | cv2.createTrackbar('Low threshold', 'Dynamic Canny', self.Dynamic_Low_Threshold, 200, self.DynamicCannyBase)
56 | # cv2.createTrackbar('High threshold', 'Dynamic Canny', self.Dynamic_High_Threshold, 500, self.DynamicCanny)
57 |
58 |
59 | def nothing():
60 | pass
61 |
62 |
63 | def DynamicCanny(self, img, img_path = None, kernel_size = 3):
64 | cv2.namedWindow('thresholds')
65 | # function 'nothing' means pass.
66 | cv2.createTrackbar('low threshold', 'thresholds', 0, 200, self.nothing)
67 | cv2.createTrackbar('high threshold', 'thresholds', 0, 1000, self.nothing)
68 | while(1):
69 | self.Dynamic_Low_Threshold = cv2.getTrackbarPos('low threshold', 'thresholds')
70 | self.Dynamic_High_Threshold = cv2.getTrackbarPos('high threshold', 'thresholds')
71 | if img_path == None:
72 | img_path = self.IMG_PATH
73 | img = cv2.imread(img_path, 0)
74 | img = cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)
75 | img = cv2.Canny(img, self.Dynamic_Low_Threshold, self.Dynamic_High_Threshold)
76 | img = cv2.bitwise_and(img, img, mask = img)
77 | cv2.imshow('canny demo', img)
78 | stop = cv2.waitKey(1)
79 | # press Esc to exit.
80 | if stop == 27:
81 | break
82 | # Press 'Space' to save parameters and go on.
83 | if stop == 32:
84 | return img
85 | cv2.destroyAllWindows()
86 |
87 |
88 | '''Split the source image to 3 channels(r,g,b), find each image's edge through Canny,
89 | then bitwise AND and merge 3 new edges.'''
90 | def SplitMerge(self, img):
91 | b = cv2.Canny(cv2.GaussianBlur(img[:,:,0], (3,3), 0), self.Static_Low_Threshold, self.Static_High_Threshold)
92 | g = cv2.Canny(cv2.GaussianBlur(img[:,:,1], (3,3), 0), self.Static_Low_Threshold, self.Static_High_Threshold)
93 | r = cv2.Canny(cv2.GaussianBlur(img[:,:,2], (3,3), 0), self.Static_Low_Threshold, self.Static_High_Threshold)
94 |
95 | BitwiseAnd = cv2.bitwise_and(b,g,r)
96 | cv2.imshow('BitwiseAnd', BitwiseAnd)
97 | cv2.waitKey(0)
98 | cv2.destroyAllWindows()
99 | return img
100 |
101 | # test code
102 | if __name__=="__main__":
103 | op = ImageChange()
104 | img = cv2.imread(op.IMG_PATH)
105 | # 1.Static Canny
106 | '''
107 | img = op.StaticCanny(img)
108 | '''
109 | # 2.Dynamic Canny for one threshold.
110 | '''
111 | op.DynamicThresholdTrackbar()
112 | op.DynamicCannyBase(0)
113 | if cv2.waitKey(0) == 27:
114 | cv2.destroyAllWindows()
115 | '''
116 | # 3. Dynamic Canny for two threshold.
117 |
118 | img = op.DynamicCanny(img)
119 |
120 | # 4. Canny for each channel and then bitwise AND.
121 | '''
122 | img = op.SplitMerge(img)
123 | '''
--------------------------------------------------------------------------------
/CloseAreaDetect.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import numpy as np
3 | import random
4 |
5 | class CloseAreaDetect():
6 |
7 | def __init__(self,
8 | IMG_PATH = "G:/Deecamp/6.jpg",
9 | Save_PATH = "G:/Deecamp/closeArea.jpg",
10 | Gray_Threshold = 200
11 | ):
12 | self.IMG_PATH = IMG_PATH
13 | self.Save_PATH = Save_PATH
14 | self.Gray_Threshold = Gray_Threshold
15 |
16 |
17 | def CloseArea(self, img, GrayWhiteChange = False, MinAreaSize = 10, SaveImage = False):
18 | # 1.Get grayscale for close area judgement.
19 | cv2.imshow('source image', img)
20 | cv2.waitKey(0)
21 | if (len(img.shape)==3):
22 | gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
23 | elif (len(img.shape)==2):
24 | gray_img = img
25 | else:
26 | print("The image is neither RGB or grayscale.")
27 |
28 | # 2.Divide pixels to 2 categories through threshold (colors in original image may not be just balck and white).
29 | # Gray image just has 2 channels.
30 | # print(type(img)):np.adarray; So we use 'img.shape' instead of 'len(img[0][0])'.
31 | for i in range(gray_img.shape[0]):
32 | for j in range(gray_img.shape[1]):
33 | # The threshold should be set manually.
34 | if not (GrayWhiteChange):
35 | gray_img[i][j] = 255 if gray_img[i][j]>=self.Gray_Threshold else 0
36 | else:
37 | gray_img[i][j] = 0 if gray_img[i][j]>=self.Gray_Threshold else 255
38 | cv2.imshow('gray image', gray_img)
39 | cv2.waitKey(0)
40 |
41 | # 3.Detect connected components and Mark one by one.
42 | _, labels = cv2.connectedComponents(gray_img, connectivity=8)
43 | AreaNum = np.max(labels)
44 | print("There are {} closed ares.".format(AreaNum) )
45 |
46 | # 4.Create a blank image and color each closed area.
47 | # I don't know why image.shape is (w,h) instead of (w,h,channel=3). Debug for a long time.
48 | new_img = np.zeros((img.shape[0],img.shape[1],3), np.uint8)
49 | color_list = [(255,255,255)] * (AreaNum+1)
50 |
51 | # Create random color.
52 | for i in range(AreaNum):
53 | r = random.randint(0, 255)
54 | g = random.randint(0, 255)
55 | b = random.randint(0, 255)
56 | color_list[i+1] = (r,g,b)
57 |
58 | # # Delete small closed areas.
59 | # AreaSize = [0] * (AreaNum + 1)
60 | # for i in range(img.shape[0]):
61 | # for j in range(img.shape[1]):
62 | # k = labels[i][j]
63 | # AreaSize[k] = AreaSize[k] + 1
64 | # # print(AreaSize)
65 | # print(labels.shape)
66 | # print(np.where(labels == 24))
67 | # for i in range(AreaNum+1):
68 | # if AreaSize[i] < MinAreaSize:
69 | # new_labels = np.where( labels == i, 0 )
70 | # # labels[ np.where( labels == i ) ] = 0
71 |
72 | # color each area.
73 | for i in range(len(labels)):
74 | for j in range(len(labels[0])):
75 | color = labels[i][j]
76 | for k in range(3):
77 | new_img[i][j][k] = color_list[color][k]
78 | cv2.imshow('closed-area image', new_img)
79 | if (SaveImage):
80 | cv2.imwrite(self.Save_PATH, new_img)
81 | cv2.waitKey(0)
82 |
83 |
84 | # test code
85 | if __name__=="__main__":
86 | op = CloseAreaDetect()
87 | img = cv2.imread(op.IMG_PATH)
88 | op.CloseArea(img, SaveImage = True)
--------------------------------------------------------------------------------
/Edge.py:
--------------------------------------------------------------------------------
1 | # For extracting better edges, we combine several traditional algorithms.
2 | # We provide more flexible paramters.
3 |
4 | import cv2
5 | import numpy
6 | import Basic
7 | import CloseAreaDetect
8 | import CannyOperator
9 |
10 | if __name__ == "__main__":
11 | OpBasic = Basic.Basic(IMG_PATH = "G:/Deecamp/building.jpg",
12 | Save_PATH = "G:/Deecamp/test/gradient.jpg")
13 | OpCanny = CannyOperator.ImageChange(Static_Low_Threshold = 30,
14 | Static_High_Threshold = 80)
15 | OpDetect = CloseAreaDetect.CloseAreaDetect(Save_PATH = "G:/Deecamp/test/closeArea.jpg",
16 | Gray_Threshold = 50)
17 |
18 | img = cv2.imread(OpBasic.IMG_PATH)
19 | # 1. Do 'Gradient'(dilation - erosion).
20 | img = OpBasic.Gradient(img, KernelSize = 3, SaveImage = True)
21 | # img = OpBasic.Closing( img, ErodeIter = 1, DilateIter = 1, KernelSize = 3)
22 | # 2. Do 'Canny'. Press 'Space' to move on.
23 | # img = OpCanny.StaticCanny(img)
24 | img = OpCanny.DynamicCanny(img, img_path = OpBasic.Save_PATH)
25 | # 3. Detect closed areas.
26 | img = OpDetect.CloseArea(img, GrayWhiteChange = True, MinAreaSize = 100, SaveImage = False)
27 |
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DeeCamp Group32: image-based closed edge map generation.
2 | ### Proud to say that we have won the Best Innovation Award and won the 4th place in BeiJing's 35 groups.
3 | ### Contributors: Kuangyi CheHe, Yuxiang Chen, Jingwen Chen, Jingwen Guo, Chenxu Hu, Yang Jiao, Tao Li, Guanting Lou, Zixiao Pan, Heng Sun, Jiaqi Sun, Jiangxue Xu, Rui Xu, Zhangli Zhou, Zexian Li.
4 |
5 | In order to reduce unnecessary repetitive work, we open some code, which mainly consist of traditional methods in computer vision. More deep-learning methods we used can be found in "Related Works".
6 |
7 | 1.['CannyOperator.py'](https://github.com/FuNian788/Deecamp32/blob/master/CannyOperator.py) includes two main operators.
8 | The function 'StaticCanny' can perform a Canny operation on the input image, but you need to set the low / high thresholds manually.
9 | The function 'DynamicCanny' can dynamically modify the thresholds through a trackbar.
10 | The function 'SplitMerge' splits the source image to 3 channels, finds each image's edge through Canny, then bitwise AND and merge 3 new edges, which may get good edges.
11 | By the way, don't forget to change the image's path and static thresholds in the function 'init'. The same below.
12 |
13 | 2.['CloseAreaDetect.py'](https://github.com/FuNian788/Deecamp32/blob/master/CloseAreaDetect.py) can detect all closed areas in the input images and output its number.
14 | You should set a threshold for the grayscale to get better performance, change the threshold until the grayscale image has a sharp edge.
15 | In order to visually see the closed area, we randomly color all areas and then save the image.
16 | We hope that this Op can help you evaluate the quality of the edge extraction algorithm.
17 | We get some perfect results, so if the result you get is a mess, check your code and don't sleep.
18 | Some samples are shown as below(source image, gray image and the result).
19 |
20 |
21 |
22 |
23 | 3.['Basic.py'](https://github.com/FuNian788/Deecamp32/blob/master/Basic.py) includes some basic operations for edge extraction. We hope you can use these methods to get good results. (If one method doesn't work, try another one, or you can average the results of all methods.)
24 | We collected some basic algorithms based on python-opencv, which help to extract the edges.
25 | The functions are introduced as below.
26 | (1) CompressChannel: compress the value of each channel from 256 kinds to 16 kinds, which improves the contrast of the image, but may lead to some obvious edges in the continuous color block.
27 | (2) AdaThreshold: For images with uneven brightness distribution, we can use adaptive methods(mean/Gussian) to find suitable threshold.
28 | (3) Opening: We always erode --> dilate to remove samll background-objects/noises, fill some edges. We set iterations instead of using 'cv2.morphologyEx' here, and please be careful of the size of the kernel, same as below.
29 | (4) Closing: We always dilate --> erode to splice break edges, eliminate holes inside the foreground.
30 | (5) Gradient: Gradient = Dilation - Erosion, which is some kind of edge. Surprisingly, this method works very well in some images.
31 | In order to see the effect of the methods more intuitively, the running results of the algorithm are visualized as follows.
32 | I use 송민국's daily photo as source image because I love him so much.
33 | >