opencv进阶2

opencv进阶二

直方图匹配

直方图匹配又称为直方图规定化,是指将一幅图像的直方图变成规定形状的直方图而进行的图像增强方法。 [1] 即将某幅影像或某一区域的直方图匹配到另一幅影像上。使两幅影像的色调保持一致。可以在单波段影像直方图之间进行匹配,也可以对多波段影像进行同时匹配。两幅图像比对前,通常要使其直方图形式一致。

直方图规定化,也叫做直方图匹配,用于将图像变换为某一特定的灰度分布,也就是其目的的灰度直方图是已知的。这其实和均衡化很类似,均衡化后的灰度直方图也是已知的,是一个均匀分布的直方图;而规定化后的直方图可以随意的指定,也就是在执行规定化操作时,首先要知道变换后的灰度直方图,这样才能确定变换函数。规定化操作能够有目的的增强某个灰度区间,相比于,均衡化操作,规定化多了一个输入,但是其变换后的结果也更灵活。

直方图规定化的实现步骤如下:

  • 计算原图像的累积直方图
  • 计算规定直方图的累积直方图
  • 计算两累积直方图的差值的绝对值
  • 根据累积直方图最小差值建立灰度级的映射
灰度值 0 1 2 3 4 5 6 7
原图累计概率 0.19 0.44 0.65 0.81 0.89 0.95 0.98 1.00
目标图累计概率 0 0 0 0.15 0.35 0.65 0.85 1.00
映射差值最小 0->3 1->4 2->5 3->6 4->6 5->7 6->7 7->7
hist_normal.jpg

代码实现:

import cv2 as cv
import  numpy as np

def calcImage(data):
    # 1. 读取图片信息
    imgInfo = data.shape
    height = imgInfo[0]
    width = imgInfo[1]

    # 2. 计算原图片直方图
    hist = cv.calcHist([data],[0],None,[256],[0.0,255.0])
    # 3. 计算概率
    ratios = np.zeros(256,np.float)
    for i in range(256):
        ratios[i] = hist[i]/(height*width)

    # 4. 计算累计概率
    sumRatios = np.zeros(256,np.float)
    sum1 = 0.0
    for i in range(256):
        sum1 += ratios[i]

        sumRatios[i] = sum1
    return sumRatios


def color_map(srcRatios,referRatios):
    """颜色映射"""
    mapColors = np.zeros(256,np.float)

    for i in range(256):
        ratio = srcRatios[i]
        minVal = 1
        mapIndex = 0
        for j in range(256):
           referRatio = referRatios[j]
           diff = np.fabs(ratio - referRatio)          
            # 找到概率最接近的颜色值
            if minVal > diff:
                minVal=diff
                mapIndex=j

        # 保存但前映射 原图120--> 参考图 150
        mapColors[i] = mapIndex

    return mapColors

# 构建单一通道的颜色
def map_chanel(data,mapColors):
    imgInfo = data.shape
    height = imgInfo[0]
    width = imgInfo[1]

    singleChanel = np.zeros((height,width),np.uint8)
    for row in range(height):
        for col in range(width):
            # 从原图中获取原始颜色值
            color = data[row,col]
            # 从映射表获取映射之后的颜色值
            currentColor = mapColors[color]
            # 使用映射之后的颜色值,匹配原来的图片中的值
            singleChanel[row,col] = currentColor

    return singleChanel

def hist_match(src_img,refer_img):
    """完成两张图片的映射"""
    src_bgr = cv.split(src_img)
    refer_bgr = cv.split(refer_img)

    chanels = []
    for i in range(3):
        chanelData = src_bgr[i]
        referChanelData = refer_bgr[i]

        srcRatios = calcImage(chanelData)
        referRatios = calcImage(referChanelData)

        mapColors = color_map(srcRatios,referRatios)

        mapChanelData = map_chanel(chanelData,mapColors)
        chanels.append(mapChanelData)

    ret = cv.merge(chanels)

    return ret


