数图

288 阅读6分钟

代办事项:

    1. 章节8之后的代码参数含义尽量注释起来,便于理解
    1. 10 章之后的内容听得比较迷糊,重复听下

前言

感谢极棒的数字图像处理入门到进阶教程:Python OpenCV实战数图_哔哩哔哩_bilibili老师开源了部分课程,基于这门课做的笔记,用到了老师的课件截图(侵删),推荐到腾讯课堂支持下这个老师

numpy

参考:NumPy 中文
Numpy全面语法教程 & 更好理解Numpy用法 - 知乎 (zhihu.com)

  • 矩阵创建及切片
    objp = np.zeros((6 * 8, 3), np.float32)      
    # 从索引为2行切到5行(不包括5),2列切到4列(不包括4列), 索引的起始下标为0
    objp[2:5, 2:4]      
    # 创建二位矩阵
    objp = np.zeros((6, 3), np.float32)
    print(objp)
    [[0. 0. 0.]
     [0. 0. 0.]
     [0. 0. 0.]
     [0. 0. 0.]
     [0. 0. 0.]
     [0. 0. 0.]]       
    
  • 绘图矩阵
    # 返回两个矩阵  
    print(np.mgrid[0:8, 0:6])  
    [[[0 0 0 0 0 0]
      [1 1 1 1 1 1]
      [2 2 2 2 2 2]
      [3 3 3 3 3 3]
      [4 4 4 4 4 4]
      [5 5 5 5 5 5]
      [6 6 6 6 6 6]
      [7 7 7 7 7 7]]
    
     [[0 1 2 3 4 5]
      [0 1 2 3 4 5]
      [0 1 2 3 4 5]
      [0 1 2 3 4 5]
      [0 1 2 3 4 5]
      [0 1 2 3 4 5]
      [0 1 2 3 4 5]
      [0 1 2 3 4 5]]]
    
  • 将 3 rows 4 cols 上的点填充
    # 参数分别指定列号的变化关系,及行号的变化关系
    x,y = np.mgrid[0:4, 0:3]
    print(x)
    # 打印输出
    # x 矩阵
    [[0 0 0]       
     [1 1 1]
     [2 2 2]
     [3 3 3]]
    print(y)          
    # y 矩阵 
    [[0 1 2]
     [0 1 2]
     [0 1 2]
     [0 1 2]]  
    # 绘图  
    plt.plot(x, y, '*', color = 'r')
    plt.show()   
    
    # 将点格式化成2列, [x, y]的形式  
    print(np.mgrid[0:4, 0:3].T.reshape(-1, 2))        
    # 2列的矩阵 
    [[0 0]
     [1 0]
     [2 0]
     [3 0]
     [0 1]
     [1 1]
     [2 1]
     [3 1]
     [0 2]
     [1 2]
     [2 2]
     [3 2]]            
    
    数字图像处理_mgrid效果.png
  • pltshow图片的函数
    import cv2 as cv
    '''
    import matplotlib.pyplot as plt  
    '''
    from matplotlib, import pyplot as plt
    def show(img):
        if img.ndim == 2:
              plt.imshow(img, cmap = 'gray', vmin = 0, vmax = 255)
        else:
              plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
        plt.show()
    
    img = cv.imread("./1.png")
    show(img)
    img = cv.imread("./1.png", 0)
    show(img)
    
  • 最简单的绘图
    • 绘单条曲线
      # 得到数组
      x = np.arange(2, 20)
      y = 2*x + np.random.randint(5, 20, 18)
      plt.plot(x, y, '*-', color = 'r')
      # plt.plot(x, y, color = 'r')
      plt.show()
      
    • 绘多条曲线
      # 均分切片,100个元素的数组
      x = np.linspace(0, 1, 100)
      y1 = np.power(x, 0.5)
      y2 = np.power(x, 1)
      y3 = np.power(x, 1.5)
      plt.plot(x, y1, x, y2, x, y3)
      plt.show()
      
      分开绘图
      # 均分切片,100个元素的数组
      x = np.linspace(0, 1, 100)
      y1 = np.power(x, 0.5)
      y2 = np.power(x, 1)
      y3 = np.power(x, 1.5)
      # 分开写
      plt.plot(x, y1, label = '0.5')
      plt.plot(x, y2, label = '1')
      plt.plot(x, y3, label = '1.5')
      # 显示图例
      plt.legend()
      # 显示网格
      plt.grid()
      # 轴起始坐标
      plt.xlim([0, 1])
      plt.ylim([0, 1])
      # 设置轴名称
      plt.xlabel("x")
      plt.ylabel('y')
      plt.show()
      
  • 绘直方图
    • 最简单绘制
      # 生成1000个随机数,范围在0,101之间(不包括101)  
      a = np.random.randint(0, 101, 1000)  
      # 直方图,参数二规范感兴趣的直方范围, 只显示0.9部分,间隔0.1  
      plt.hist(a, rwidth = 0.9, color = 'g')  
      plt.show()  
      
    • 规定感兴趣区域
      # 拿到数组,起点是-0.5,终点是101,步长是1  
      # 这样的话,间隔1,落到直方里, 比如 0, 1, 2 这些整数刚好能落到-0.5~1.5的自方里    
      bins = np.arange(-0.5, 101, 1)  
      # 将数组a直方化
      plt.hist(a, bins)  
      plt.show()
      

