图像轮廓

基本理论

轮廓可以简单地解释为连接所有连续点(沿着边界)的曲线,具有相同的颜色或强度。轮廓是形状分析和物体检测和识别的有用工具。

  • 处理轮廓时,使用的是二进制图像,首先应该使用阈值处理或者边缘检测( canny )
  • cv2.findContours(), cv2.drawContours()

findContours(image, mode, method[, contours[, hierarchy[, offset]]]) -> contours, hierarchy

  • image: 源图像
  • mode: 轮廓检索模式
    • cv2.RETR_EXTERNAL 只检测最外层轮廓
    • cv2.RETR_LIST 提取所有轮廓,并放置在 list 中。建立的轮廓不建立等级关系
    • cv2.RETR_CCOMP 提取所有轮廓,并将其组织为双层结构,顶层为连通域的外围边界,第二层为孔的内层边界,若孔内还有物体轮廓,则将其放在第一层
    • cv2.RETR_TREE 提取所有轮廓,并重新建立网状的轮廓结构
    • cv2.RETR_FLOODFILL
  • method: 轮廓的近似方法
    • cv2.CHAIN_APPROX_NONE 获取每个轮廓的像素,相邻的两个点的像素位置差不超过1
    • cv2.CHAIN_APPROX_SIMPLE 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,如一个矩形需要四个坐标来保存轮廓信息
    • cv2.CHAIN_APPROX_TC89_KCOS、cv2.CHAIN_APPROX_TC89_L1: 应用 Teh-Chin 链式近似算法的一个
  • contours: 检测到的轮廓保存的地方,每个轮廓保存为一个点向量
  • hierarchy: 包含图像的拓扑信息,图像的层次结构
  • offset: ROI 图像时用到的参数
    参数说明

drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]]) -> image

  • image:
  • contours: 轮廓保存信息的参数,数据结构为列表
  • contourIdx: 轮廓绘制的标志量,若为负数,则代表绘制所有轮廓
  • color: 颜色
  • thickness: 轮廓线条粗细,默认值为 1,若为负值,则绘制在轮廓内部
  • lineType: 线条类型默认为 cv2.LINE_8 (8 连通线型)、cv2.LINE_4(4 连通线型)、cv2.LINE_AA (抗锯齿线型)
  • hierarchy: 层次结构信息
  • maxLevel: 绘制轮廓的最大等级默认 cv2.INTER_MAX
  • offset: 轮廓偏移参数

例程

  1. import cv2
  2. import numpy as np
  3. img = np.zeros((300, 300, 3), dtype=np.uint8)
  4. cv2.rectangle(img, (100, 100), (200, 200), (255, 255, 255), -1)
  5. cv2.circle(img, (250, 250), 30, (255, 255, 255), -1)
  6. #img = cv2.imread("./sample_img/back.jpg")
  7. imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  8. ret, thresh = cv2.threshold(imgray, 127, 255, 0)
  9. # findContours 只能处理二值图像
  10. contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
  11. count = len(contours)
  12. for i in range(count):
  13. if i % 2 == 0:
  14. cv2.drawContours(img, contours, i, (0, 255, 0), 2, lineType=cv2.LINE_8, hierarchy=hierarchy)
  15. else:
  16. cv2.drawContours(img, contours, i, (0, 0, 255), 2, lineType=cv2.LINE_8, hierarchy=hierarchy)
  17. cv2.imshow("img", img)
  18. cv2.waitKey(0)
  19. cv2.destroyAllWindows()