src_img = cv.imread("assets/itheima.jpg",cv.IMREAD_COLOR);
refer_img = cv.imread("assets/timg2.jpg",cv.IMREAD_COLOR);

cv.imshow("src img",src_img)
cv.imshow("refer img",refer_img)

dst_img = hist_match(src_img,refer_img)

cv.imshow("dst img",dst_img)

cv.waitKey(0)
cv.destroyAllWindows()

图片卷积

图像滤波是尽量保留图像细节特征的条件下对目标图像的噪声进行抑制,是图像预处理中不可缺少的操作,其处理效果的好坏将直接影响到后续图像处理和分析的有效性和可靠性。

线性滤波是图像处理最基本的方法,它允许我们对图像进行处理,产生很多不同的效果。首先,我们需要一个二维的滤波器矩阵(卷积核)和一个要处理的二维图像。然后,对于图像的每一个像素点,计算它的邻域像素和滤波器矩阵的对应元素的乘积,然后加起来,作为该像素位置的值。这样就完成了滤波过程。

<img src="./img2/juanji.gif" width="200"/>

juanji.gif

对图像和滤波矩阵进行逐个元素相乘再求和的操作就相当于将一个二维的函数移动到另一个二维函数的所有位置,这个操作就叫卷积

卷积需要4个嵌套循环,所以它并不快,除非我们使用很小的卷积核。这里一般使用3x3或者5x5。而且,对于滤波器/卷积核,也有一定的规则要求:

  1. 滤波器的大小应该是奇数,这样它才有一个中心,例如3x3,5x5或者7x7。有中心了,也有了半径的称呼,例如5x5大小的核的半径就是2
  2. 滤波器矩阵所有的元素之和应该要等于1,这是为了保证滤波前后图像的亮度保持不变。当然了,这不是硬性要求了。
  3. 如果滤波器矩阵所有元素之和大于1,那么滤波后的图像就会比原图像更亮,反之,如果小于1,那么得到的图像就会变暗。如果和为0,图像不会变黑,但也会非常暗。
  4. 对于滤波后的结构,可能会出现负数或者大于255的数值。对这种情况,我们将他们直接截断到0和255之间即可。对于负数,也可以取绝对值。

均值滤波

将卷积核内的所有灰度值加起来,然后计算出平均值,用这个平均值填充卷积核正中间的值,这样做可以降低图像的噪声,同时也会导致图像变得模糊
G =1/9\left[\begin{matrix}1 & 1 & 1 \\1 & 1 & 1\\1 & 1 & 1\end{matrix}\right]

示例代码:

import cv2 as cv

img = cv.imread("./assets/itheima.jpg", cv.IMREAD_COLOR)
cv.imshow("src",img)

dst = cv.blur(img, (3,3))
cv.imshow("dst",dst)

cv.waitKey(0)
cv.destroyAllWindows()

高斯模糊

采用均值滤波降噪会导致图像模糊的非常厉害,有没有一种方式既能保留像素点真实值又能降低图片噪声呢?那就是加权平均的方式. 离中心点越近权值越高,越远权值越低.

但是权重的大小设置非常麻烦,那么有没有一种方式能够自动生成呢? 这个就是需要用到高斯函数

高斯函数呈现出的特征就是中间高,两边低的钟形

高斯模糊通常被用来减少图像噪声以及降低细节层次。
G =1/16\left[\begin{matrix}1 & 2 & 1 \\2 & 4 & 2\\1 & 2 & 1\end{matrix}\right]
<img src="./img2/gaussian.png" width="300" />

示例代码:

import cv2 as cv

# 回调函数
def updateSigma(val):
    # 高斯模糊                 参数1:图像  参数2:卷积核大小, 参数3:标准差越大,去除高斯噪声能力越强,图像越模糊
    gaussian_blur = cv.GaussianBlur(img, (5,5), val)
    cv.imshow("gaussian",gaussian_blur)

