├── README.md ├── imgCorrection.py ├── imgCut.py ├── removeStripesVertical.py ├── rotateandsift.py └── utils.py /README.md: -------------------------------------------------------------------------------- 1 | # 图像缺陷检测批处理程序 2 | 包括轮廓自动识别筛选,区域分割,以及传统的缺陷检测算法 3 | 4 | ### 运行imgCut.py提取ROI,如果图像中有n个待检测区域,可以设置max_cnt = n,imgCut.py将自动摆正图像。 5 | 6 | ### 可选择执行imgCorrection.py将ROI设置为具体摆放姿态(e.g. 相同方向)。 7 | 8 | ### 执行detection.py以检测缺陷,可以选择use_label = True以去除ROI上的特定的pattern,该文件会自动去除竖直方向的栅线,用户可按需求修改。 9 | 10 | ### fileIO.py提供批处理调用功能。 11 | 12 | ### utils.py文件中提供了一些常用的图像处理函数。 13 | -------------------------------------------------------------------------------- /imgCorrection.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | from scipy import ndimage 4 | import math 5 | import utils 6 | import os 7 | from utils import * 8 | 9 | 10 | def findCorner(img): 11 | blurred = cv2.GaussianBlur(img, (5, 5), 0) 12 | dilate = cv2.dilate(blurred, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))) 13 | edged = cv2.Canny(dilate, 30, 120, 3) # 边缘检测 14 | 15 | cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 轮廓检测 16 | cnts = cnts[0] 17 | docCnt = None 18 | 19 | if len(cnts) > 0: 20 | cnts = sorted(cnts, key=cv2.contourArea, reverse=True) # 根据轮廓面积从大到小排序 21 | for c in cnts: 22 | peri = cv2.arcLength(c, True) # 计算轮廓周长 23 | approx = cv2.approxPolyDP(c, 0.02 * peri, True) # 轮廓多边形拟合 24 | # 轮廓为4个点 25 | if len(approx) == 4: 26 | docCnt = approx 27 | break 28 | 29 | for peak in docCnt: 30 | peak = peak[0] 31 | cv2.circle(img, tuple(peak), 10, (255, 0, 0)) 32 | 33 | def correction(img,peaks,extend=False,l=10): 34 | ''' 35 | :param img: 36 | :param peaks: 角点 37 | :param extend: 是否向外延伸 38 | :param l: 延伸的距离 39 | :return: 40 | ''' 41 | # src: 四个顶点 42 | #l = 10# 向外扩张的长度 43 | #utils.showImg(img) 44 | w,h,_ = img.shape 45 | #print(w,h) 46 | peaks = np.float32(peaks) 47 | #print(src) 48 | #src = np.float32([[207, 151], [517, 285], [17, 601], [343, 731]]) 49 | #dst = np.float32([[0, 0], [337, 0], [0, 488], [337, 488]])#原图大小的四个顶点 50 | ###找到对应点 51 | tmp = [[0, 0], [0, w], [h, 0], [h, w]] 52 | dst = [] 53 | for i in range(len(peaks)): 54 | pi = peaks[i] 55 | min_dis = float('inf') 56 | match_p = pi 57 | for pj in tmp: 58 | if distance(pi,pj)=0: peaks[i][0] = peaks[i][0] - l 64 | if pi[1]-l>=0: peaks[i][1] = peaks[i][1] - l 65 | if match_p==[0,w]: 66 | if pi[0]-l>=0: peaks[i][0] = peaks[i][0] - l 67 | if pi[1]+l=0: peaks[i][1] = peaks[i][1] - l 71 | if match_p==[h,w]: 72 | if pi[0]+l dis: 117 | min_dis = dis 118 | p = peak 119 | new_peaks.append(p) 120 | new_peaks = sorted(new_peaks, key=lambda x: (x[0], x[1])) 121 | return new_peaks 122 | 123 | def correctionCall(img_path, same_ori, method = "minRect"): 124 | ''' 125 | :param folder_path: 批处理文件夹 126 | :param same_ori: 处理的图片是否需要保证方向一致 127 | :param method:如果图像存在明显扭曲形变请选择 method = “PolyDP” 128 | :return: 129 | ''' 130 | ############### 131 | # 受cv2.threshold影响较大 132 | ############### 133 | 134 | img = utils.readImg(img_path) 135 | w, h = img.shape 136 | img2 = utils.readImg(img_path, False) 137 | img = cv2.convertScaleAbs(img, alpha=3, beta=0) # alpha=5 138 | #utils.showImg(img) 139 | # _, dst = cv2.threshold(img, 65, 255, cv2.THRESH_BINARY)#155 140 | dst = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 301, 10) 141 | #showImg(dst) 142 | #dst = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 201, 10) 143 | # 闭运算,把断线补上 144 | dst = cv2.erode(dst, utils.Config.kernel1, iterations=1) 145 | dst = cv2.dilate(dst, utils.Config.kernel2, iterations=1) 146 | #utils.showImg(dst) 147 | contours, hierarchy = cv2.findContours(dst, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 轮廓检测 148 | 149 | docCnt = None 150 | # print(len(contours)) 151 | if len(contours) > 0: 152 | contours = sorted(contours, key=cv2.contourArea, reverse=True) # 根据轮廓面积从大到小排序 153 | 154 | # 最大轮廓 155 | maxContour = contours[0] 156 | # cv2.drawContours(img2, contours, 0, (255, 0, 0), 1) 157 | # showImg(img2) 158 | if method == "minRect": 159 | rect = cv2.minAreaRect(maxContour) 160 | cx, cy = rect[0] 161 | box = cv2.boxPoints(rect) 162 | peaks = np.int0(box) 163 | else: 164 | ##多边形拟合的方法 165 | peri = cv2.arcLength(maxContour, True) # 计算轮廓周长 166 | approx = cv2.approxPolyDP(maxContour, 0.06 * peri, True) # 轮廓多边形拟合 167 | docCnt = approx 168 | # print(len(docCnt)) 169 | # cv2.drawContours(img, maxContour, -1, (0, 0, 255), 5) 170 | #utils.showImg(img) 171 | # 找到四个顶点 172 | peaks = [] 173 | for peak in docCnt: 174 | peak = peak[0] 175 | peaks.append(peak) 176 | print(peak) 177 | #cv2.circle(img2, tuple(peak), 10, (255, 0, 0)) 178 | showImg(img2) 179 | ##确保是四个顶点,通过调节min_dis 180 | corners = [[0, 0], [0, w], [h, 0], [h, w]] 181 | peaks = peakMod(peaks, corners) # 四个顶点 182 | 183 | c_img = correction(img2, peaks, True) 184 | #utils.showImg(c_img) 185 | c_img = rotate(c_img, same_ori) 186 | return c_img 187 | 188 | def rotate(img, same_ori = False): 189 | ''' 190 | 191 | :param img: 192 | :param same_ori: 保证图像方向统一 193 | :return: 194 | ''' 195 | w,h,_ = img.shape 196 | img2 = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) 197 | if same_ori: 198 | bin_img = cv2.adaptiveThreshold(img2, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 501, 5) 199 | stripes = [bin_img[:,40:70],bin_img[40:70,:],bin_img[:,max(h-70,0):max(h-40,0)],bin_img[max(w-70,0):max(w-40,0),:]] 200 | label = 0 201 | for i in range(len(stripes)): 202 | if(check(stripes[i])): 203 | label = i 204 | print("label: ",label) 205 | if label == 0: 206 | #img = np.rot90(img, 3)#270 207 | img = cv2.flip(cv2.transpose(img), 0) 208 | if label == 1: 209 | #img = np.rot90(img, 2)#180 210 | img = cv2.flip(img, -1) 211 | if label == 2: 212 | #img = np.rot90(img)#90 213 | img = cv2.flip(cv2.transpose(img), 1) 214 | else: 215 | if w>h: 216 | img = cv2.flip(cv2.transpose(img), 0) 217 | return img 218 | 219 | def check(img): 220 | img = cv2.erode(img, utils.Config.kernel1, iterations=5) 221 | img = cv2.dilate(img, utils.Config.kernel2, iterations=5) 222 | #utils.showImg(img) 223 | contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) # 寻找轮廓点 224 | cnt = 0 225 | for obj in contours: 226 | area = cv2.contourArea(obj) # 计算轮廓内区域的面积 227 | #cv2.drawContours(imgContour, obj, -1, (255, 0, 0), 4) # 绘制轮廓线 228 | perimeter = cv2.arcLength(obj, True) # 计算轮廓周长 229 | approx = cv2.approxPolyDP(obj, 0.02 * perimeter, True) # 获取轮廓角点坐标 230 | CornerNum = len(approx) # 轮廓角点的数量 231 | #print(CornerNum,end=" ") 232 | x, y, w, h = cv2.boundingRect(approx) # 获取坐标值和宽度、高度 233 | 234 | # 轮廓对象分类 235 | if CornerNum == 3: 236 | objType = "triangle" 237 | elif CornerNum == 4: 238 | cnt = cnt+1 239 | objType = "Rectangle" 240 | elif CornerNum > 4: 241 | objType = "Circle" 242 | else: 243 | objType = "N" 244 | print() 245 | if cnt>=2: return True 246 | else: return False 247 | 248 | 249 | if __name__ == '__main__': 250 | folder_path = r"path to your image" 251 | #folder_path = r"D:\testImg" 252 | img = correctionCall(folder_path, same_ori=False,method='DP') 253 | showImg(img) 254 | -------------------------------------------------------------------------------- /imgCut.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | import os 5 | from utils import * 6 | import math 7 | from PIL import Image 8 | from rotateandsift import * 9 | 10 | def eaualHist(img): 11 | #gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) #opencv的直方图均衡化要基于单通道灰度图像 12 | # cv.namedWindow('input_image', cv.WINDOW_NORMAL) 13 | # cv.imshow('input_image', gray) 14 | dst = cv2.equalizeHist(img) #自动调整图像对比度,把图像变得更清晰 15 | return dst 16 | 17 | def cutImage(img, contours, extend=False, l=100):#l=100 18 | W,H,_=img.shape 19 | imgs = [] 20 | if len(contours)==0: 21 | print("no valid contours") 22 | for i in range(0, len(contours)): 23 | x, y, w, h = cv2.boundingRect(contours[i]) 24 | #cv2.rectangle(img, (x, y), (x + w, y + h), (153, 153, 0), 5) 25 | up = y 26 | down = y + h 27 | left = x 28 | right = x + w 29 | if extend: 30 | if y-l>=0: up = y-l 31 | if y +h + l < W: down = y +h + l 32 | if x-l>=0:left = x-l 33 | if x+w+lminArea and area 500: 52 | lines = cv2.HoughLinesP(img_canny, 1, np.pi / 180, 1, minLineLength=L, maxLineGap=LG) 53 | if lines is None: 54 | L = L - 50 55 | else: 56 | break 57 | if L < 510: 58 | LG = LG + 20 59 | else: 60 | break 61 | # print('i=') 62 | # print(i,L,LG) 63 | 64 | linenum = 0 65 | try: 66 | for line in lines: 67 | #print(type(line)) 68 | x1, y1, x2, y2 = line[0] 69 | if x1==x2: 70 | theta = 90 71 | # print(line[0]) 72 | # print((y2-y1)/(x2-x1)) 73 | else: 74 | #t = float(y2-y1)/(x2-x1) 75 | t = float (x2 - x1)/(y2 - y1) 76 | theta = math.degrees(math.atan(t)) 77 | #print(theta) 78 | if theta < 45 and theta > -45: 79 | sum_theta += theta 80 | linenum = linenum + 1 81 | #print(line[0][1]) 82 | cv2.line(img_cor_raw, (x1, y1), (x2, y2), (0, 255, 0), 2) 83 | #showImg(img_cor_raw) 84 | except: 85 | print("图片位置偏移") 86 | return 0 87 | 88 | if linenum == 0: 89 | return 0 90 | angle = sum_theta / linenum 91 | #print(angle) 92 | #angle = average / np.pi * 180 - 90 93 | if angle > 45: 94 | angle = -90 + angle 95 | if angle < -45: 96 | angle = 90 + angle 97 | return angle 98 | 99 | 100 | def correct(img_cor_raw): 101 | #img_process = Image_Binarization(img_cor_raw) 102 | if len(img_cor_raw.shape)==2: img_gary = img_cor_raw 103 | else: img_gary = cv2.cvtColor(img_cor_raw,cv2.COLOR_RGB2GRAY) 104 | img_process = cv2.adaptiveThreshold(img_gary, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 301, 10) 105 | img_process = ligature(img_process) 106 | #showImg(img_process) 107 | angle = get_angle(img_process,img_cor_raw) 108 | if angle == -1: 109 | print("No lines!!!") 110 | return 0 111 | return angle 112 | 113 | 114 | def get_point(event, x, y, flags, param): 115 | # 鼠标单击事件 116 | if event == cv2.EVENT_LBUTTONDOWN: 117 | # 输出坐标 118 | print('坐标值: ', x, y) 119 | # 在传入参数图像上画出该点 120 | #cv2.circle(param, (x, y), 1, (255, 255, 255), thickness=-1) 121 | img = param.copy() 122 | # 输出坐标点的像素值 123 | print('像素值:',param[y][x]) # 注意此处反转,(纵,横,通道) 124 | # 显示坐标与像素 125 | text = "("+str(x)+','+str(y)+')'+str(param[y][x]) 126 | cv2.putText(img,text,(0,param.shape[0]),cv2.FONT_HERSHEY_PLAIN,1.5,(0,0,255),1) 127 | cv2.imshow('image', img) 128 | cv2.waitKey(0) 129 | 130 | 131 | 132 | 133 | def search(path, name): 134 | for item in os.listdir(path): 135 | item_path = os.path.join(path, item) 136 | if os.path.isdir(item_path): 137 | search(item_path, name) 138 | elif os.path.isfile(item_path): 139 | if name in item: 140 | global result 141 | result.append(item_path) 142 | 143 | 144 | #这里使用的Python 3 145 | def sift_kp(image): 146 | gray_image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) 147 | #sift = cv2.xfeatures2d_SIFT.create() 148 | sift = cv2.SIFT_create() 149 | kp,des = sift.detectAndCompute(image,None) 150 | kp_image = cv2.drawKeypoints(gray_image,kp,None) 151 | return kp_image,kp,des 152 | 153 | def get_good_match(des1,des2): 154 | bf = cv2.BFMatcher() 155 | matches = bf.knnMatch(des1, des2, k=2) 156 | good = [] 157 | for m, n in matches: 158 | if m.distance < 0.75 * n.distance: 159 | good.append(m) 160 | return good 161 | 162 | def siftImageAlignment(img1,img2): 163 | _,kp1,des1 = sift_kp(img1) 164 | _,kp2,des2 = sift_kp(img2) 165 | goodMatch = get_good_match(des1,des2) 166 | if len(goodMatch) > 4: 167 | ptsA= np.float32([kp1[m.queryIdx].pt for m in goodMatch]).reshape(-1, 1, 2) 168 | ptsB = np.float32([kp2[m.trainIdx].pt for m in goodMatch]).reshape(-1, 1, 2) 169 | ransacReprojThreshold = 4 170 | H, status =cv2.findHomography(ptsA,ptsB,cv2.RANSAC,ransacReprojThreshold) 171 | imgOut = cv2.warpPerspective(img2, H, (img1.shape[1],img1.shape[0]),flags=cv2.INTER_LINEAR + cv2.WARP_INVERSE_MAP) 172 | return imgOut,H,status 173 | 174 | 175 | def rotateall(path): 176 | img = cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_COLOR) 177 | img1 = img[2800:3500, 1800:2200, :] 178 | img1 = cv2.blur(img1, (5, 5)) 179 | 180 | # means, dev = cv2.meanStdDev(img1) 181 | # print(means) 182 | img2 = img1 - 0 183 | img_bright_size = 3#3 184 | # showImg(img1) 185 | # 186 | # #得到对比度很大的图片 187 | img_bright = cv2.convertScaleAbs(img2, alpha=img_bright_size, beta=0) 188 | #showImg(img_bright) 189 | img2 = img_bright 190 | angle = correct(img_bright) 191 | #print(angle) 192 | imgrotate = rotate(img, -angle)#顺时针旋转 193 | return imgrotate 194 | 195 | if __name__ == '__main__': 196 | file_name = r"path to your image" 197 | image = rotateall(file_name) 198 | showImg(image) 199 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | #encoding=utf-8 2 | import cv2 3 | import numpy as np 4 | import math 5 | import os 6 | import PIL.Image as Image 7 | 8 | class Config: 9 | def __init__(self): 10 | pass 11 | 12 | src = r"D:\cut_image\1.jpg" 13 | folder_path = r"D:\cut_image" 14 | minArea = 800000 15 | kernel1 = np.ones((3, 3), dtype=np.uint8) 16 | kernel2 = np.ones((5, 5), dtype=np.uint8) 17 | kernel_sharpen_1 = np.array([ 18 | [-1, -1, -1], 19 | [-1, 9, -1], 20 | [-1, -1, -1]]) 21 | kernel_sharpen_2 = np.array([ 22 | [1, 1, 1], 23 | [1, -7, 1], 24 | [1, 1, 1]]) 25 | kernel_sharpen_3 = np.array([ 26 | [-1, -1, -1, -1, -1], 27 | [-1, 2, 2, 2, -1], 28 | [-1, 2, 8, 2, -1], 29 | [-1, 2, 2, 2, -1], 30 | [-1, -1, -1, -1, -1]]) / 8.0 31 | resizeRate = 1 32 | 33 | def showImg(img): 34 | cv2.namedWindow('Image', 0) 35 | cv2.imshow("Image", img) 36 | cv2.waitKey(0) 37 | cv2.destroyAllWindows() 38 | 39 | def readImg(img_path, gray=True): 40 | img = cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), cv2.IMREAD_COLOR) 41 | if gray: 42 | img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 43 | return img 44 | 45 | def saveImg(img, save_path): 46 | cv2.imencode('.jpg', img)[1].tofile(save_path) 47 | 48 | def get_point(event, x, y, flags, param): 49 | # 鼠标单击事件 50 | if event == cv2.EVENT_LBUTTONDOWN: 51 | # 输出坐标 52 | print('坐标值: ', x, y) 53 | # 在传入参数图像上画出该点 54 | # cv2.circle(param, (x, y), 1, (255, 255, 255), thickness=-1) 55 | img = param.copy() 56 | # 输出坐标点的像素值 57 | print('像素值:', param[y][x]) # 注意此处反转,(纵,横,通道) 58 | # 显示坐标与像素 59 | text = "(" + str(x) + ',' + str(y) + ')' + str(param[y][x]) 60 | cv2.putText(img, text, (0, param.shape[0]), cv2.FONT_HERSHEY_PLAIN, 1.5, (0, 0, 255), 1) 61 | cv2.imshow('image', img) 62 | cv2.waitKey(0) 63 | 64 | 65 | # 得到图片像素值 66 | def get_point_value(img): 67 | cv2.namedWindow('image', 0) 68 | cv2.setMouseCallback('image', get_point, img) 69 | # 显示图像 70 | cv2.imshow('image', img) 71 | cv2.waitKey(0) 72 | cv2.destroyAllWindows() 73 | 74 | def png2jpg(path): 75 | for filename in os.listdir(path): # 文件夹里不止一张图片,所以要用for循环遍历所有的图片 76 | if os.path.splitext(filename)[1] == '.png': # 把path这个路径下所有的文件都读一遍,如果后缀名是png,进行下一步,即imread的读取 77 | img_path = path + '/' + filename 78 | img = cv2.imread(img_path) 79 | # print(img) 80 | # print(img_path) 81 | # print(filename.replace(".png", ".jpg")) 82 | newfilename = filename.replace(".png", ".jpg") # 用replace函数把.png换成.jpg 83 | new_path = path + '/' + newfilename 84 | print(new_path) 85 | cv2.imwrite(new_path, img) 86 | 87 | def clahe(img): 88 | if len(img.shape)==3: 89 | b, g, r = cv2.split(img) 90 | clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) 91 | b = clahe.apply(b) 92 | g = clahe.apply(g) 93 | r = clahe.apply(r) 94 | img_clahe = cv2.merge([b, g, r]) 95 | else: 96 | clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) 97 | img_clahe = clahe.apply(img) 98 | return img_clahe 99 | 100 | def imgBrightness(img, c, b): 101 | rows, cols = img.shape 102 | blank = np.zeros([rows, cols], img.dtype) 103 | rst = cv2.addWeighted(img, c, blank, 1-c, b) 104 | return rst 105 | 106 | def getLargestContour(img): 107 | _, dst = cv2.threshold(img, 35, 255, cv2.THRESH_BINARY) 108 | # print(dst) 109 | # 提取轮廓 110 | contours, hierarchy = cv2.findContours(dst, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 111 | 112 | # 计算轮廓面积 113 | contours = sorted(contours, key=cv2.contourArea, reverse=True) 114 | return [contours[0]] 115 | 116 | def getContourCenter(cnt): 117 | try: 118 | M = cv2.moments(cnt) 119 | except: 120 | print("ERROR: invalid contour") 121 | 122 | cX = int(M["m10"] / M["m00"]) 123 | cY = int(M["m01"] / M["m00"]) 124 | return [cX, cY] 125 | 126 | def distance(p1,p2): 127 | X = p1[0]-p2[0] 128 | Y = p1[1]-p2[1] 129 | return math.sqrt((X ** 2) + (Y ** 2)) 130 | 131 | def ligature(img,i=4,j=5): 132 | kernel = np.ones((3, 3), np.uint8) 133 | img = cv2.erode(img, kernel, iterations=i) 134 | img = cv2.dilate(img, kernel, iterations=j) 135 | return img 136 | 137 | def rotate(img_rotate_raw, angle): 138 | (h, w) = img_rotate_raw.shape[:2] 139 | (cx, cy) = (w // 2, h // 2) 140 | m = cv2.getRotationMatrix2D((cx, cy), angle, 1.0) # 计算二维旋转的仿射变换矩阵 141 | return cv2.warpAffine(img_rotate_raw, m, (w, h), borderValue=(0, 0, 0)) 142 | 143 | def linePts(startx, starty, endx, endy, length): 144 | if endx==startx: theta = np.pi/2 145 | else: theta = np.arctan(abs(endy - starty)/abs(endx - startx)) 146 | if endy>starty: 147 | newendy = int(length*np.sin(theta)+starty) 148 | newendx = int(length*np.cos(theta)+startx) 149 | else: 150 | newendy = int(starty - length * np.sin(theta)) 151 | newendx = int(length * np.cos(theta) + startx) 152 | pts = [] 153 | if startx >= endx: 154 | for x in range(endx, startx): 155 | y = int((x - startx) * (endy - starty) / (endx - startx)) + starty 156 | pts.append([x,y]) 157 | else: 158 | for x in range(startx, endx): 159 | y = int((x - startx) * (endy - starty) / (endx - startx)) + starty 160 | pts.append([x, y]) 161 | for x in range(endx, newendx): 162 | y = int((x - endx) * (newendy - endy) / (newendx - endx)) + endy 163 | pts.append([x, y]) 164 | return pts 165 | 166 | def remove_small_objects(img, size): 167 | binary = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 501, 10) 168 | contours, hierarch = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) 169 | for i in range(len(contours)): 170 | area = cv2.contourArea(contours[i]) 171 | if area < size: 172 | cv2.drawContours(img, [contours[i]], 0, 255, -1) 173 | return img --------------------------------------------------------------------------------