From 114dd91c405c37351076252b38dab6d8f3c94195 Mon Sep 17 00:00:00 2001 From: Konst Kolesnichenko Date: Wed, 20 Aug 2014 13:16:20 +0300 Subject: [PATCH 1/9] * primitive line detector (debug version) --- unshred/features/lines.py | 41 +++++++++++++++++++++++++++++++++++++++ unshred/split.py | 5 +++-- 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 unshred/features/lines.py diff --git a/unshred/features/lines.py b/unshred/features/lines.py new file mode 100644 index 0000000..faa17a5 --- /dev/null +++ b/unshred/features/lines.py @@ -0,0 +1,41 @@ +import cv2 +import numpy +from unshred.features import AbstractShredFeature + + +class LinesFeatures(AbstractShredFeature): + TAG_HAS_LINES_FEATURE = "has lines" + TAG_PARALLEL_FEATURE = "parallel" + TAG_PERPENDECULAR_FEATURE = "perpendecular" + + def get_info(self, shred, contour, name): + + tags = [] + params = {} + + gray = cv2.cvtColor(shred, cv2.COLOR_BGR2GRAY) + edges = cv2.Canny(gray, 150, 200, apertureSize = 3) + # removing contours from the edges (by drawing them black) + cv2.drawContours(edges, contour, -1, (0, 0, 0), 12, 2) + cv2.imwrite('../debug/edges_%s.png'%name, edges) + + lines = cv2.HoughLines(edges, 3, 1* numpy.pi/180, 60) + + if not lines is None: + #debug images + for rho,theta in lines[0]: + a = numpy.cos(theta) + b = numpy.sin(theta) + x0 = a*rho + y0 = b*rho + x1 = int(x0 + 1000*(-b)) # Here i have used int() instead of rounding the decimal value, so 3.8 --> 3 + y1 = int(y0 + 1000*(a)) # But if you want to round the number, then use np.around() function, then 3.8 --> 4.0 + x2 = int(x0 - 1000*(-b)) # But we need integers, so use int() function after that, ie int(np.around(x)) + y2 = int(y0 - 1000*(a)) + cv2.line(shred,(x1,y1),(x2,y2),(255,0,0),2) + cv2.imwrite('../debug/houghlines_%s.png'%name, shred) + + params['Lines Count'] = len(lines) + tags.append(self.TAG_HAS_LINES_FEATURE) + + return params, tags diff --git a/unshred/split.py b/unshred/split.py index 9f6347e..ce40fee 100644 --- a/unshred/split.py +++ b/unshred/split.py @@ -9,6 +9,7 @@ import exifread from jinja2 import FileSystemLoader, Environment from features import GeometryFeatures, ColourFeatures +from unshred.features.lines import LinesFeatures def convert_poly_to_string(poly): @@ -226,7 +227,7 @@ def open_image_and_separate_bg(self, img): img = cv2.bitwise_and(img, img, mask=mask) # Write original image with no background for debug purposes - cv2.imwrite("debug/mask.tif", mask) + cv2.imwrite("../debug/mask.tif", mask) return img, mask @@ -401,7 +402,7 @@ def save_thumb(self, width=200): print("Processing file %s" % fname) sheet = Sheet(fname, sheet_name, - [GeometryFeatures, ColourFeatures], out_dir, out_format) + [GeometryFeatures, ColourFeatures, LinesFeatures], out_dir, out_format) sheet.export_results_as_html() sheets.append({ From be53905a8053bb8ae4c9e4d605186980cf4cf5be Mon Sep 17 00:00:00 2001 From: Konst Kolesnichenko Date: Wed, 20 Aug 2014 17:14:05 +0300 Subject: [PATCH 2/9] * more reliable line detection (but still too much false positives) * added sorting for fast clusterization --- unshred/features/lines.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/unshred/features/lines.py b/unshred/features/lines.py index faa17a5..dd10b1d 100644 --- a/unshred/features/lines.py +++ b/unshred/features/lines.py @@ -1,3 +1,4 @@ +from PIL import Image import cv2 import numpy from unshred.features import AbstractShredFeature @@ -14,16 +15,23 @@ def get_info(self, shred, contour, name): params = {} gray = cv2.cvtColor(shred, cv2.COLOR_BGR2GRAY) - edges = cv2.Canny(gray, 150, 200, apertureSize = 3) + # gray = cv2.blur(gray, (5,5)) + # thresh = cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) + # thimg = Image.fromarray(thresh[1]) + # thimg.save('../debug/thresh_%s.png'%name) + edges = cv2.Canny(gray, 100, 200, apertureSize = 3) # removing contours from the edges (by drawing them black) - cv2.drawContours(edges, contour, -1, (0, 0, 0), 12, 2) + cv2.drawContours(edges, contour, -1, (0, 0, 0), 18) cv2.imwrite('../debug/edges_%s.png'%name, edges) - lines = cv2.HoughLines(edges, 3, 1* numpy.pi/180, 60) + lines = cv2.HoughLines(edges, 1, 1* numpy.pi/180, 40) if not lines is None: + ar = lines[0] + # sorting by theta (for grouping by angle) + ar = ar[ar[:,1].argsort()] #debug images - for rho,theta in lines[0]: + for rho,theta in ar: a = numpy.cos(theta) b = numpy.sin(theta) x0 = a*rho @@ -32,8 +40,8 @@ def get_info(self, shred, contour, name): y1 = int(y0 + 1000*(a)) # But if you want to round the number, then use np.around() function, then 3.8 --> 4.0 x2 = int(x0 - 1000*(-b)) # But we need integers, so use int() function after that, ie int(np.around(x)) y2 = int(y0 - 1000*(a)) - cv2.line(shred,(x1,y1),(x2,y2),(255,0,0),2) - cv2.imwrite('../debug/houghlines_%s.png'%name, shred) + cv2.line(gray,(x1,y1),(x2,y2),(255,0,0),2) + cv2.imwrite('../debug/houghlines_%s.png'%name, gray) params['Lines Count'] = len(lines) tags.append(self.TAG_HAS_LINES_FEATURE) From 2e8f59b5db2d95c9b75519a24d5681383b7efa6f Mon Sep 17 00:00:00 2001 From: Konst Kolesnichenko Date: Wed, 20 Aug 2014 18:44:37 +0300 Subject: [PATCH 3/9] * better line detection --- unshred/features/lines.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/unshred/features/lines.py b/unshred/features/lines.py index dd10b1d..fc594c1 100644 --- a/unshred/features/lines.py +++ b/unshred/features/lines.py @@ -15,16 +15,14 @@ def get_info(self, shred, contour, name): params = {} gray = cv2.cvtColor(shred, cv2.COLOR_BGR2GRAY) - # gray = cv2.blur(gray, (5,5)) - # thresh = cv2.threshold(gray, 220, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) - # thimg = Image.fromarray(thresh[1]) - # thimg.save('../debug/thresh_%s.png'%name) - edges = cv2.Canny(gray, 100, 200, apertureSize = 3) + edges = cv2.Canny(gray, 50, 200, apertureSize=3) # removing contours from the edges (by drawing them black) - cv2.drawContours(edges, contour, -1, (0, 0, 0), 18) + cv2.drawContours(edges, contour, -1, (0, 0, 0), 16) + edges = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, (3, 3)) cv2.imwrite('../debug/edges_%s.png'%name, edges) + # cv2.imwrite('../debug/cont_%s.png'%name, contourImg) - lines = cv2.HoughLines(edges, 1, 1* numpy.pi/180, 40) + lines = cv2.HoughLines(edges, 1, 1* numpy.pi/180, 38) if not lines is None: ar = lines[0] From 2e366ceb61c1870d338665405254326ae99323e2 Mon Sep 17 00:00:00 2001 From: Konst Kolesnichenko Date: Wed, 20 Aug 2014 19:29:05 +0300 Subject: [PATCH 4/9] changed detetection to probable model much better lines identification --- unshred/features/lines.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/unshred/features/lines.py b/unshred/features/lines.py index fc594c1..c3f110d 100644 --- a/unshred/features/lines.py +++ b/unshred/features/lines.py @@ -15,29 +15,19 @@ def get_info(self, shred, contour, name): params = {} gray = cv2.cvtColor(shred, cv2.COLOR_BGR2GRAY) - edges = cv2.Canny(gray, 50, 200, apertureSize=3) + gray_blur = cv2.GaussianBlur(gray, (15, 15), 0) + edges = cv2.adaptiveThreshold(gray_blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, + cv2.THRESH_BINARY_INV, 5, 1) # removing contours from the edges (by drawing them black) - cv2.drawContours(edges, contour, -1, (0, 0, 0), 16) - edges = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, (3, 3)) - cv2.imwrite('../debug/edges_%s.png'%name, edges) - # cv2.imwrite('../debug/cont_%s.png'%name, contourImg) + cv2.drawContours(edges, contour, -1, (0, 0, 0), 24) + edges = cv2.morphologyEx(edges, cv2.MORPH_ERODE, (6, 6), iterations=2) - lines = cv2.HoughLines(edges, 1, 1* numpy.pi/180, 38) + cv2.imwrite('../debug/edges_%s.png'%name, edges) + lines = cv2.HoughLinesP(edges, 1, numpy.pi/180, 20, minLineLength = 30, maxLineGap = 10) if not lines is None: - ar = lines[0] - # sorting by theta (for grouping by angle) - ar = ar[ar[:,1].argsort()] #debug images - for rho,theta in ar: - a = numpy.cos(theta) - b = numpy.sin(theta) - x0 = a*rho - y0 = b*rho - x1 = int(x0 + 1000*(-b)) # Here i have used int() instead of rounding the decimal value, so 3.8 --> 3 - y1 = int(y0 + 1000*(a)) # But if you want to round the number, then use np.around() function, then 3.8 --> 4.0 - x2 = int(x0 - 1000*(-b)) # But we need integers, so use int() function after that, ie int(np.around(x)) - y2 = int(y0 - 1000*(a)) + for x1,y1,x2,y2 in lines[0]: cv2.line(gray,(x1,y1),(x2,y2),(255,0,0),2) cv2.imwrite('../debug/houghlines_%s.png'%name, gray) From dd508b842d7f4c254c0b4875c077fe4c6ed56489 Mon Sep 17 00:00:00 2001 From: Konst Kolesnichenko Date: Wed, 20 Aug 2014 19:36:35 +0300 Subject: [PATCH 5/9] * commented out debug images output * fixed line count --- unshred/features/lines.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/unshred/features/lines.py b/unshred/features/lines.py index c3f110d..0235974 100644 --- a/unshred/features/lines.py +++ b/unshred/features/lines.py @@ -22,16 +22,18 @@ def get_info(self, shred, contour, name): cv2.drawContours(edges, contour, -1, (0, 0, 0), 24) edges = cv2.morphologyEx(edges, cv2.MORPH_ERODE, (6, 6), iterations=2) - cv2.imwrite('../debug/edges_%s.png'%name, edges) + # const: uncomment for debug + # cv2.imwrite('../debug/edges_%s.png'%name, edges) lines = cv2.HoughLinesP(edges, 1, numpy.pi/180, 20, minLineLength = 30, maxLineGap = 10) if not lines is None: + # const: uncomment for debug #debug images - for x1,y1,x2,y2 in lines[0]: - cv2.line(gray,(x1,y1),(x2,y2),(255,0,0),2) - cv2.imwrite('../debug/houghlines_%s.png'%name, gray) + # for x1,y1,x2,y2 in lines[0]: + # cv2.line(gray,(x1,y1),(x2,y2),(255,0,0),2) + # cv2.imwrite('../debug/houghlines_%s.png'%name, gray) - params['Lines Count'] = len(lines) + params['Lines Count'] = len(lines[0]) tags.append(self.TAG_HAS_LINES_FEATURE) return params, tags From 36bd6336be04c8f75ff6e60cd882296df6fc6ef4 Mon Sep 17 00:00:00 2001 From: Konst Kolesnichenko Date: Sun, 31 Aug 2014 15:40:51 +0300 Subject: [PATCH 6/9] * much better contour removal based on a mask erosion --- unshred/features/lines.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/unshred/features/lines.py b/unshred/features/lines.py index 0235974..13e5639 100644 --- a/unshred/features/lines.py +++ b/unshred/features/lines.py @@ -14,24 +14,30 @@ def get_info(self, shred, contour, name): tags = [] params = {} + _, _, _, mask = cv2.split(shred) + + # expanding mask for future removal of the contour + mask = cv2.morphologyEx(mask, cv2.MORPH_ERODE, (5, 5), iterations=200) + gray = cv2.cvtColor(shred, cv2.COLOR_BGR2GRAY) gray_blur = cv2.GaussianBlur(gray, (15, 15), 0) edges = cv2.adaptiveThreshold(gray_blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 5, 1) # removing contours from the edges (by drawing them black) - cv2.drawContours(edges, contour, -1, (0, 0, 0), 24) + edges = edges & mask edges = cv2.morphologyEx(edges, cv2.MORPH_ERODE, (6, 6), iterations=2) # const: uncomment for debug - # cv2.imwrite('../debug/edges_%s.png'%name, edges) + cv2.imwrite('../debug/edges_%s.png'%name, edges) + cv2.imwrite('../debug/mask_%s.png'%name, mask) lines = cv2.HoughLinesP(edges, 1, numpy.pi/180, 20, minLineLength = 30, maxLineGap = 10) if not lines is None: # const: uncomment for debug #debug images - # for x1,y1,x2,y2 in lines[0]: - # cv2.line(gray,(x1,y1),(x2,y2),(255,0,0),2) - # cv2.imwrite('../debug/houghlines_%s.png'%name, gray) + for x1,y1,x2,y2 in lines[0]: + cv2.line(gray,(x1,y1),(x2,y2),(255,0,0),2) + cv2.imwrite('../debug/houghlines_%s.png'%name, gray) params['Lines Count'] = len(lines[0]) tags.append(self.TAG_HAS_LINES_FEATURE) From 079ad51492f3592debd28542ab836c847bb8ab4f Mon Sep 17 00:00:00 2001 From: Konst Kolesnichenko Date: Sun, 31 Aug 2014 23:06:34 +0300 Subject: [PATCH 7/9] * reduced mask erosion due to weird image distortion * better noise reduction (i hope) --- unshred/features/lines.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/unshred/features/lines.py b/unshred/features/lines.py index 13e5639..e363c60 100644 --- a/unshred/features/lines.py +++ b/unshred/features/lines.py @@ -1,4 +1,3 @@ -from PIL import Image import cv2 import numpy from unshred.features import AbstractShredFeature @@ -15,29 +14,30 @@ def get_info(self, shred, contour, name): params = {} _, _, _, mask = cv2.split(shred) - - # expanding mask for future removal of the contour - mask = cv2.morphologyEx(mask, cv2.MORPH_ERODE, (5, 5), iterations=200) - + # + # # expanding mask for future removal of a border + mask = cv2.morphologyEx(mask, cv2.MORPH_ERODE, (3, 3), iterations=2) + # + # # thresholding our shred gray = cv2.cvtColor(shred, cv2.COLOR_BGR2GRAY) - gray_blur = cv2.GaussianBlur(gray, (15, 15), 0) + gray_blur = cv2.GaussianBlur(gray, (9, 9), 0) edges = cv2.adaptiveThreshold(gray_blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, - cv2.THRESH_BINARY_INV, 5, 1) - # removing contours from the edges (by drawing them black) + cv2.THRESH_BINARY_INV, 5, 1) + # attemting to remove borders edges = edges & mask + # reducing noise edges = cv2.morphologyEx(edges, cv2.MORPH_ERODE, (6, 6), iterations=2) - + # removing small white noise + edges = cv2.medianBlur(edges, 3) # const: uncomment for debug - cv2.imwrite('../debug/edges_%s.png'%name, edges) - cv2.imwrite('../debug/mask_%s.png'%name, mask) + cv2.imwrite('../debug/edges_%s.png' % name, edges) - lines = cv2.HoughLinesP(edges, 1, numpy.pi/180, 20, minLineLength = 30, maxLineGap = 10) + lines = cv2.HoughLinesP(edges, 1, numpy.pi / 180, 20, minLineLength=50, maxLineGap=30) if not lines is None: # const: uncomment for debug - #debug images - for x1,y1,x2,y2 in lines[0]: - cv2.line(gray,(x1,y1),(x2,y2),(255,0,0),2) - cv2.imwrite('../debug/houghlines_%s.png'%name, gray) + for x1, y1, x2, y2 in lines[0]: + cv2.line(shred, (x1, y1), (x2, y2), (255, 255, 0, 0), 2) + cv2.imwrite('../debug/houghlines_%s.png' % name, shred) params['Lines Count'] = len(lines[0]) tags.append(self.TAG_HAS_LINES_FEATURE) From 5db3808c2fad2670103e4c53f38856ee7d6717e5 Mon Sep 17 00:00:00 2001 From: Konst Kolesnichenko Date: Mon, 1 Sep 2014 00:00:08 +0300 Subject: [PATCH 8/9] * better border remove * shred-dependent line length --- unshred/features/lines.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/unshred/features/lines.py b/unshred/features/lines.py index e363c60..55dd727 100644 --- a/unshred/features/lines.py +++ b/unshred/features/lines.py @@ -16,7 +16,9 @@ def get_info(self, shred, contour, name): _, _, _, mask = cv2.split(shred) # # # expanding mask for future removal of a border - mask = cv2.morphologyEx(mask, cv2.MORPH_ERODE, (3, 3), iterations=2) + kernel = numpy.ones((5,5),numpy.uint8) + mask = cv2.morphologyEx(mask, cv2.MORPH_ERODE, kernel, iterations=2) + _, mask = cv2.threshold(mask, 240, 0, cv2.THRESH_TOZERO) # # # thresholding our shred gray = cv2.cvtColor(shred, cv2.COLOR_BGR2GRAY) @@ -31,8 +33,12 @@ def get_info(self, shred, contour, name): edges = cv2.medianBlur(edges, 3) # const: uncomment for debug cv2.imwrite('../debug/edges_%s.png' % name, edges) + cv2.imwrite('../debug/mask_%s.png' % name, mask) - lines = cv2.HoughLinesP(edges, 1, numpy.pi / 180, 20, minLineLength=50, maxLineGap=30) + _, _, r_w, r_h = cv2.boundingRect(contour) + + # Line len should be at least 80% of shred's width, gap - 20% + lines = cv2.HoughLinesP(edges, 1, numpy.pi / 180, 30, minLineLength=r_w*0.7, maxLineGap=r_w*0.2) if not lines is None: # const: uncomment for debug for x1, y1, x2, y2 in lines[0]: From 692dd2886e7bbffd4ebaaceffa7547801503402f Mon Sep 17 00:00:00 2001 From: Konst Kolesnichenko Date: Mon, 1 Sep 2014 00:00:31 +0300 Subject: [PATCH 9/9] * minor startup parameters fix --- unshred/split.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unshred/split.py b/unshred/split.py index ce40fee..1d51730 100644 --- a/unshred/split.py +++ b/unshred/split.py @@ -385,8 +385,8 @@ def save_thumb(self, width=200): if __name__ == '__main__': - fnames = "../src/puzzle_small.tif" if len(sys.argv) == 1 else sys.argv[1] - out_format = "png" if len(sys.argv) < 2 else sys.argv[2] + fnames = "../src/puzzle_small.tif" if len(sys.argv) <= 1 else sys.argv[1] + out_format = "png" if len(sys.argv) <= 2 else sys.argv[2] out_dir = "../out" static_dir = os.path.join(out_dir, "static")