img = cv.imread("assets/itheima.jpg", cv.IMREAD_GRAYSCALE)
cv.imshow("src",img)
# 创建一个窗口
cv.namedWindow("gaussian",cv.WINDOW_AUTOSIZE)
# 创建一个窗口进度条: 参数1:名称 参数2:窗口名称  参数3: 起始值  参数4: 最大值, 参数5:回调函数
cv.createTrackbar("sigma","gaussian",0,255,updateSigma)

updateSigma(0)

cv.waitKey(0)
cv.destroyAllWindows()

中值滤波

对邻近的像素点进行灰度排序,然后取中间值,它能有效去除图像中的椒盐滤波

操作原理:
卷积域内的像素值从小到大排序
取中间值作为卷积输出

示例代码

import cv2 as cv
img = cv.imread("./assets/itheima_salt.jpg", cv.IMREAD_COLOR)
cv.imshow("src",img)

dst = cv.medianBlur(img, 3)
cv.imshow("dst",dst)

cv.waitKey(0)
cv.destroyAllWindows()

Sobel算子

Sobel算子是像素图像边缘检测中最重要的算子之一,在机器学习、数字媒体、计算机视觉等信息科技领域起着举足轻重的作用。在技术上,它是一个离散的一阶差分算子,用来计算图像亮度函数的一阶梯度之近似值。在图像的任何一点使用此算子,将会产生该点对应的梯度矢量

  • 水平梯度
    G_x = \left[ \begin{matrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{matrix} \right]

  • 垂直梯度

G_y= \left[ \begin{matrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{matrix} \right]

  • 合成

    G=\sqrt{G_x^2+G_y^2} 为了提高计算机效率我们通常会使用: G = |Gx|+|Gy|

这里我们使用sobel卷积算子来查看脑干图像

图片顺序: 原图---> x 方向sobel ----> y 方向sobel ----> xy合在一起

示例代码

import cv2 as cv

img = cv.imread("./assets/brain.jpg",cv.IMREAD_GRAYSCALE)
cv.imshow("src",img)

# sobel算子  参数1:图像, 参数2:图像的深度 -1表示和原图相同, 参数3: x方向求导的阶数 参数4: y方向求导的阶数
x_sobel = cv.Sobel(img, cv.CV_32F, 1, 0)
# 将图像转成8位int
x_sobel = cv.convertScaleAbs(x_sobel)

cv.imshow("x sobel",x_sobel)


# sobel算子
y_sobel = cv.Sobel(img, cv.CV_16S, 0, 1)
# 将图像转成8位int
y_sobel = cv.convertScaleAbs(y_sobel)
cv.imshow("y_sobel",y_sobel)


# 将x,y方向的内容叠加起来
x_y_sobel = cv.addWeighted(x_sobel, 0.5, y_sobel, 0.5,0)
cv.imshow("x,y sobel",x_y_sobel)

cv.waitKey(0)
cv.destroyAllWindows()

由于使用Sobel算子计算的时候有一些偏差, 所以opencv提供了sobel的升级版Scharr函数,计算比sobel更加精细.

下面是使用Scharr计算出来的边缘图像

scharr.jpg

示例代码

import cv2 as cv

img = cv.imread("./assets/brain.jpg",cv.IMREAD_GRAYSCALE)
cv.imshow("src",img)

# sobel算子
x_scharr = cv.Scharr(img, cv.CV_32F, 1, 0)
# 将图像转成8位int
x_scharr = cv.convertScaleAbs(x_scharr)
cv.imshow("x scharr",x_scharr)

# # sobel算子
y_scharr = cv.Scharr(img, cv.CV_16S, 0, 1)
# 将图像转成8位int
y_scharr = cv.convertScaleAbs(y_scharr)
cv.imshow("y scharr",y_scharr)


# 将x,y方向的内容叠加起来
xy_scharr = cv.addWeighted(x_scharr, 0.5, y_scharr, 0.5,0)
cv.imshow("x,y scharr",xy_scharr)

cv.waitKey(0)
cv.destroyAllWindows()

拉普拉斯算子

通过拉普拉斯变换后增强了图像中灰度突变处的对比度,使图像中小的细节部分得到增强,使图像的细节比原始图像更加清晰。

  • 普通

\left[ \begin{matrix} 0 & 1 & 0 \\ 1 & -4 & 0 \\ 0 & 1 & 0 \end{matrix} \right]

  • 增强型

\left[ \begin{matrix} 1 & 1 & 1 \\ 1 & -8 & 1 \\ 1 & 1 & 1 \end{matrix} \right]

import cv2 as cv

img = cv.imread("./assets/grbq.jpg",cv.IMREAD_GRAYSCALE)
cv.imshow("src",img)

# 使用拉普拉斯算子
dst = cv.Laplacian(img,cv.CV_32F)
# 取绝对值,将数据转到uint8类型
dst = cv.convertScaleAbs(dst)

cv.imshow("dst",dst)

cv.waitKey(0)
cv.destroyAllWindows();

canny边缘检测算法

Canny算法由John F.Canny于1986年开发,是很常用的边缘检测算法。

它是一种多阶段算法,内部过程共4个阶段:

  1. 噪声抑制(通过Gaussianblur高斯模糊降噪):使用5x5高斯滤波器去除图像中的噪声
  2. 查找边缘的强度及方向(通过Sobel滤波器)
  3. 应用非最大信号抑制(Non-maximum Suppression): 完成图像的全扫描以去除可能不构成边缘的任何不需要的像素
  4. 高低阈值分离出二值图像(Hysteresis Thresholding)
    • 体检的高低阈值比例为T2:T1 = 3:1 / 2:1
    • T2为高阈值,T1为低阈值


      canny-edge.png
tx08.jpg

示例代码

import cv2 as cv
import numpy as np
import random

# 将图片数据读取进来
img = cv.imread("img/itheima.jpg",cv.IMREAD_COLOR)
cv.imshow("img",img)

# 1. 将图片转成灰度图片
grayImg = cv.cvtColor(img, cv.COLOR_BGR2GRAY)

# 2. canny算法
dstImg = cv.Canny(grayImg,50,180)


# 显示效果图
cv.imshow('dstimg',dstImg)
cv.waitKey(0)

双边滤波

双边滤波其综合了高斯滤波器和α-截尾均值滤波器的特点,同时考虑了空间域与值域的差别,而Gaussian Filter和α均值滤波分别只考虑了空间域和值域差别。高斯滤波器只考虑像素间的欧式距离,其使用的模板系数随着和窗口中心的距离增大而减小;α-截尾均值滤波器则只考虑了像素灰度值之间的差值,去掉α%的最小值和最大值后再计算均值。

在opencv中,双边滤波的api已经提供好啦!

cv.bilateralFilter(输入图像, d, sigmaColor, sigmaSpace)

src: 输入图像 
d: 表示在过滤过程中每个像素邻域的直径范围。如果这个值是非正数,则函数会从sigmaSpace计算该值。 
sigmaColor: 颜色空间过滤器的sigma值,这个参数的值越大,表明该像素邻域内有越宽广的颜色会被混合到一起,产生较大的半相等颜色区域。
sigmaSpace: 坐标空间中滤波器的sigma值,如果该值较大,则意味着越远的像素将相互影响,从而使更大的区域中足够相似的颜色获取相同的颜色.

双边滤波器可以很好的保存图像边缘细节而滤除掉低频分量的噪音,但是双边滤波器的效率不是太高,花费的时间相较于其他滤波器而言也比较长。

<img src="./img/mh07.jpg" width="400"/>

import cv2 as cv
# 将图片数据读取进来
img = cv.imread("img/timg.jpg",cv.IMREAD_COLOR)
cv.imshow('img',img)
# 双边滤波
dstImg = cv.bilateralFilter(img, 10, 50, 50)
# 显示改变之后的图像
cv.imshow('newimg',dstImg)
cv.waitKey(0)

补充知识点

锐化滤波

sharpness.jpg

图像的锐化和边缘检测很像,首先找到边缘,然后把边缘加到原来的图像上面,这样就强化了图像的边缘,使图像看起来更加锐利了
G =\left[ \begin{matrix} -1 & -1 & -1 \\-1 & 9 & -1 \\-1 & -1 & -1 \end{matrix} \right]
实际上是计算当前点和周围点的差别,然后将这个差别加到原来的位置上。另外,中间点的权值要比所有的权值和大于1,意味着这个像素要保持原来的值。

我们还可以按照公式来创建锐化滤波核:
G =\left[\begin{matrix}-k & -k & -k \\-k & 2k+1 & -k\\-k & -k & -k\end{matrix}\right]

示例代码

import cv2 as cv
import numpy as np


img = cv.imread("./assets/hehua.jpg",cv.IMREAD_COLOR)
cv.imshow("src",img)

kernel = np.array([
                    [-1,-1,-1],
                    [-1,9,-1],
                    [-1,-1,-1]])

dst = cv.filter2D(img,-1,kernel)

cv.imshow("sharpness filter",dst)

cv.waitKey(0)
cv.destroyAllWindows()

霍夫变换

霍夫直线变换

霍夫直线变换(Hough Line Transform)用来做直线检测

霍夫直线变换官网文档

为了加升大家对霍夫直线的理解,我在左图左上角大了一个点,然后在右图中绘制出来经过这点可能的所有直线


houghline.jpg

绘制经过某点的所有直线的示例代码如下,这个代码可以直接拷贝运行

import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np

def draw_line():
    # 绘制一张黑图
    img = np.zeros((500, 500, 1), np.uint8)
    # 绘制一个点
    cv.line(img, (10, 10), (10, 10), (255), 1)
    cv.imshow("line",img)
    return img

def hough_lines(img):
    rho = 1;
    theta = np.pi/180
    threshold=0
    lines = cv.HoughLines(img,rho, theta, threshold)

    dst_img = img.copy()

    for line in lines[:,0]:
        rho,theta = line
        a = np.cos(theta)
        b = np.sin(theta)
        x = a*rho
        y=b*rho

        x1 = int(np.round(x + 1000*(-b)))
        y1 = int(np.round(y + 1000*a))

        x2 = int(np.round(x - 1000*(-b)))
        y2 = int(np.round(y - 1000*a))

        cv.line(dst_img,(x1,y1),(x2,y2),(255,0,0),1)

    cv.imshow("li",dst_img)

img = draw_line()
hough_lines(img)

cv.waitKey(0)
cv.destroyAllWindows()

寻找棋盘中的直线

示例代码如下:

import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np

# 1. 将图片以灰度的方式读取进来
img = cv.imread("assets/weiqi.jpg", cv.IMREAD_COLOR)
cv.imshow("src",img)

gray_img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# cv.imshow("gray",gray_img)
# 
flag,thresh_img = cv.threshold(gray_img,100,255,cv.THRESH_BINARY_INV)
cv.imshow("thresh_img",thresh_img)


# 3. 霍夫变换
#  线段以像素为单位的距离精度,double类型的,推荐用1.0
rho = 1
# 线段以弧度为单位的角度精度,推荐用numpy.pi/180
theta = np.pi/180
# 累加平面的阈值参数,int类型,超过设定阈值才被检测出线段,值越大,基本上意味着检出的线段越长,检出的线段个数越少。
threshold=10
# 线段以像素为单位的最小长度
min_line_length=25
# 同一方向上两条线段判定为一条线段的最大允许间隔(断裂),超过了设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段
max_line_gap = 3

lines = cv.HoughLinesP(thresh_img,rho,theta,threshold,minLineLength=min_line_length,maxLineGap=max_line_gap)

dst_img = img.copy()

for line in lines:
    x1,y1,x2,y2 = line[0]
    cv.line(dst_img,(x1,y1),(x2,y2),(0,0,255),2)

cv.imshow("dst img",dst_img)

cv.waitKey(0)
cv.destroyAllWindows()

霍夫圆

一个圆可以由以下公式表示 (x - x_0)^2 + (y - y_0)^2=r^2,其中(x_0, y_0)是圆心,r是半径。圆环需要3个参数来确定,所以进行圆环检测的累加器必须是三维的,这样效率就会很低,因此OpenCV使用了霍夫梯度法这个巧妙的方法,来使用边界的梯度信息,从而提升计算的效率。

使用步骤:

  1. 霍夫圆检测对噪声敏感,先对对象做中值滤波
  2. 检测边缘,先把可能的圆边缘过滤出来
  3. 根据边缘得到最大概率的圆心,进而得到最佳半径

OpenCV的Logo检测结果:

  • 参数及代码
def hough_circle(img):
    img_copy = img.copy()
    # 中值滤波降噪
    img_copy = cv2.GaussianBlur(img_copy, (3,3), 0)
    img_copy = cv2.medianBlur(img_copy, 5)
    # img_copy = cv2.pyrMeanShiftFiltering(img_copy, 10, 30)

    gray = cv2.cvtColor(img_copy, cv2.COLOR_BGR2GRAY)
    cv2.imshow("gray", gray)

    """
    @param 8-bit 单通道图片
    @param method 检测方法, 当前只有cv2.HOUGH_GRADIENT
    @param dp 累加器分辨率和图像分辨率的反比例, 例如:
        如果 dp=1 累加器和输入图像分辨率相同. 
        如果 dp=2 累加器宽高各为输入图像宽高的一半相同. 
    @param minDist 检测到圆的圆心之间的最小距离。
        如果参数太小,除了真实的一个之外,可能错误地检测到多个相邻的圆圈。
        如果参数太大,可能会遗漏一些圆
    @param param1 参数1。它是两个传递给Canny边缘检测器的较高阈值(较低的阈值是此值的一半)
    @param param2 参数2, 它是检测阶段圆心的累加器阈值。
        它越小,会检测到更多的假圆圈。较大累加器值对应的圆圈将首先返回。
    @param minRadius 最小圆半径.
    @param maxRadius 最大圆半径. 
        如果<=0, 会使用图片最大像素值
        如果< 0, 直接返回圆心, 不计算半径
    """
    # ../images/coins.jpg
    circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT,
                               dp = 1,
                               minDist = 50,
                               param1=160,
                               param2=50,
                               minRadius=0,
                               maxRadius=100)

    circles = np.uint16(np.around(circles))
    for i in circles[0, :]:
        # draw the outer circle
        cv2.circle(img_copy, (i[0], i[1]), i[2], (0, 255, 0), 2)
        # draw the center of the circle
        cv2.circle(img_copy, (i[0], i[1]), 2, (0, 0, 255), 3)

    cv2.imshow("detected circles", img_copy)

寻找棋盘中的棋子

houghcircle.jpg
import cv2 as cv
import numpy as np


img = cv.imread("assets/weiqi.jpg", cv.IMREAD_COLOR)
cv.imshow("src",img)
# 将图片转成灰色图片
gray_img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)