数图

  • 灰度图矩阵

    a = np.random.randint(0, 256, (2, 4), dtype = uint8)  
    show(a)
    
  • 真彩色矩阵,三维矩阵

    # 定义三维时一定要是(x, x, 3)
    b = np.random.randint(0, 256, (2, 4, 3), dtype = uint8)  
    show(b)
    
  • 查看矩阵行列,通道数

    a.shape
    

    输出

    # 二维矩阵  
    (200, 300)
    # 三维矩阵  
    (200, 400, 3)
    
  • 通道合并

    img = cv.merge([b, g, r])
    
  • float型矩阵转uint8的矩阵

    uint8_array = np.uint8(float_array)  
    # 或者
    uint8_array = float_array.astype(np.uint8)
    
  • 彩色转灰度图

    gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)      
    
  • 阈值二值化

    thresh = 125  
    gray[gray > 125] = 255  
    gray[gray <= 125] = 0     
    

    opencv 的

    _, img_bin = cv.threshold(gray, 125, 255, cv.THRESH_BINARY)  
    
  • 几张图片矩阵拼接

    mat = np.hstack([mat1, mat2, mat3])  
    
  • 图片相加,矩阵相加的话,溢出会做循环处理
    opencv提供图片相加的方法,超过类型(比如uint8)会截断

    img = cv,add(img1, img2)      
    

    一般加权相加也就是图像融合

    # 注意,img1*0.5后就是float,不会截断成uint8,即imgfloat
    img = cv,add(img1*0.5, img2*0.5)  
    # 最有一个参数 offset,得到uint8的img
    img = cv.addWeighted(img1, 0.5, img2, 0.5, 0)
    
  • 图片相减
    如果图片本身是由两张合成的,那么相减考虑权重
    考虑是否将减后的图像 *2
    考虑类型是否转成uint8(截断)

    img = np.uint8((img_inter*1.0 - img1*0.5) * 2)      
    

    opencv的
    考虑两个矩阵的数据类型要一致, img_inter*1.0得到的imgfloat64

      1. cv.imshow()动作前最好np.uint8()类型转换下
      1. 抑或不转成uint8,使用自定义的show()函数
    img = cv.subtract(img_inter*1.0, img1*0.5)  
    # 会得到符合肉眼期望的效果
    img = np.uint8(img)  
    cv.imshow(img)    
    # 抑或直接使用自定义的 show() 亦能将 float64 的矩阵图像正常显示    
    show(img)
    # 直接show可能会造成二值化  
    cv.imshow(img)    
    
  • 矩阵中找出0元素,并修改它的值
    找出0元素,并修改为1, 非0元素不动

    obj = np.where(obj == 0, 1, obj)
    

    0元素不能做被除数, 把它改为1
    意为两个矩阵相比,取数最大的

    noise = np.maximum(noise, 1)
    

    元素为0的改动为1

    obj[obj == 0] = 1
    
  • 图像乘法,掩膜矩阵最好是 0 - 1 之间的元素
    掩膜矩阵不是 0 - 1 的可以先转成 0 - 1

    mask = mask / 255  
    # 乘以掩码,再放大*255,,再把类型转成uint8
    img = np.uint8((img * mask)*255)
    
  • 掩码矩阵(元素在 0 - 1中间)取补

    mask = 1 - mask
    
  • 图片的除法
    因为该方法会将除数做处理,0元素会被改为非0值
    因此,即使两张图片一模一样,计算出的结果也不会全是1

    img = cv.divide(noise, obj)
    

    注意,参与运算的几个数都是uint8的话,结果默认也是uint8,结果在处理溢出是按循环,非截断的方式

  • np数据的转换

    img.astype(np.int32)
    
  • 数据截断

    np.clip(img, 0, 255)
    
  • opencv像素值的变换

    • 线性缩放,倍率 alpha=2,偏移 beta=20
      img = cv.convertScaleAbs(gray, alpha=2, beta=20)
      
    • 对数缩放
      img = np.log(gray.astype(np.float32)+1) / b
      
    • gama缩放,首先把矩阵缩放到 0~1 之间
      # 系数(倍率)c = 1, gama = 0.5
      gama = 0.5
      img = np.power(img, gama) * 255
      
  • 图像截断
    先截断行,后列, 然后是通道数,选取全部通道

    img_cut = img[150:500, 50:300, :]          
    
  • 图像的仿射变换

    • 先准备变换矩阵 数图_错切矩阵.png

      数图_镜像矩阵.png

      数图_变换矩阵.png

    • 矩阵写法

      M = np.array([[1, 0, 50],
               [0, 1, 100]],dtype=np.float32)
      
    • 仿射变换
      指定输出图像尺寸
      注意,和Mat::resize(x, y)类似,指定x 轴宽,y 轴宽,而不是 rows, cols
      注意, rows, cols = obj.shape记住这么返回的
      要想图像能被完整显示,不被截断

      rows, cols = obj.shape
      img = cv.warpAffine(obj, M, (cols + 50, rows + 100))
      

      opencv 提供的翻转函数

      # 水平翻转
      img1 = cv.flip(obj, 1)
      # 垂直翻转
      img2 = cv.flip(obj, 0)
      # 水平+垂直翻转
      img3 = cv.flip(obj, -1)
      

      按矩阵旋转,因为矩阵就说明了旋转的原心,即左上角

      rows, cols = obj.shape
      # 旋转矩阵
      beta = np.pi / 4
      M = np.array([[np.sin(beta), np.cos(beta), 0],
               [-np.cos(beta), np.sin(beta), 0]],dtype=np.float32)
      
      img = cv.warpAffine(obj, M, (cols, rows))
      show(np.hstack([obj, img]))
      

      opencv 旋转函数,只能按n * 90°

      img = cv.rotate(obj, cv.ROTATE_90_CLOCKWISE)
      
    • 透视变换

      # 获取透视变换矩阵  
      src = np.array([[190, 120], [590, 310], [650, 480], [140, 420]], dtype = np.float32)
      dst = np.array([[20, 20], [600, 20], [600, 500], [20, 500]], dtype = np.float32)
      M = cv.getPerspectiveTransform(src, dst)
      # 透视变换
      img = cv.warpPerspective(obj, M, (cols, rows))
      show(img)  
      
  • 图像线性放缩

    # 线性
    img = cv.resize(obj, (800, 800), interpolation=cv.INTER_LINEAR)
    # 双线性
    img1 = cv.resize(obj, (800, 800), interpolation=cv.INTER_LINEAR_EXACT)
    show(np.hstack([img, img1]))
    

