├── 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 | source image 20 | source image 21 | source image 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 | >
source image
34 | The images in Line 1 are 'compress', 'mean' and 'Gussian', the images in Line 2 are 'opening','closing', and 'gradient'. 35 | compress image 36 | mean image 37 | Gussian image 38 | opening image 39 | closing image 40 | gradient image 41 | 42 | 4.['RegionGrow.py'](https://github.com/FuNian788/Deecamp32/blob/master/RegionGrow.py) can easily get closed regions through random seeds, but excellent results mainly depend on the color difference between foreground and background. By the way, the algorithm runs for a long time, maybe several minutes. 43 | In our code, you have 3 ways to generate seeds: randomly, uniformly or let all pixels to be seeds. 44 | We use the difference values of R/G/B channels to determine whether to expand, and we consider each pixel's eight-neighborhood. 45 | As for the overall process, we first generate few seeds and let them grow, then we generate & grow a few more times in order to prevent some regions from being dropped, in the growing process, we discard grown-regions whose areas are still small. For remaining blank areas, the SMALL ones(like noises), we think they are too small to expand to a region so we let big & near regions 'annex' them; while we think the BIG ones may contain complex textures, so we ignore their texture details and treat them as separate new regions. In the end, we select the average color of each region on the original image to paint the new image, we get the final edges at the same time. 46 | All parameters are in the 'main' function, see comments for their meanings. 47 | Many tricks have been applied, hope you can adjust parameters happily... 48 | 49 | #### All the code has detailed comments, if you don't understand, just google it. 50 | 51 | ### Update Log 52 | July,30,2019 / 0.9 / Refactor the code and fix some bug. 53 | Oct,7,2019 / 1.0 / Add the method 'Region-Grow' and optimize codes. 54 | Oct,19,2019 / 2.0 / Organize all used methods. 55 | 56 | ### Related Works 57 | #### We mainly use 'PoolNet' and 'CycleGAN' for DeeCamp's display. 58 | 1. [Hed]https://github.com/s9xie/hed 59 | 2. [Contour-GAN]https://github.com/Feynman1999/ContourGAN-PyTorch 60 | 3. [PoolNet]https://github.com/backseason/PoolNet 61 | 4. [Semantic-Soft-Segmentation]https://github.com/yaksoy/SemanticSoftSegmentation 62 | 5. [COB]https://github.com/kmaninis/COB 63 | 6. [EdgeBox]https://github.com/AlexMa011/edgeBoxes-Cpp-version 64 | 7. [CycleGAN]https://junyanz.github.io/CycleGAN/ 65 | 8. [Pix2Pix]https://github.com/affinelayer/pix2pix-tensorflow 66 | 9. [Cartoon-GAN]https://github.com/taki0112/CartoonGAN-Tensorflow 67 | [Web-Display]https://github.com/Feynman1999/Web-For-Project 68 | 69 | -------------------------------------------------------------------------------- /RegionGrow.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import random 3 | import cv2 4 | 5 | class grow(): 6 | def __init__(self, img, ColorThreshold, RegionAreaThreshold): 7 | # 'Seed' is a list which consists of random seeds, it gives only ONE seed to 'OpenList' once. 8 | self.Seed = [] 9 | # 'OpenList' is a list which consists of a seed from List(Seed) and its near points, 10 | # near points must meet color-threshold conditions. 11 | self.OpenList = [] 12 | # Eight connected. 13 | self.Connect = [(-1,-1), (-1,0), (-1,1), 14 | (0, -1), (0, 1), 15 | (1, -1), (1, 0), (1, 1)] 16 | self.width = img.shape[1] 17 | self.height = img.shape[0] 18 | # self.new_img: save region labels. 19 | self.new_img = np.zeros([self.height, self.width]) 20 | # self.img_mask: save existing areas and blank areas. 21 | self.img_mask = np.zeros([self.height, self.width], dtype=np.uint8) 22 | # self.img_edge: save the final edges. 23 | self.img_edge = np.zeros([self.height, self.width], dtype=np.uint8) 24 | 25 | self.ColorThreshold = ColorThreshold 26 | self.RegionAreaThreshold = RegionAreaThreshold 27 | self.RegionNum = 0 28 | self.Save_Folder = "None" 29 | 30 | 31 | ''' Set the save folder.''' 32 | def Set_Save_Folder(self, folder_path): 33 | self.Save_Folder = folder_path 34 | 35 | 36 | ''' Decide whether the RGB values of two pixels meet the color-threshold condition.''' 37 | def ColorDifferent(self, x, y): 38 | color1 = img[x[0]][x[1]] 39 | color2 = img[y[0]][y[1]] 40 | difference = np.sqrt(np.sum(np.square(color1-color2))) 41 | if difference <= self.ColorThreshold: 42 | return True 43 | else: 44 | return False 45 | 46 | 47 | ''' Generate many seeds randomly / uniformly.''' 48 | def GenerateSeed(self, seedtype, parameter): 49 | if seedtype == "Random": 50 | for i in range(parameter): 51 | x = random.randint(0, self.height-1) 52 | y = random.randint(0, self.width-1) 53 | self.Seed.append( (x, y) ) 54 | if seedtype == "Uniform": 55 | x_unit = int(self.height / parameter) 56 | y_unit = int(self.width / parameter) 57 | for i in range(parameter): 58 | for j in range(parameter): 59 | self.Seed.append( (i * x_unit, j * y_unit) ) 60 | if seedtype == "all": 61 | for i in range(self.height): 62 | for j in range(self.width): 63 | self.Seed.append( (i,j) ) 64 | 65 | 66 | ''' The process of Region-Growing.''' 67 | def RegionGrow(self): 68 | self.RegionNum = 1 69 | print("Let's begin.") 70 | while (len(self.Seed)>0): 71 | if( len(self.Seed) % 5 == 0): 72 | print("There are still {} seeds.".format(len(self.Seed)) ) 73 | # 1. Give a seed to 'OpenList' and decide whether it belongs to an existing region. 74 | OneSeed = self.Seed.pop(-1) 75 | if self.new_img[OneSeed[0]][OneSeed[1]] == 0: 76 | self.new_img[OneSeed[0]][OneSeed[1]] = self.RegionNum 77 | self.OpenList.append(OneSeed) 78 | # print("Now add a new region.") 79 | # 2. 'OpenList' first receive a random seed, then add its near pixels(meet color-threshlod condition) to be new seeds. 80 | # NOTE: each pixel just use once. 81 | while(len(self.OpenList)>0): 82 | SourceSeed = self.OpenList.pop(-1) 83 | x = SourceSeed[0] 84 | y = SourceSeed[1] 85 | if (x < 0 or y < 0 or x >= self.height or y >= self.width): 86 | continue 87 | for i in range(8): 88 | new_x = x + self.Connect[i][0] 89 | new_y = y + self.Connect[i][1] 90 | if (new_x >= 0 and new_y >= 0 and new_x < self.height and new_y < self.width): 91 | if self.new_img[new_x][new_y] == 0: 92 | if self.ColorDifferent(SourceSeed, (new_x, new_y)): 93 | self.new_img[new_x][new_y] = self.RegionNum 94 | self.AddNearPoint(new_x, new_y) 95 | # 3. the region whose area belows threshold will be eliminated. 96 | actual_threshold = int(self.RegionAreaThreshold * self.width * self.height) 97 | if (np.sum(self.new_img == self.RegionNum) < actual_threshold): 98 | self.new_img = np.where(self.new_img == self.RegionNum, 0, self.new_img) 99 | continue 100 | # 4. When the pixels in 'OpenList' run out, use a new random seed to create a new region. 101 | self.RegionNum = self.RegionNum + 1 102 | # print("Now is the {} region.".format(self.RegionNum)) 103 | print("There are {} regions in total.".format(self.RegionNum)) 104 | 105 | 106 | '''Add near pixels to 'OpenList', which shouldn't be traversed.''' 107 | def AddNearPoint(self, x, y): 108 | for i in range(-1,2,1): 109 | for j in range(-1,2,1): 110 | if (x+i >= 0 and y+j >= 0 and x+i < self.height and y+j < self.width): 111 | if self.new_img[x+i][y+j] == 0: 112 | self.OpenList.append( (x, y) ) 113 | 114 | 115 | ''' Randomly paint all regions.''' 116 | def AddRandomColor(self, __RegionNum): 117 | color_img = np.zeros((img.shape[0],img.shape[1],3), np.uint8) 118 | color_list = [(255,255,255)] * (__RegionNum+1) 119 | # Create random color. 120 | for i in range(__RegionNum): 121 | r = random.randint(0, 255) 122 | g = random.randint(0, 255) 123 | b = random.randint(0, 255) 124 | color_list[i+1] = (r,g,b) 125 | # color each region. 126 | for i in range(len(self.new_img)): 127 | for j in range(len(self.new_img[0])): 128 | color = int(self.new_img[i][j]) 129 | for k in range(3): 130 | color_img[i][j][k] = color_list[color][k] 131 | cv2.namedWindow('closed-region image', cv2.WINDOW_NORMAL) 132 | cv2.imshow('closed-region image', color_img) 133 | cv2.waitKey(0) 134 | 135 | 136 | ''' Color each area using the color of the original image.''' 137 | def AddSourceColor(self, __RegionNum): 138 | color_img = np.zeros((img.shape[0],img.shape[1],3), np.uint8) 139 | color_list = [(255,255,255)] * (__RegionNum+1) 140 | # 3 channels mean certain color's R/G/B values. 141 | Certain_Color = np.array([0, 0, 0]) 142 | 143 | '''Find the source color. ''' 144 | # 'i' means how many regions. 145 | for i in range(__RegionNum): 146 | color_tuple = np.where(self.new_img == i+1) 147 | # 'j' means how many pixels are there in certain region. 148 | for j in range(len(color_tuple[0])): 149 | # 'k' means 3 channels. 150 | for k in range(3): 151 | Certain_Color[k] = Certain_Color[k] + img[color_tuple[0][j]][color_tuple[1][j]][k] 152 | length = max(len(color_tuple[0]), 1) 153 | Certain_Color = Certain_Color // length 154 | color_list[i+1] = (Certain_Color[0], Certain_Color[1], Certain_Color[2]) 155 | 156 | '''Color each region. ''' 157 | for i in range(len(self.new_img)): 158 | for j in range(len(self.new_img[0])): 159 | color = int(self.new_img[i][j]) 160 | for k in range(3): 161 | color_img[i][j][k] = color_list[color][k] 162 | cv2.namedWindow('closed-region image', cv2.WINDOW_NORMAL) 163 | cv2.imshow('closed-region image', color_img) 164 | print("Now save a image.") 165 | cv2.imwrite(self.Save_Folder + str(self.RegionNum) + ".jpg", color_img) 166 | cv2.waitKey(0) 167 | cv2.destroyAllWindows() 168 | 169 | 170 | ''' Add some remain pixels to be new seeds because one traversal can't detect all regions.''' 171 | def AddNewSeed(self): 172 | RemainPixel = np.where(self.new_img == 0) 173 | for i in range(1000): 174 | x = random.randint(0, len(RemainPixel[0])-1) 175 | self.Seed.append((RemainPixel[0][x], RemainPixel[1][x])) 176 | 177 | 178 | ''' 179 | After spreading seeds several times, we think the remaining blank regions belong to 2 types. 180 | (1) Small blank regions: 181 | They are too to small to expand to a region.(because of the region-size-threshold.) 182 | So we just paint them their near region's color. 183 | (2) Big blank regions: 184 | They are probably connected regions with complex textures, can't expand well because 185 | textures divide the image to many pieces. 186 | So we just see the blank region as a new region, ignoring its textures. 187 | ''' 188 | def NearInfection(self): 189 | self.img_mask = np.array(np.where(self.new_img == 0, 1, 0), dtype = np.uint8) 190 | _, labels = cv2.connectedComponents(self.img_mask, connectivity=4) 191 | # There are 'AreaNum' blank regions at all. 192 | AreaNum = np.max(labels) 193 | actual_threshold = int(self.RegionAreaThreshold * self.height * self.width) 194 | for k in range(AreaNum): 195 | if ( (k+1) % 100 == 0 ): 196 | print("Now we paint NO.{}/{} region. Please don't cry.".format(k+1, AreaNum) ) 197 | if np.sum(labels == k+1) < actual_threshold: 198 | near_pixel = (np.where(labels == k+1)[0][0], np.where(labels == k+1)[1][0]) 199 | near_pixel_region = self.new_img[near_pixel[0]][near_pixel[1]-1] 200 | self.new_img = self.new_img + (np.where(labels == k+1, near_pixel_region, 0)) 201 | else: 202 | self.RegionNum = self.RegionNum + 1 203 | self.new_img = self.new_img + (np.where(labels == k+1, self.RegionNum, 0)) 204 | 205 | 206 | '''Draw edges by traversing 8 neighborhoods.''' 207 | def DrawEdge(self): 208 | for i in range(1, self.height-1, 1): 209 | for j in range(1, self.width-1, 1): 210 | for m in range(-1, 2, 1): 211 | for n in range(-1, 2, 1): 212 | if self.new_img[i+m][j+n] != self.new_img[i][j]: 213 | self.img_edge[i][j] = 255 214 | continue 215 | continue 216 | cv2.imshow("final edge", self.img_edge) 217 | cv2.waitKey(0) 218 | cv2.destroyAllWindows() 219 | cv2.imwrite(self.Save_Folder + "final.jpg", self.img_edge) 220 | 221 | 222 | 223 | 224 | if __name__ == "__main__": 225 | img = cv2.imread("G:/Deecamp/1.jpg") 226 | print("The image shape is {}.".format(img.shape)) 227 | 228 | ''' 229 | First parameter: the source image. 230 | Second parameter: color threshold. 231 | Third parameter: the region whose area belows threshold will be eliminated. 232 | actual_threshold = RegionAreaThreshold * img_height * img_width) 233 | ''' 234 | Op = grow(img, 9, 0.001) 235 | Op.Set_Save_Folder("G:/Deecamp/cat/") 236 | 237 | ''' 238 | There are 3 type of seed-generate methods. "random", "unform" and "all". 239 | if seedtype == "random", parameter means "the number of seeds." 240 | if seedtype == "uniform", parameter means "the image has parameter*parameter blocks/seeds." 241 | if seedtype == "all", parameter means "all the pixels are seeds." 242 | ''' 243 | Op.GenerateSeed("Uniform", 40) 244 | # Op.GenerateSeed("random", 1000) 245 | # Op.GenerateSeed("all", "None") 246 | 247 | Op.RegionGrow() 248 | Op.AddSourceColor(Op.RegionNum) 249 | # The image has 'i' chances to find all regions. 250 | for i in range(2): 251 | Op.AddNewSeed() 252 | Op.RegionGrow() 253 | Op.AddSourceColor(Op.RegionNum-1) 254 | 255 | print("Now we deal with small empty regions.") 256 | Op.NearInfection() 257 | Op.AddSourceColor(Op.RegionNum) 258 | print("there are {} pixels still no region.".format(np.sum(Op.new_img == 0))) 259 | Op.DrawEdge() 260 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /img/Basic/Gussian.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuNian788/DeeCamp32/22165f85fae9e974d8cc9bcfe8d71264272be913/img/Basic/Gussian.jpg -------------------------------------------------------------------------------- /img/Basic/closing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuNian788/DeeCamp32/22165f85fae9e974d8cc9bcfe8d71264272be913/img/Basic/closing.jpg -------------------------------------------------------------------------------- /img/Basic/compress.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuNian788/DeeCamp32/22165f85fae9e974d8cc9bcfe8d71264272be913/img/Basic/compress.jpg -------------------------------------------------------------------------------- /img/Basic/gradient.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuNian788/DeeCamp32/22165f85fae9e974d8cc9bcfe8d71264272be913/img/Basic/gradient.jpg -------------------------------------------------------------------------------- /img/Basic/mean.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuNian788/DeeCamp32/22165f85fae9e974d8cc9bcfe8d71264272be913/img/Basic/mean.jpg -------------------------------------------------------------------------------- /img/Basic/opening.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuNian788/DeeCamp32/22165f85fae9e974d8cc9bcfe8d71264272be913/img/Basic/opening.jpg -------------------------------------------------------------------------------- /img/Basic/source.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuNian788/DeeCamp32/22165f85fae9e974d8cc9bcfe8d71264272be913/img/Basic/source.jpg -------------------------------------------------------------------------------- /img/CloseAreaDetect/gray.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuNian788/DeeCamp32/22165f85fae9e974d8cc9bcfe8d71264272be913/img/CloseAreaDetect/gray.jpg -------------------------------------------------------------------------------- /img/CloseAreaDetect/result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuNian788/DeeCamp32/22165f85fae9e974d8cc9bcfe8d71264272be913/img/CloseAreaDetect/result.jpg -------------------------------------------------------------------------------- /img/CloseAreaDetect/source.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FuNian788/DeeCamp32/22165f85fae9e974d8cc9bcfe8d71264272be913/img/CloseAreaDetect/source.jpg --------------------------------------------------------------------------------