thresh_img = cv.adaptiveThreshold(gray_img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY_INV,11,5)


# 霍夫圆形检测
def hough_circle(gray_img):
    # 定义检测图像中圆的方法。目前唯一实现的方法是cv2.HOUGH_GRADIENT
    method = cv.HOUGH_GRADIENT
    # 累加器分辨率与图像分辨率的反比。例如,如果dp = 1,则累加器具有与输入图像相同的分辨率。如果dp = 2,则累加器的宽度和高度都是一半。
    dp = 1
    # 检测到的圆的圆心之间最小距离。如果minDist太小,则可能导致检测到多个相邻的圆。如果minDist太大,则可能导致很多圆检测不到。
    minDist = 20
    # param1 Canny算法阈值上线
    # param2 cv2.HOUGH_GRADIENT方法的累加器阈值。阈值越小,检测到的圈子越多。
    # minRadius : 最小的半径,如果不确定,则不指定
    # maxRadius : 最大的半径,若不确定,则不指定
    circles = cv.HoughCircles(gray_img,method,dp,minDist=minDist,param1=70,param2=30,minRadius=0,maxRadius=20)

    for circle in circles[0,:]:
        # 圆心坐标,半径
        x,y,r = circle
        # 绘制圆心
        cv.circle(img,(x,y),2,(0,255,0),1)
        # 绘制圆形
        cv.circle(img,(x,y),r,(0,0,255),2)

    cv.imshow("result",img)