滤波

  • 卷积实现
    比如 3 * 3 的均值卷积
    size = 3
    kernel = np.ones((size, size))
    kernel /= (size*size)
    dst = cv.filter2D(obj, -1, kernel)
    
  • opencv 均值滤波
    dst = cv.blur(src, (3, 3))          
    
    或者
    dst = cv.boxFilter(obj, cv.CV_8UC1, (3,3))
    
  • 中位值滤波
    # 参数二是 3 5 7 9 这些奇数值
    dst = cv.medianBlur(obj, 9)
    
  • 高斯滤波(高斯卷积核)
    方差越小,数据越集中, 越大越平坦
    方差过小达不到滤波效果,方差过大等效于均值滤波
    sigmaX = 0.5
    dst = cv.GaussianBlur(obj, (9, 9), sigmaX)
    show(dst)
    
  • 双边滤波
    要求既实现滤波又能保留高频的边缘信息
    卷积核特征:卷积核权值不固定,两个卷积核共同决定
    空间距离卷积核:离目标越远权重越小
    灰度差异卷积核:差异越大权重越小
    两个卷积核逐元素相乘
    # 参数二:,自动卷积核大小, 后面分别是 颜色卷积核方差, 空间距离卷积核方差
    dst = cv.bilateralFilter(obj, -1, sigmaColor=50, sigmaSpace=3)
    