OpenCV 系列教程6 - OpenCV 图像处理(下) - 图1

  1. import cv2
  2. import numpy as np
  3. img = cv2.imread("./sample_img/contours.jpg")
  4. imgray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  5. ret, thresh = cv2.threshold(imgray, 119, 255, 0)
  6. contours, hierarchy = cv2.findContours(
  7. thresh, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
  8. img = cv2.drawContours(img, contours, -1, (0, 255, 0), 2,
  9. lineType=cv2.LINE_AA, hierarchy=hierarchy)
  10. cv2.imshow("img", img)
  11. cv2.imshow("thresh", thresh)
  12. cv2.waitKey(0)
  13. cv2.destroyAllWindows()

OpenCV 系列教程6 - OpenCV 图像处理(下) - 图2

  1. import cv2
  2. import numpy as np
  3. def contours_demo(img):
  4. #dst = cv2.GaussianBlur(img, (3, 3), 0)
  5. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  6. ret, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
  7. cv2.imshow("binary image", binary)
  8. contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  9. for i, contour in enumerate(contours):
  10. cv2.drawContours(img, contours, i, (0, 0, 255), 2)
  11. #print(i)
  12. cv2.imshow("detect contours", img)
  13. src = cv2.imread("./sample_img/circle.png")
  14. cv2.imshow("src", src)
  15. contours_demo(src)
  16. cv2.waitKey(0)
  17. cv2.destroyAllWindows()

OpenCV 系列教程6 - OpenCV 图像处理(下) - 图3

凸包检测及其他轮廓特征

OpenCV 系列教程6 - OpenCV 图像处理(下) - 图4

convexHull(points[, hull[, clockwise[, returnPoints]]]) -> hull

  • points: 输入的二维点集
  • hull: 返回的结果,即凸包
  • clockwise: 操作方向标识符 True:凸包为顺时针,False:逆时针。且设定 x 轴指向右, y 轴指向上方
  • returnPoints: 操作标识符,默认为 True:返回凸包的各个点,False:返回凸包各个点的指数

简易例程

  1. import cv2
  2. import numpy as np
  3. import random
  4. def contours_demo(img):
  5. #dst = cv2.GaussianBlur(img, (3, 3), 0)
  6. #gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  7. ret, binary = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
  8. #cv2.imshow("binary image", binary)
  9. contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  10. #for i, contour in enumerate(contours):
  11. # cv2.drawContours(img, contours, i, (0, 0, 255), 2)
  12. #print(i)
  13. cv2.imshow("detect contours", img)
  14. return contours
  15. img = np.zeros((600, 600), dtype=np.uint8)
  16. for i in range(30):
  17. x = random.randint(200, 250)
  18. y = random.randint(200, 250)
  19. #print(x, y)
  20. cv2.circle(img, (x, y), 10, 255, -1)
  21. contours = contours_demo(img)
  22. cnt = contours[0]
  23. hull = cv2.convexHull(cnt)
  24. hullcount = hull.shape[0]
  25. for i in range(hullcount-1):
  26. x0 = hull[i][0][0]
  27. y0 = hull[i][0][1]
  28. x1 = hull[i+1][0][0]
  29. y1 = hull[i+1][0][1]
  30. cv2.line(img, (x0, y0), (x1, y1), 255, 2)
  31. if i == hullcount-2:
  32. #print(x1, y1)
  33. x2 = hull[0][0][0]
  34. y2 = hull[0][0][1]
  35. cv2.line(img, (x1, y1), (x2, y2), 255, 2)
  36. cv2.imshow("dst", img)
  37. cv2.waitKey(0)
  38. cv2.destroyAllWindows()

OpenCV 系列教程6 - OpenCV 图像处理(下) - 图5

综合例程

  1. import cv2
  2. import numpy as np
  3. import random
  4. def convexHull_demo(img, sample, threshold):
  5. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  6. cv2.imshow("gray", gray)
  7. blur = cv2.GaussianBlur(gray, (3, 3), 0)
  8. ret, thresh = cv2.threshold(blur, threshold, 255, cv2.THRESH_BINARY)
  9. cv2.imshow("thresh", thresh)
  10. contours, hierachy = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
  11. hull = []
  12. for i in range(len(contours)-1):
  13. hull.append(cv2.convexHull(contours[i], True))
  14. for i in range(len(contours)-1): # 减 1 的作用是去掉图像最外层的轮廓,但是有些图像没有
  15. cv2.drawContours(sample, contours, i, (255, 0, 0), 2, cv2.LINE_8, hierachy)
  16. cv2.drawContours(sample, hull, i, (0, 255, 0), 2, cv2.LINE_8)
  17. cv2.imshow("sample", sample)
  18. def nothing(x):
  19. pass
  20. cv2.namedWindow("sample")
  21. cv2.createTrackbar("threshold", "sample", 0, 255, nothing)
  22. img = cv2.imread("./sample_img/contours_2.jpg", 1)
  23. while(1):
  24. sample = np.ones_like(img)
  25. threshold = cv2.getTrackbarPos("threshold", "sample")
  26. #print(threshold)
  27. convexHull_demo(img, sample, threshold)
  28. k = cv2.waitKey(1) & 0xff
  29. if k == 27:
  30. break
  31. cv2.destroyAllWindows()

image.png

轮廓近似

approxPolyDP(curve, epsilon, closed[, approxCurve]) -> approxCurve 以指定的精度近似多边形曲线

  • curve 二维矩阵
  • epsilon 指定近似精度的参数 epsilon = rate*cv2.arcLength(contours[i], True) # rate 为趋近的比例 如取 0.1, 0.01
  • closed 如果为真,则近似值曲线是闭合的(它的第一个和最后一个顶点是闭合的)
  1. import cv2
  2. import numpy as np
  3. import random
  4. img = np.zeros((500, 500, 3), dtype=np.uint8)
  5. cv2.rectangle(img, (200, 200), (400, 400), 255, -1)
  6. cv2.rectangle(img, (250, 200), (260, 220), 0, -1)
  7. cv2.rectangle(img, (200, 300), (220, 320), 0, -1)
  8. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  9. _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
  10. contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  11. count = len(contours)
  12. rate = 0.01 # 不同的取值会有不同的结果
  13. for i in range(count):
  14. epsilon = rate*cv2.arcLength(contours[i], True)
  15. approx = cv2.approxPolyDP(contours[i], epsilon, True)
  16. cv2.drawContours(img, approx, -1, (0, 0, 255), 10)
  17. for i in range(len(approx)-1):
  18. pt1 = (approx[i][0][0], approx[i][0][1])
  19. pt2 = (approx[i+1][0][0], approx[i+1][0][1])
  20. cv2.line(img, pt1, pt2, (0, 255, 0), 1)
  21. t = 1
  22. if t == 1:
  23. cv2.line(img, (approx[0][0][0], approx[0][0][1]), (approx[len(approx)-1][0][0], approx[len(approx)-1][0][1]), (0, 255, 0), 1)
  24. t = t + 1
  25. cv2.imshow("dst "+str(rate), img)
  26. cv2.waitKey(0)
  27. cv2.destroyAllWindows()

image.png

矩形边框

  1. import cv2
  2. import numpy as np
  3. import random
  4. img = np.zeros((500, 500, 3), dtype=np.uint8)
  5. cv2.rectangle(img, (100, 100), (200, 200), 255, -1)
  6. cv2.rectangle(img, (200, 200), (300, 300), 255, -1)
  7. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  8. _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
  9. contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  10. count = len(contours)
  11. for i in range(count):
  12. x, y, w, h = cv2.boundingRect(contours[i])
  13. img = cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
  14. cv2.imshow("dst", img)
  15. cv2.waitKey(0)
  16. cv2.destroyAllWindows()

image.png

旋转矩形

  1. import cv2
  2. import numpy as np
  3. import random
  4. img = np.zeros((500, 500, 3), dtype=np.uint8)
  5. cv2.rectangle(img, (100, 100), (200, 150), 255, -1)
  6. #cv2.rectangle(img, (200, 200), (300, 300), 255, -1)
  7. cv2.circle(img, (300, 300), 50, 255, -1)
  8. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  9. _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
  10. contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  11. count = len(contours)
  12. for i in range(count):
  13. rect = cv2.minAreaRect(contours[i]) # 返回 ((center_x, center_y), h, w, degree)
  14. print(rect)
  15. box = cv2.boxPoints(rect)
  16. box = np.int0(box)
  17. img = cv2.drawContours(img,[box],0,(0,0,255),2)
  18. cv2.imshow("dst", img)
  19. cv2.waitKey(0)
  20. cv2.destroyAllWindows()
  1. ((300.0, 300.0), (98.99495697021484, 98.99495697021484), -45.0)
  2. ((150.0, 125.0), (50.0, 100.0), -90.0)

image.png

最小封闭圆

  1. import cv2
  2. import numpy as np
  3. import random
  4. img = np.zeros((500, 500, 3), dtype=np.uint8)
  5. cv2.rectangle(img, (100, 100), (200, 200), 255, -1)
  6. cv2.rectangle(img, (211, 211), (300, 300), 255, -1)
  7. #cv2.circle(img, (300, 300), 50, 255, -1)
  8. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  9. _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
  10. contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  11. count = len(contours)
  12. for i in range(count):
  13. (x, y), radius = cv2.minEnclosingCircle(contours[i])
  14. center = (int(x), int(y))
  15. radius = int(radius)
  16. img = cv2.circle(img, center, radius, (0, 255, 0), 2)
  17. cv2.imshow("dst", img)
  18. cv2.waitKey(0)
  19. cv2.destroyAllWindows()

image.png

拟合椭圆 (至少需要 5 个点)

  1. import cv2
  2. import numpy as np
  3. import random
  4. img = np.zeros((500, 500, 3), dtype=np.uint8)
  5. cv2.rectangle(img, (100, 100), (200, 200), 255, -1)
  6. cv2.rectangle(img, (201, 201), (300, 300), 255, -1)
  7. #cv2.circle(img, (300, 300), 50, 255, -1)
  8. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  9. _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
  10. contours, hieriachy = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  11. count = len(contours)
  12. for i in range(count):
  13. ellipse = cv2.fitEllipse(contours[i])
  14. img = cv2.ellipse(img,ellipse,(0,255,0),2)
  15. cv2.imshow("dst", img)
  16. cv2.waitKey(0)
  17. cv2.destroyAllWindows()

image.png

拟合直线

  1. import cv2
  2. import numpy as np
  3. import random
  4. img = np.zeros((500, 500, 3), dtype=np.uint8)
  5. cv2.rectangle(img, (100, 100), (200, 200), 255, -1)
  6. cv2.rectangle(img, (201, 201), (300, 300), 255, -1)
  7. cv2.circle(img, (300, 300), 50, 255, -1)
  8. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  9. _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
  10. contours, hieriachy = cv2.findContours(
  11. binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  12. count = len(contours)
  13. for i in range(count):
  14. rows, cols = img.shape[:2]
  15. [vx, vy, x, y] = cv2.fitLine(contours[i], cv2.DIST_L2, 0, 0.01, 0.01)
  16. lefty = int((-x*vy/vx) + y)
  17. righty = int(((cols-x)*vy/vx)+y)
  18. img = cv2.line(img, (cols-1, righty), (0, lefty), (0, 255, 0), 2)
  19. cv2.imshow("dst", img)
  20. cv2.waitKey(0)
  21. cv2.destroyAllWindows()

image.png

轮廓属性

纵横比(Aspect Ratio) OpenCV 系列教程6 - OpenCV 图像处理(下) - 图13

extent :是物体面积与边界矩形面积的比值 OpenCV 系列教程6 - OpenCV 图像处理(下) - 图14

Solidity :坚实度是等高线面积与其凸包面积之比。OpenCV 系列教程6 - OpenCV 图像处理(下) - 图15

Equivalent Diameter :等效直径是面积与等值线面积相等的圆的直径。

OpenCV 系列教程6 - OpenCV 图像处理(下) - 图16

Orientation 方向是物体所指向的角度

最大最小值及其位置

  1. import cv2
  2. import numpy as np
  3. import random
  4. img = np.zeros((500, 500, 3), dtype=np.uint8)
  5. cv2.rectangle(img, (100, 100), (300, 200), (255, 2, 0), -1)
  6. cv2.rectangle(img, (211, 201), (300, 300), 255, -1)
  7. #cv2.circle(img, (300, 300), 50, 255, -1)
  8. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  9. _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
  10. contours, hieriachy = cv2.findContours(
  11. binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  12. count = len(contours)
  13. cnt = contours[0]
  14. x, y, w, h = cv2.boundingRect(cnt)
  15. aspect_ratio = float(w)/h
  16. print("横纵比:", aspect_ratio)
  17. area = cv2.contourArea(cnt)
  18. x, y, w, h = cv2.boundingRect(cnt)
  19. rect_area = w*h
  20. extent = float(area)/rect_area
  21. print("extent", extent)
  22. area = cv2.contourArea(cnt)
  23. hull = cv2.convexHull(cnt)
  24. hull_area = cv2.contourArea(hull)
  25. solidity = float(area)/hull_area
  26. print("solidity", solidity)
  27. area = cv2.contourArea(cnt)
  28. equi_diameter = np.sqrt(4*area/np.pi)
  29. print("equi_diameter", equi_diameter)
  30. (x, y), (MA, ma), angle = cv2.fitEllipse(cnt) # 返回的是椭圆的外切矩形
  31. print((x, y), (MA, ma))
  32. mask = np.zeros(gray.shape, np.uint8)
  33. cv2.drawContours(mask, [cnt], 0, 255, -1)
  34. pixelpoints = np.transpose(np.nonzero(mask))
  35. print(pixelpoints)
  36. #pixelpoints = cv2.findNonZero(mask) # Numpy 里面的方法
  37. min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(gray,mask = mask)
  38. print(min_val, max_val, min_loc, max_loc)
  39. mean_val = cv2.mean(img,mask = mask)
  40. print(mean_val)
  41. cv2.imshow("dst", img)
  42. cv2.waitKey(0)
  43. cv2.destroyAllWindows()
  1. 横纵比: 1.0
  2. extent 0.715341204425633
  3. solidity 0.838911465892598
  4. equi_diameter 191.826117780188
  5. (217.58111572265625, 178.86651611328125) (220.607177734375, 291.5225524902344)
  6. [[100 100]
  7. [100 101]
  8. [100 102]
  9. ...
  10. [300 298]
  11. [300 299]
  12. [300 300]]
  13. 29.0 30.0 (211, 201) (100, 100)
  14. (255.0, 1.3856864953414558, 0.0, 0.0)

极端点

OpenCV 系列教程6 - OpenCV 图像处理(下) - 图17

  1. import cv2
  2. import numpy as np
  3. import random
  4. img = np.zeros((500, 500, 3), dtype=np.uint8)
  5. cv2.rectangle(img, (100, 100), (300, 200), 255, -1)
  6. cv2.rectangle(img, (211, 201), (300, 300), 255, -1)
  7. cv2.circle(img, (300, 300), 50, 255, -1)
  8. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  9. _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
  10. contours, hieriachy = cv2.findContours(
  11. binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  12. count = len(contours)
  13. cnt = contours[0]
  14. leftmost = tuple(cnt[cnt[:, :, 0].argmin()][0])
  15. rightmost = tuple(cnt[cnt[:, :, 0].argmax()][0])
  16. topmost = tuple(cnt[cnt[:, :, 1].argmin()][0])
  17. bottommost = tuple(cnt[cnt[:, :, 1].argmax()][0])
  18. cv2.line(img, leftmost, (leftmost[0]+1, leftmost[1]+1), (0, 255, 0), 5)
  19. cv2.line(img, rightmost, (rightmost[0]+1, rightmost[1]+1), (0, 255, 0), 5)
  20. cv2.line(img, topmost, (topmost[0]+1, topmost[1]+1), (0, 255, 0), 5)
  21. cv2.line(img, bottommost, (bottommost[0]+1, bottommost[1]+1), (0, 255, 0), 5)
  22. cv2.imshow("dst", img)
  23. cv2.waitKey(0)
  24. cv2.destroyAllWindows()

image.png

图像的矩

图像矩的应用广泛,包括模式识别、目标分类、目标识别、方位估计、图像编码与重构。

图像矩描述的是图像形状的全局特征,包括图像中不同类型的几何特性。如大小、位置、方向、形状等。

一阶矩与形状有关、二阶矩显示曲线围绕直线平均值的扩展程度,三阶矩是关于平均值的对称性的测量。二阶矩和三阶矩可以导出共 7 个不变矩,而不变矩是图像的同居特性,满足平移、伸缩、旋转均不变的不变性,应用广泛。

moments(array[, binaryImage]) -> retval 计算图像所有的矩(最高为三阶矩)

  • array: 二维数组
  • binaryImage: bool 类型,默认 False,若为 True 则所有非 0 像素为 1

contourArea(contour[, oriented]) -> retval 计算轮廓面积

  • contour: 轮廓点(二维)
  • oriented: bool 类型,面向区域标识符。为 True 返回一个带符号的面积值,正负取决于轮廓的方向,可以根据该标识符确定轮廓的位置。默认参数为 False 返回绝对值

arcLength(curve, closed) -> retval 计算轮廓长度(轮廓周长,曲线长度)

  • curve: 二维点集
  • closed: bool 类型,指示曲线是否封闭的标识符。默认封闭

简易例程

  1. import cv2
  2. import numpy as np
  3. # 创建一个特殊的案例来说明
  4. img = np.ones((500, 500, 3), dtype=np.uint8)*255
  5. cv2.circle(img, (200, 200), 50, (0, 0, 0), -1)
  6. cv2.line(img, (300, 300), (300, 400), (0, 0, 0), 1)
  7. # 正常图片测试
  8. #img = cv2.imread("./sample_img/Moments.jpg")
  9. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  10. blur = cv2.GaussianBlur(gray, (3, 3), 0)
  11. canny = cv2.Canny(blur, 0, 100, 3)
  12. contours, hierarchy = cv2.findContours(
  13. canny, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
  14. count = len(contours)
  15. moment = [] # 计算矩
  16. for i in range(count):
  17. moment.append(cv2.moments(contours[i], False))
  18. mc = [] # 质心
  19. area = [] # 面积
  20. length = [] # 长度
  21. for i in range(count): # 计算中心矩(质心)
  22. if moment[i]['m00'] != 0:
  23. cx = int(moment[i]['m10']/moment[i]['m00'])
  24. cy = int(moment[i]['m01']/moment[i]['m00'])
  25. else:
  26. cx = cy = 9999
  27. mc.append([cx, cy])
  28. area.append(cv2.contourArea(contours[i]))
  29. length.append(cv2.arcLength(contours[i], True))
  30. print(str(i), "m00:", moment[i]["m00"],
  31. "opencv area:", area[i], "length:", length[i])
  32. sample = np.zeros_like(img)
  33. for i in range(count):
  34. cv2.drawContours(sample, contours, i, (0, 255, 0), 2, 8, hierarchy)
  35. cv2.circle(sample, (int(mc[i][0]), int(mc[i][1])), 4, (255, 0, 0))
  36. cv2.imshow("src", img)
  37. cv2.imshow("sample", sample)
  38. cv2.waitKey(0)
  39. cv2.destroyAllWindows()
  1. 0 m00: 204.0 opencv area: 204.0 length: 208.0
  2. 1 m00: 202.0 opencv area: 202.0 length: 205.65685415267944
  3. 2 m00: 7866.0 opencv area: 7866.0 length: 332.04877281188965
  4. 3 m00: 7820.0 opencv area: 7820.0 length: 329.70562267303467

image.png

带阈值调节例程

以下程序适合小图片处理,大图片会耗费大量内存,特别是在绘制轮廓的时候

  1. import cv2
  2. import numpy as np
  3. import time
  4. def moment_demo(img, sample, threshold):
  5. canny = cv2.Canny(img, threshold, threshold*2, 3)
  6. contours, hierarchy = cv2.findContours(canny, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
  7. count = len(contours)
  8. #moment = [] # 计算矩
  9. area = []
  10. length = []
  11. moment = [cv2.moments(contours[i], False) for i in range(count)]
  12. # cx, cy 为质心计算公式
  13. cx = [int(moment[i]['m10']/moment[i]['m00']) if (moment[i]['m00'] != 0) else 9999 for i in range(count)]
  14. cy = [int(moment[i]['m01']/moment[i]['m00']) if (moment[i]['m00'] != 0) else 9999 for i in range(count)]
  15. area = [cv2.contourArea(contours[i]) for i in range(count)]
  16. length = [cv2.arcLength(contours[i], True) for i in range(count)]
  17. for i in range(count): # 这里会耗费很大的内存
  18. cv2.drawContours(sample, contours, i, (0, 255, 0), 2, 8, hierarchy) # 绘制轮廓
  19. cv2.circle(sample, (int(cx[i]), int(cy[i])), 4, (255, 0, 0)) # 绘制质心
  20. #print(str(i), "m00:", moment[i]["m00"],"opencv area:", area[i], "length:", length[i])
  21. def nothing(x):
  22. pass
  23. cv2.namedWindow("sample")
  24. cv2.createTrackbar("threshold", "sample", 1, 255, nothing)
  25. img = cv2.imread("./sample_img/ml.png")
  26. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  27. blur = cv2.GaussianBlur(gray, (3, 3), 0)
  28. while(1):
  29. t0 = time.time()
  30. sample = np.zeros_like(img)
  31. threshold = cv2.getTrackbarPos("threshold", "sample")
  32. moment_demo(blur, sample,threshold)
  33. #print(time.time()-t0)
  34. cv2.imshow("sample", sample)
  35. if (cv2.waitKey(1) & 0xff) == 27:
  36. break
  37. cv2.destroyAllWindows()

image.png

分水岭算法

灰度图像的像素值当作海拔的高度,在极小值处打一个小孔,将水注入,得到一个分水岭

watershed(image, markers) -> markers

  • image:
  • makers:

例程

使用分水岭算法和距离变换分割相互接触的物体

  1. # %matplotlib notebook
  2. import cv2
  3. import numpy as np
  4. import matplotlib.pyplot as plt
  5. def opening_demo(img):
  6. kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
  7. opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
  8. # opening = cv2.morphologyEx(img, 2, kernel) # 同上
  9. cv2.imshow("opening", opening)
  10. return opening
  11. def closing_demo(img):
  12. kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
  13. closing = cv2.morphologyEx(img, 3, kernel)
  14. cv2.imshow("closing", closing)
  15. return closing
  16. def dilation_demo(img):
  17. kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
  18. dilation = cv2.dilate(img, kernel, iterations=2)
  19. cv2.imshow("dilation", dilation)
  20. return dilation
  21. def distanceTransform_demo(gray):
  22. """
  23. src: 8 位单通道
  24. """
  25. dis_transform = cv2.distanceTransform(src=gray,distanceType=cv2.DIST_L2,maskSize=5)
  26. scale = cv2.convertScaleAbs(dis_transform)
  27. normalize = cv2.normalize(dis_transform, None, 255,0, cv2.NORM_MINMAX, cv2.CV_8UC1)
  28. cv2.imshow("dis_transform", dis_transform)
  29. #cv2.imshow("scale", scale)
  30. cv2.imshow("normalize", normalize)
  31. return normalize
  32. def watershed_demo(img):
  33. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  34. ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) # 看看二值化后的效果
  35. cv2.imshow("thresh", thresh)
  36. # 用开运算去除白点噪声
  37. opening = opening_demo(thresh)
  38. # 用闭运算去除物体上的孔洞
  39. closing = closing_demo(opening)
  40. # 膨胀物体的边界,可以确保物体的区域被保留,包含了前景物和一部分背景
  41. sure_bg = dilation_demo(closing)
  42. # 距离变换
  43. dist_transform = distanceTransform_demo(opening)
  44. # 确定前景物,大概率能确定为前景物的形状
  45. ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)
  46. cv2.imshow("sure_fg", sure_fg)
  47. sure_fg = np.uint8(sure_fg)
  48. # 获得一块区域,该区域是前景物与背景交界的一部分
  49. unknown = cv2.subtract(sure_bg, sure_fg)
  50. cv2.imshow("unknown", unknown)
  51. # Marker labelling 创建一个模板用来标记不同的区域,把连通的区域绘制不同的标签
  52. # 未知区域标记为 0,其他区域标记为不同的整数
  53. _, markers = cv2.connectedComponents(sure_fg)
  54. # 使模板加 1 ,确保背景不是 0
  55. markers = markers + 1
  56. # 使背景标记为 0
  57. markers[unknown==255] = 0
  58. plt.imshow(markers, cmap='jet') # 画出 jet 形式的图,可以清晰的看出每个硬币的位置
  59. plt.title("markers jet")
  60. plt.xticks([]), plt.yticks([])
  61. # 用 OpenCV 的方式显示 colormap,但是色彩没有 plt 画出来的色彩丰富
  62. colormap = cv2.applyColorMap(src=np.uint8(markers), colormap=cv2.COLORMAP_JET)
  63. cv2.imshow("colormap", colormap)
  64. # 分水岭算法,得到的边界标记为 -1
  65. markers = cv2.watershed(img, markers)
  66. cv2.imshow("marker", markers)
  67. img[markers == -1] = [0,0,255] # 若为 -1 代表是边界
  68. cv2.imshow("dst", img)
  69. def main():
  70. img = cv2.imread('./sample_img/water_coins.jpg')
  71. #img = cv2.resize(img, (1366, 768))
  72. watershed_demo(img)
  73. main()
  74. cv2.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
  75. cv2.destroyAllWindows() # 关闭所有窗口
  1. # %matplotlib notebook
  2. import cv2
  3. import numpy as np
  4. import matplotlib.pyplot as plt
  5. def opening_demo(img):
  6. kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
  7. opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
  8. # opening = cv2.morphologyEx(img, 2, kernel) # 同上
  9. cv2.imshow("opening", opening)
  10. return opening
  11. def closing_demo(img):
  12. kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
  13. closing = cv2.morphologyEx(img, 3, kernel)
  14. cv2.imshow("closing", closing)
  15. return closing
  16. def dilation_demo(img):
  17. kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
  18. dilation = cv2.dilate(img, kernel, iterations=2)
  19. cv2.imshow("dilation", dilation)
  20. return dilation
  21. def watershed_demo(img):
  22. gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  23. ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU) # 看看二值化后的效果
  24. cv2.imshow("thresh", thresh)
  25. # 用开运算去除白点噪声
  26. opening = opening_demo(thresh)
  27. # 用闭运算去除物体上的孔洞
  28. closing = closing_demo(opening)
  29. # 膨胀物体的边界,可以确保物体的区域不饿保留
  30. sure_bg = dilation_demo(closing)
  31. dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5)
  32. cv2.imshow("dist_transform", dist_transform)
  33. ret, sure_fg = cv2.threshold(dist_transform, 0.7*dist_transform.max(), 255, 0)
  34. cv2.imshow("sure_fg", sure_fg)
  35. sure_fg = np.uint8(sure_fg)
  36. unknown = cv2.subtract(sure_bg, sure_fg)
  37. cv2.imshow("unknown", unknown)
  38. # Marker labelling 创建一个模板用来标记不同的区域
  39. # 未知区域标记为 0,其他区域标记为不同的整数
  40. ret, markers = cv2.connectedComponents(sure_fg)
  41. # 使模板加 1 ,确保背景不是 0
  42. markers = markers + 1
  43. # 使背景标记为 0
  44. markers[unknown==255] = 0
  45. plt.imshow(markers, cmap='jet') # 画出 jet 形式的图,可以清晰的看出每个硬币的位置
  46. plt.xticks([]), plt.yticks([])
  47. markers = cv2.watershed(img, markers)
  48. img[markers == -1] = [0,0,255]
  49. cv2.imshow("marker", img)
  50. def main():
  51. img = cv2.imread('./sample_img/water_coins.jpg')
  52. #img = cv2.resize(img, (1366, 768))
  53. watershed_demo(img)
  54. main()
  55. cv2.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
  56. cv2.destroyAllWindows() # 关闭所有窗口

image.png

更多资料

分水岭分割介绍
基于距离变换和分水岭算法的图像分割

图像修复

inpaint(src, inpaintMask, inpaintRadius, flags[, dst]) -> dst

  • src: 源图像 8 位单通道或三通道图像
  • inpaintMask:修复掩模,为 8 位单通道图像。非 0 像素表示要修复的图像
  • inpaintRadius:double 需要修补的每个点的圆形邻域,为修复算法的参考半径
  • flags:修复方法标识符
    • cv2.INPAINT_NS 基于 Navier-Stokes 方程
    • cv2.INPAINT_TELEA Alexandru Telea
  1. import cv2
  2. import numpy as np
  3. drawing = False # 鼠标按下为 True
  4. ix, iy = -1, -1
  5. def on_mouse(event, x, y, flags, param):
  6. global ix, iy, drawing
  7. if event == cv2.EVENT_LBUTTONDOWN: # 鼠标放下
  8. drawing = True
  9. ix, iy = x, y # 鼠标放下时的坐标
  10. elif event == cv2.EVENT_MOUSEMOVE: # 鼠标移动
  11. if drawing == True:
  12. cv2.line(img, (x, y), (x, y), (255, 255, 255), 5)
  13. cv2.line(inpaintmask, (x, y), (x, y), 255, 5)
  14. elif event == cv2.EVENT_LBUTTONUP: # 鼠标提起,结束绘制
  15. drawing = False
  16. img = cv2.imread("./sample_img/inpaint.jpg")
  17. cv2.namedWindow("image")
  18. cv2.setMouseCallback("image", on_mouse) # 功能绑定
  19. inpaintmask = np.zeros((img.shape[0], img.shape[1]), dtype=np.uint8)
  20. while(1):
  21. cv2.imshow("image", img)
  22. cv2.imshow("inpaintmask", inpaintmask)
  23. k = cv2.waitKey(1) & 0xFF
  24. if k == ord('c'):
  25. cv2.imwrite("./sample_img/inpaint_2.jpg", img)
  26. if k == ord('f'):
  27. fix_img = cv2.inpaint(img, inpaintmask, 2, cv2.INPAINT_TELEA)
  28. cv2.imshow("fix_img", fix_img)
  29. if k == 27:
  30. break
  31. cv2.destroyAllWindows()

image.png
image.png