# 调用函数,寻找霍夫圆
hough_circle(gray_img)

cv.waitKey(0)
cv.destroyAllWindows()

边缘与轮廓

  • 基于图像边缘提取或二值化的基础寻找对象轮廓
  • 边缘提取的阈值会最终影响轮廓发现的结果
  • 主要API有以下两个
    1. findContours发现轮廓
    2. drawContours绘制轮廓

查找轮廓

处理的图像,轮廓列表,继承关系 = cv.findContours(图像,轮廓检索模式,检索的方法)

# hierarchy[i][3],分别表示第i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号
  • 轮廓检索模式
RETR_EXTERNAL 只检测最外层轮廓
RETR_LIST 提取所有轮廓,并放置在list中,检测的轮廓不建立等级关系
RETR_CCOMP 提取所有轮廓,并将轮廓组织成双层结构(two-level hierarchy),顶层为连通域的外围边界,次层位内层边界
RETR_TREE 提取所有轮廓并重新建立网状轮廓结构
  • 轮廓检索算法
CHAIN_APPROX_NONE 获取每个轮廓的每个像素,相邻的两个点的像素位置差不超过1
CHAIN_APPROX_SIMPLE 压缩水平方向,垂直方向,对角线方向的元素,值保留该方向的重点坐标,如果一个矩形轮廓只需4个点来保存轮廓信息
CHAIN_APPROX_TC89_L1 Teh-Chinl链逼近算法
CHAIN_APPROX_TC89_KCOS Teh-Chinl链逼近算法

