1、实战项目(找中线)

Android使用openCv抠图 opencv实现抠图_python

Android使用openCv抠图 opencv实现抠图_拟合_02

 目标是从面对这种简单环境用opencv画出中线,下面是过程实现

 

Android使用openCv抠图 opencv实现抠图_python_03

Android使用openCv抠图 opencv实现抠图_opencv_04

2、opencv-python基于HSV抠图

这种环境看似简单,但是用灰度处理二值化的效果奇差,最后选择用HSV进行分割。

先导入包:

import cv2
import cv2 as cv
import numpy as np

然后把图片用cvtColor转化成HSV格式

img=cv.imread("1.png")



img=cv.cvtColor(img,cv.COLOR_BGR2HSV)

关于HSV :


HSV模型中颜色的参数分别是:色调(H),饱和度(S),明度(V)色调H用角度度量,取值范围为0~360,从红色开始按逆时针方向计算,红色为0,绿色为120,蓝色为240,他们的补色是:黄色为60,青色为180,紫色为300 如果直接使用opencv中cvtColor函数,并设置参数为CV_BGR2HSV,那么所得的HSV范围分别是【0,180】【0,255】【0,255】


lower=np.array([0,0,137])
upper=np.array([120,255,255])


mask = cv2.inRange(img,lowerb=lower,upperb=upper)

 关于cv2.inRange方法


mask = inRange(hsv,lower_red,upper_red)hsv:原图lower_red指的是图像中低于这个lower_red的值,图像值变为0 upper_red指的是图像中高于这个upper_red的值,图像值变为0


阈值的选择

阈值的选择比较烦人,上面的数值是匹配第一张图片分割的。下面提供一个可视化工具,帮助你找到合适的阈值。

import cv2
import numpy as np


# 滑动条的回调函数,获取滑动条位置处的值
def empty(a):
    h_min = cv2.getTrackbarPos("Hue Min", "TrackBars")
    h_max = cv2.getTrackbarPos("Hue Max", "TrackBars")
    s_min = cv2.getTrackbarPos("Sat Min", "TrackBars")
    s_max = cv2.getTrackbarPos("Sat Max", "TrackBars")
    v_min = cv2.getTrackbarPos("Val Min", "TrackBars")
    v_max = cv2.getTrackbarPos("Val Max", "TrackBars")
    print(h_min, h_max, s_min, s_max, v_min, v_max)
    return h_min, h_max, s_min, s_max, v_min, v_max


path = '1.png'
# 创建一个窗口,放置6个滑动条
cv2.namedWindow("TrackBars")
cv2.resizeWindow("TrackBars", 640, 240)
cv2.createTrackbar("Hue Min", "TrackBars", 0, 120, empty)
cv2.createTrackbar("Hue Max", "TrackBars", 19, 120, empty)
cv2.createTrackbar("Sat Min", "TrackBars", 110, 255, empty)
cv2.createTrackbar("Sat Max", "TrackBars", 240, 255, empty)
cv2.createTrackbar("Val Min", "TrackBars", 153, 255, empty)
cv2.createTrackbar("Val Max", "TrackBars", 255, 255, empty)

while True:
    img = cv2.imread(path)
    imgHSV = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    # 调用回调函数,获取滑动条的值
    h_min, h_max, s_min, s_max, v_min, v_max = empty(0)
    lower = np.array([h_min, s_min, v_min])
    upper = np.array([h_max, s_max, v_max])
    # 获得指定颜色范围内的掩码
    mask = cv2.inRange(imgHSV, lower, upper)
    # 对原图图像进行按位与的操作,掩码区域保留
    imgResult = cv2.bitwise_and(img, img, mask=mask)

    cv2.imshow("Mask", mask)
    cv2.imshow("Result", imgResult)

    cv2.waitKey(1)



边界轮廓线拟合 :

contours_,h_ = cv.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
mask = cv2.cvtColor(mask,cv2.COLOR_GRAY2BGR)
cv2.drawContours(mask,contours_,-1,(0,255,0),3)

这里拟合出边界轮廓线并且直接画出来,线的厚度可以在drawContours的最后一个参数3的地方修改。这里contours_的数据类型如下,如果有相关操作注意:

Android使用openCv抠图 opencv实现抠图_轮廓线_05

 最后展示图片

cv2.imshow("result",mask)
cv2.waitKey(0)

轮廓拟合效果如下:

 

Android使用openCv抠图 opencv实现抠图_opencv_06

附上二值化效果:

Android使用openCv抠图 opencv实现抠图_Android使用openCv抠图_07

 3.拟合中线:

在网上找了多种直线拟合方法,如霍夫直线拟合骨架提取,opencv骨架提取,效果都不好,这里尝试用cv2.fitLine和cv2.line函数自己写一个中线提取方法。

middle_s=[]

for i in range(0,480):
    count = 0
    sum=0
    for j in range(0,1103):
        if contours_[1][j][0][1] == i:


            sum+=contours_[1][j][0][0]
            count+=1
    middle=sum/count
    print(count)
    middle_s.append([[middle,i]])



contours_middle=np.array(middle_s).astype(np.int32)

cv2.drawContours(mask,contours_middle,-1,(0,255,0),2)


output=cv2.fitLine(contours_middle, cv2.DIST_L2, 0, 0.01, 0.01)
k = output[1] / output[0]
b = output[3] - k * output[2]
x_0=-b/k
x_480=(479-b)/k
cv.line(mask,(int(x_0),0),(int(x_480),479),(255,0,0),4)
cv.line(img_c,(int(x_0),0),(int(x_480),479),(255,0,0),4)

cv.imshow("img_c",img_c)
cv.imshow("line",mask)
cv.imshow("mask",mask_c)
cv.waitKey(0)

代码中的img_c是上面代码copy的原图,mask是经过轮廓拟合的图片

关于fitLine及直线拟合算法:

fitLine的返回值output有四维,前面两个维度表示方向,类似方向向量两个相除就可以得到斜率k,后面两个维度是直线上一个点的横坐标和纵坐标。斜率k=output[1]/output[0]。有了斜率之后可以求得直线与上下边界线的焦点,直接用line()函数画出拟合的直线。

注意:

这里拟合的是直线,如果有拟合曲线中线需求的可以尝试使用两端轮廓线均值的方法,或者其他函数方法。这里的代码是仅仅适用于第一个图那种单直到的直线拟合,如果是复杂场景可以自己调整。

 完整代码如下:

#导入包
import cv2
import cv2 as cv
import numpy as np

#导入图片并备份
img=cv.imread("1.png")
img_c=img.copy()
#把图片转换成HSV颜色空间
img=cv.cvtColor(img,cv.COLOR_BGR2HSV)

#创建二值化阈值的上下界数组
lower=np.array([0,0,137])
upper=np.array([120,255,255])
#进行inRange()二值化
mask = cv2.inRange(img,lowerb=lower,upperb=upper)

mask_c=mask.copy()#备份图片


#拟合轮廓线,这种方法找的轮廓线不太干净,可以使用Canny会好一点,但是对于中线拟合影响不大
contours_,h_ = cv.findContours(mask,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
mask = cv2.cvtColor(mask,cv2.COLOR_GRAY2BGR)
cv2.drawContours(mask,contours_,-1,(0,255,0),3)
mask_line=mask.copy()#备份轮廓线图片

#创建中线点集
middle_s=[]

#利用循环将中线的点填充到点集中
for i in range(0,480):
    count = 0
    sum=0
    for j in range(0,1103):
        if contours_[1][j][0][1] == i:


            sum+=contours_[1][j][0][0]
            count+=1
    middle=sum/count
    print(count)
    middle_s.append([[middle,i]])


#转化成int32类型
contours_middle=np.array(middle_s).astype(np.int32)

#这里可以直接把中线上的点打印出来就可以得到中线,但是可能有些噪点
#所以选择用直线拟合,在drawContours()下面直接输出mask就可以看到
cv2.drawContours(mask,contours_middle,-1,(0,255,0),2)

#下面把点集放大fitLine()中拟合出直线
output=cv2.fitLine(contours_middle, cv2.DIST_L2, 0, 0.01, 0.01)
k = output[1] / output[0]
b = output[3] - k * output[2]
x_0=-b/k
x_480=(479-b)/k
cv.line(mask,(int(x_0),0),(int(x_480),479),(255,0,0),4)
cv.line(img_c,(int(x_0),0),(int(x_480),479),(255,0,0),4)

#展示图片
cv.imshow("mask_line",mask_line)#二值化后拟合轮廓图
cv.imshow("img_c",img_c)#原图画上中线
cv.imshow("line",mask)#二值化,轮廓线和中线
cv.imshow("mask",mask_c)#二值化图像
cv.waitKey(0)

结果图片:

Android使用openCv抠图 opencv实现抠图_python_08

Android使用openCv抠图 opencv实现抠图_Android使用openCv抠图_09