锐化

  • premitt 算子
    注意, 指定类型 CV_64F
    后续处理,梯度是有+-的,可以取绝对值
    为方便显示,会截断到(0~255)
    梯度合成
    数图_premitt算子检测梯度.png

    kx = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype = np.float32)  
    ky = np.array([[-1, -1, -1], [0, 0, 0], [1, 1, 1]], dtype = np.float32)  
    # 注意, 指定类型 CV_64F 
    img_x = cv.filter2D(img, cv.CV_64F, kx)  
    img_y = cv.filter2D(img, cv.CV_64F, ky)    
    img_x = np.abs(img_x).clip(0, 255)
    img_y = np.abs(img_y).clip(0, 255)  
    img_xy = np.sqrt(img_x**2+img_y**2)  
    img_xy2 = np.abs(img_x) + np.abs(img_y)
    
  • Sobel算子(换成Scharr函数一样的)
    数图_sobel算子.png
    数图_sobel-feldman,scharr算子.png

    # 参数3: x 方向偏导系数
    img_x = cv.Sobel(img, cv.CV_16S, 1, 0)  
    # 参数4: y 方向偏导系数  
    img_y = cv.Sobel(img, cv.CV_16S, 0, 1)    
    # 合成梯度  
    img_xy = np.abs(img_x) + np.abs(img_y)
    
  • 拉氏算子,会产生双边缘,应用较少
    数图_laplacian算子LoG算子.png

    img_lap = cv.Laplacian(img, cv.CV_64F)  
    # 可视化处理  
    np.abs(img_lap)  
    
  • LoG算子

    img_blur = cv.GaussianBlur(img, (3, 3), 1)  
    img_log = cv.Laplacian(img_blur, cv.CV_64F)        
    
  • Canny算子推荐
    数图_canny算子.png

    # 20 200 阈值
    img_edge = cv.Canny(img, 20, 200)  
    
  • 阈值分割

    img = cv.imread("./1.bmp", 0)
    # plt.hist(img.ravel(), 256, [0, 256])
    plt.hist(img.flatten(), np.arange(-0.5, 256, 1), color='g')
    plt.show()
    # 阈值分割
    _, img_bin = cv.threshold(img, 250, 255, cv.THRESH_BINARY)
    show(img)
    

    数图_threshold函数参数.png 一个不是很好的光斑检测思路

    img_edge = cv.Canny(img, 200, 250)
    sigmaX = 0.6
    img_edge = cv.GaussianBlur(img_edge, (3, 3), sigmaX)
    _, img_edge = cv.threshold(img_edge, 10, 255, cv.THRESH_BINARY)
    

    光斑检测方案二

    sigmaX = 2
    img = cv.GaussianBlur(img, (9, 9), sigmaX)
    _, img = cv.threshold(img, 100, 255, cv.THRESH_BINARY)
    img_edge = cv.Canny(img, 200, 250)
    
  • 自适应阈值分割

    数图_自适应阈值分割.png

    # 参二: 阈值目标  
    # 参三: 适应阈值取得的算法, 均值或高斯加权  
    # 参四: 二值化  
    # 参五: 局部矩阵大小  
    # 参六: 偏移值      
    img_adpt = cv.adaptive(img, 255, cv.ADAPTIVE_MEAN_C, cv.THRESH_BINARY, 25, 0)  
    

形态学