绘制轮廓

cv.drawContours(图像,轮廓列表,轮廓索引-1则绘制所有,轮廓颜色,轮廓的宽度)
contour01.jpg

绘制外切圆

((x,y),radius) = cv.minEnclosingCircle(contour)
tu02.jpg

实现步骤:

  1. 读取图片
  2. 将图片转成一张灰色图片
  3. 对图片进行二值化处理
  4. 使用findContours查找轮廓
  5. 对轮廓进行处理
import cv2 as cv

def read_rgb_img(img_name):
    rgb_img = cv.imread(img_name,cv.IMREAD_COLOR)
    cv.imshow("rgb img",rgb_img)
    return rgb_img

def convert_rgb2gray(img):
    gray_img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)

    cv.imshow("gray img", gray_img)
    return gray_img

def convert_gray2binary(img):
    binary_img = cv.adaptiveThreshold(img,
                                      255,
                                      cv.ADAPTIVE_THRESH_GAUSSIAN_C,
                                      cv.THRESH_BINARY,5,2)
    # _,binary_img = cv.threshold(img,50,255,cv.THRESH_BINARY_INV)
    cv.imshow("binary img", binary_img)
    return binary_img

def getContours(img):
    _,contours,hierarchy = cv.findContours(img,cv.RETR_TREE,cv.CHAIN_APPROX_SIMPLE)
    print(contours,hierarchy)
    return contours


def draw_contours(img,contours):
    index = -1 # 所有的轮廓
    thickness = 2 # 轮廓的宽度
    color = (255,125,125) # 轮廓的颜色
    cv.drawContours(img,contours,index,color,thickness)
    cv.imshow('draw contours',img)


if __name__ == '__main__':
    img_name = "assets/shape0.jpg"

    rgb_img = read_rgb_img(img_name)
    gray_img = convert_rgb2gray(rgb_img)
    binary_imgage = convert_gray2binary(gray_img)
    contours = getContours(binary_imgage)
    draw_contours(rgb_img,contours)

    cv.waitKey(0)
    cv.destroyAllWindows()

网球案例

tenis01.jpg

实现步骤:

  1. 读取图片
  2. 过滤出球的颜色
  3. 使用轮廓检测
  4. 找到球的中心点
  5. 展示信息
import cv2 as cv
import numpy as np

def read_rgb_img(img_name):
    rgb_img = cv.imread(img_name,cv.IMREAD_COLOR)
    cv.imshow("rgb img",rgb_img)
    return rgb_img


def convert_rgb2gray(img):
    gray_img = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
    # 采用高斯滤波去掉噪点
    gray_img = cv.GaussianBlur(gray_img,(5,5),0)
    cv.imshow("gray img", gray_img)

    return gray_img