数图_处理及相关记号.png

  • 腐蚀:消除毛刺和小区域(噪声被腐蚀掉,同时线条变细)

    • 分步进行
      K1 = np.ones((2,3), np.uint8)
      img_erode2 = cv.morphologyEx(img, cv.MORPH_ERODE, K1)
      
    • opencv
      I = cv.imread('pic/bird1000x800.jpg', 0)  
      K = np.ones((11,11), dtype=np.int)  
      img_e = cv.erode(I, K)
      
  • 膨胀:连接边缘,可能扩大噪声

    • 分步
      img = cv.imread('pic/ninety_bin50x50.png', -1)
      K = cv.getStructuringElement(cv.MORPH_RECT, (3,1))
      img_dilate2 = cv.morphologyEx(img, cv.MORPH_DILATE, K)
      
    • opencv
      img_dilate = cv.dilate(img, K)
      show(np.hstack([img, img_dilate, img_dilate2]))
      

    注意,元矩阵(元素为1)的写法

    # 推荐
    K = np.ones((3, 3), dtype=np.int)        
    # 要求掌握  
    array([[1, 1, 1],
       [1, 1, 1],
       [1, 1, 1]], dtype=uint8)
    

    注意,以二值的形式读入图片,参数二为 -1

    img_bin = cv.imread(\"pic/ninety_bin50x50.png\", -1)  
    
  • 取得一个矩形的结构元

    kernel = cv.getStructuringElement(cv.MORPH_RECT, (3,3))  
    
  • 获取椭圆的结构元

    K2 = cv.getStructuringElement(cv.MORPH_ELLIPSE, (7,7))
    
  • 开运算 既想消除噪声又想保持线条的粗细
    开运算: 腐蚀 --> 膨胀

    • 分步
    K = cv.getStructuringElement(cv.MORPH_RECT, (3,3))
    img_open = cv.dilate(cv.erode(img, K), K)
    
    • opencv
    img_open2 = cv.morphologyEx(img, cv.MORPH_OPEN, K)    
    show(np.hstack([img, img_open, img_open2]))
    
  • 闭运算 线条上有孔洞,填充孔洞,同时保持线条粗细
    闭运算: 膨胀 --> 腐蚀

    • 分步
    K = cv.getStructuringElement(cv.MORPH_RECT, (3,3))
    img_close = cv.erode(cv.dilate(img, K), K)
    
    
    • opencv
    img_close1 = cv.morphologyEx(img, cv.MORPH_CLOSE, K)  
    show(np.hstack([img, img_close, img_close1]))
    
  • 形态学梯度,简单认为就是 膨胀 - 腐蚀

    数图_形态学梯度,简单认为就是膨胀-腐蚀.png 相减,推荐cv.subtract超过存储空间的自动截断,不会循环到高bit

    img = cv.imread('pic/flower_bin500x500.png', -1)  
    K = np.ones((3,3), np.uint8)  
    img_grad1 = cv.morphologyEx(img, cv.MORPH_GRADIENT, K)
    img_grad2 = cv.subtract(cv.dilate(img, K), img)
    show(np.hstack([img, img_grad1, img_grad2]))
    
  • 顶帽与底帽 二值图的顶帽与底帽没有意义
    顶帽(效果类似局部阈值)

    数图_滚图表现开闭运算.png

    数图_顶帽变换效果.png

    • 顶帽中开运算的意义:获取背景
      原图 - 开运算(背景) = 突出的图
    • 分步
      I = cv.imread('pic/page760x900.jpg', 0)\n",        
      Ic = 255 - I        
      K = np.ones((21, 21), np.uint8)    
      # 开运算 获取背景  
      Ic_open = cv.morphologyEx(Ic, cv.MORPH_OPEN, K)        
      Ic_tophat = cv.subtract(Ic, Ic_open)        
      _, Ic_bin = cv.threshold(Ic_tophat, 25, 255, 0)  
      I_bin = 255 - Ic_bin        
      show(I_bin)
      
    • opencv
      I = cv.imread('pic/page760x900.jpg', 0)  
      Ic = 255 - I  
      K = np.ones((21, 21), np.uint8)  
      Ic_tophat = cv.morphologyEx(Ic, cv.MORPH_TOPHAT, K)  
      _, Ic_bin = cv.threshold(Ic_tophat, 25, 255, 0)  
      I_bin = 255 - Ic_bin  
      show(I_bin)
      
  • 底帽与顶帽是对偶操作,用底帽处理上个顶帽例子可以第一步不进行颜色反转的操作

    • 底帽
      I = cv.imread('pic/page760x900.jpg', 0)  
      K = np.ones((21, 21), np.uint8)  
      I_blackhat = cv.morphologyEx(I, cv.MORPH_BLACKHAT, K)  
      _, Ic_bin = cv.threshold(I_blackhat, 25, 255, 0)  
      I_bin = 255 - Ic_bin    
      
  • 击中击不中
    数图_击中击不中操作及效果过程.png 代码很少,过程重要
    用白色的结构元来腐蚀相当于与运算
    注意:
    卷积的获取
    两矩阵求交,逐cv.bitwise_and()

    I = cv.imread('pic/rectangle_find35.png', -1)  
    K = np.zeros((37,37), np.uint8)      
    # 卷积的获取
    K[1:36, 1:36] = 1  
    IeK = cv.erode(I, K)  
    Ic = 255 - I  
    Kc = 1 - K  
    IceKc = cv.erode(Ic, Kc)  
    hitmiss = cv.bitwise_and(IeK, IceKc)  
    show(hitmiss)
    
  • 查找某像素值的坐标
    可能得到 x y 是一个数组

    x, y = np.where(hitmiss == 255)    
    
  • 下/上采样
    数图_金字塔下采样.png
    数图_金字塔上采样.png 图像金字塔:多分辨率解释图像的有效结构

    img = cv.imread('pic/apple.jpg')  
    img_down = cv.pyrDown(img)  
    img_up = cv.pyrUp(img)
    show(img_down)
    show(img_up)      
    
  • 图像融合,拉氏金字塔的应用
    拉氏金字塔
    数图_拉氏金字塔.png
    数图_高斯金字塔与拉氏金字塔.png

    S0 = cv.imread('pic/apple.jpg')  
    S1 = cv.pyrDown(S0)    
    L0 = cv.subtract(S0, cv.pyrUp(S1))
    S2 = cv.pyrDown(S1)
    L1 = cv.subtract(S1, cv.pyrUp(S2))                
    

    图像融合
    数图_图像融合过程.png 注意学习for循环的写法
    连续下采样的写法

    apple = cv.imread('pic/apple.jpg')
    orange = cv.imread('pic/orange.jpg')
    A = [apple]  
    B = [orange]  
    LA, LB, L = [], [], []  
    n = 5  
    for i in range(1, n+1):  
        A.append(cv.pyrDown(A[-1]))
        B.append(cv.pyrDown(B[-1]))  
    for i in range(n):          
        LA.append(cv.subtract(A[i], cv.pyrUp(A[i+1])))    
        LB.append(cv.subtract(B[i], cv.pyrUp(B[i+1])))
    LA.append(A[n])  
    LB.append(B[n])
    for la, lb in zip(LA, LB):  
        h, w, c = la.shape  
        L.append(np.hstack([la[:, :w//2], lb[:, w//2:]]))  
    B = L[n]      
    for i in range(n, 0, -1)  
        B = cv.add(cv.pyrUp(B), L[i-1])  
    show(B)             
    
  • 几何绘图

    • 直线
      cv.line(img, (50, 25), (300, 175), (255, 0, 0), 2)
      
    • 矩形
      # 左上角,右下角
      cv.rectangle(img, (50, 25), (300, 175), (255, 255, 0)) 
      
    • cv.circle(img, (150, 100), 80, (255, 0, 255), 2)
      
    • 椭圆
      box = ((150, 100), (100, 50), 30)  
      cv.ellipse(img, box, (0, 255, 255))
      
    • 多边形
      pts = np.array([ [[100, 50]], [[250, 50]], [[200, 150]] ])  
      cv.polylines(img, [pts, pts+20], True, (255, 0, 125), 2)
      
    • 文字
      cv.putText(img, \"Lion\", (25,25), cv.FONT_HERSHEY_COMPLEX, 1.0, (125, 255, 125))
      
  • 轮廓绘制

    img = cv.imread('pic/contour_bin.png', -1)  
    contours, hierarchy = cv.findContours(img, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_NONE)  
    bgr = cv.merge([img, img, img])  
    cv.drawContours(bgr, contours, 1, (255, 255, 0), 3)  
    show(bgr)  
    # 只取通道2
    contours[2].shape
    
  • 轮廓提取

    img = cv.imread('pic/contours2_bin.png', -1)  
    contours, hierarchy = cv.findContours(img, cv.RETR_TREE, cv.CHAIN_APPROX_NONE)  
    bgr = cv.merge([img, img, img])  
    cv.drawContours(bgr, contours, -1, (255, 255, 0), 3)  
    show(bgr)
    array([[[ 5, -1,  1, -1],
               [ 2, -1, -1,  0],
               [-1,  1,  3,  0],
               [ 4, -1, -1,  2],
               [-1,  3, -1,  2],
               [ 6,  0, -1, -1],
               [-1,  5, -1, -1]]], dtype=int32)
    
  • 提取图片轮廓并添加 alph 通道

    img = cv.imread('pic/goldfish500x500.jpg')  
    b, g, r = cv.split(img)  
    edge1 = cv.Canny(r, 5, 50)  
    K = cv.getStructuringElement(cv.MORPH_RECT, (3,3))    
    edge2 = cv.morphologyEx(edge1, cv.MORPH_CLOSE, K, iterations=3)
    show(edge2)  
    cnts, hiers = cv.findContours(edge2, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)  
    mask = np.zeros((500, 500), np.uint8)  
    cv.drawContours(mask, cnts, 0, 255, -1)  
    show(mask)  
    goldfish = np.zeros((500, 500, 4), np.uint8)  
    goldfish[:, :, :3] = img  
    goldfish[:, :, 3] = mask
    show(goldfish)  
    cv.imwrite('test/goldfish.png', goldfish)