def convert_gray2binary(img):
    binary_img = cv.adaptiveThreshold(img,255,cv.ADAPTIVE_THRESH_GAUSSIAN_C,cv.THRESH_BINARY_INV,5,2)
    cv.imshow("binary img", binary_img)
    return binary_img

def filter_tenis(img,lower_color,upper_color):
    hsv_img = cv.cvtColor(img,cv.COLOR_BGR2HSV)
    # 查找颜色
    mask_img = cv.inRange(hsv_img, lower_color, upper_color)
    cv.imshow("mask img",mask_img)
    return mask_img

def getContours(img):
    _,contours,hierarchy = cv.findContours(img,cv.RETR_EXTERNAL,cv.CHAIN_APPROX_SIMPLE)
    print(contours,hierarchy)
    return contours


def process_tenis_contours(rgb_img,contours):
    black_img = np.zeros([rgb_img.shape[0],rgb_img.shape[1],3],np.uint8)

    for c in contours:
        # 计算面积
        area = cv.contourArea(c)
        # 该函数计算曲线长度或闭合轮廓周长。
        perimeter = cv.arcLength(c,True)
        # 获取最小的外切圆
        ((x,y),radius) = cv.minEnclosingCircle(c)
        
        # 绘制轮廓
        cv.drawContours(rgb_img,[c],-1,(150,250,150),2)
        cv.drawContours(black_img,[c],-1,(150,250,150),2)
        # 获取轮廓中心点
        # cx,cy = get_contour_center(c)
        # print(cx,cy)
        x = int(x)
        y = int(y)
        cv.circle(rgb_img,(x,y),int(radius),(0,0,255),2)
        cv.circle(black_img,(x,y),int(radius),(0,0,255),2)

        print("Area:{},primeter:{}".format(area,perimeter))

    print("number of contours:{}".format(len(contours)))
    cv.imshow("rgb img contours",rgb_img)
    cv.imshow("black img contours",black_img)

if __name__ == '__main__':
    img_name = "assets/tenis1.jpg"
    # 定义范围
    lower_color = (30, 120, 130)
    upper_color = (60, 255, 255)

    rgb_img = read_rgb_img(img_name)
    binary_imgage = filter_tenis(rgb_img,lower_color,upper_color)

    contours = getContours(binary_imgage)
    process_tenis_contours(rgb_img,contours)

    cv.waitKey(0)
    cv.destroyAllWindows()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351