基于图像的裂缝分割与裂缝宽度计算(正交骨架线法)

基于C++、GDAL、OpenCV的矢量数据骨架线提取算法 CGAL已经实现了该功能,但由于CGAL依赖于Boost库,编译后过大,因此本文所采用的这套方式实现骨架线提取功能。2、将所有拆分后的shp分别转栅格,利用OpenCV提取骨架线。1、将导入shp按照要素逐一拆分成新的shp。3、将所有骨架线转为shp,并合并输出。 阅读详情
AI摘要

本文详细介绍了如何利用OpenCV、深度学习方法分割裂缝图像,通过中轴变换和正交骨架线法计算裂缝宽度,包括法向量估计、边缘提取和宽度计算步骤。关键点包括边缘检测、裂缝骨架线生成和宽度测量策略的改进版算法。

基于图像的裂缝分割与裂缝宽度计算(正交骨架线法)

From https://subce.gitee.io/htmls/essays/crack_width_calculation.html

分割

可以使用 opencv 的阈值分割,或者使用 CNN 等深度学习的方法进行裂缝分割,一般得到的分割结果如下,这里不再赘述。

在这里插入图片描述

寻找边缘

寻找边缘有两种方法,如下

在这里插入图片描述

中轴变换

为了计算裂缝的宽度,一般使用正交骨架线法,所以还需要计算裂缝的骨架线,这里一般使用中轴变换,同样可以使用 scikit-image 库中的算法,参见:Skeletonize — skimage v0.18.0 docs (scikit-image.org),官方示例:

在这里插入图片描述

宽度计算

下面是最重要的一部分,主要包括法向量估计和宽度计算,主要参见下面的两个函数 estimate_normal_for_pos() 和 get_crack_ctrlpts()

  • 法向量估计

    这里主要是查找给定点的 K 近邻点,可以使用 kd-tree 算法,然后使用 奇异值分解 (SVD)计算骨架线的法向量。然后,裂缝的宽度计算就是在这个方向量的方向上计算的。

在这里插入图片描述

  • 宽度计算

    这里的主要思想是: 对弈给定的一个位置,根据上方,估计此位置骨架线的方向量,并将此作为局部坐标系的y轴方向,x轴方向与此垂直,然后把裂缝的边缘线变换到这个局部坐标系中,然后在局部坐标系中,查找到骨架线法向量最近的两个裂缝边缘线点,并计算这两个点所形成的线段与骨架线法向量的交点。对于左右侧的两个裂缝边缘线,分别计算。(这一部分具体可参见代码实现,后续会补充具体的图解)

在这里插入图片描述

  • 代码解释

如上图所示,两条圆点线为边缘线,叉号线为骨架线,也即中心线,中心线上任意一点的法向量可以通过对邻域点集拟合直线得到。对于Condition1,中心线上一点 c c c, 根据 c c c 点的法向量建立局部坐标系,如上所示,根据 c c c 点的局部坐标系,可将裂缝边缘线上的点分为四个象限,为了找到每一个象限内离 y y y 轴最近的点,定义参数hband(见下面代码),然后只保留点到y轴的距离小于hband的点,也就是上图中三条虚线内的点,即保留中心点 c c c 两侧一定带宽内的点,然后vband是为了规定边缘线的粗细的参数,当发现 c c c 上方或者下方(两条边缘线)任意一侧点集的 y y y 坐标的极差(np.ptp)大于vband,说明这时候检测到的边缘线存在噪点,因为噪点为导致边缘线在 y y y 方向的极差变大,如图中Condition2中所标识处的噪点,噪点在vband带宽外,需要进一步过滤,过滤的方法是对存在噪点的地方,计算 y y y 坐标的均值,然后保留 y y y 坐标大于均值的点。通过hband,vband连个参数将中心点 c c c 处的边缘线分为4块区域,即图中的 b l t , b l b , b r t , b r b blt,blb,brt,brb blt,blb,brt,brb,最后,再在4块去区域中分别计算到 y y y 轴最近的点,即可以从来计算交点啦。

注:使用hband,vband主要是将c点附近的边缘线先初步框定出来。

代码

import numpy as np
from skimage import io
from skimage.morphology import medial_axis, skeletonize
from skimage import measure
from skimage import data
import matplotlib.pyplot as plt
from sklearn.neighbors import KDTree


def show_2dpoints(pointcluster,s=None,quivers=None,qscale=1):
    # pointcluster should be a list of numpy ndarray
    # This functions would show a list of pint cloud in different colors
    n = len(pointcluster)
    nmax = n
    if quivers is not None:
        nq = len(quivers)
        nmax = max(n,nq)
    
    colors = ['r','g','b','c','m','y','k','tomato','gold']
    if nmax < 10:
        colors = np.array(colors[0:nmax])
    else: 
        colors = np.random.rand(nmax,3)

    fig = plt.figure(num=1)
    ax = fig.add_subplot(1,1,1)

    if s is None:
        s = np.ones(n)*2

    for i in range(n):
        ax.scatter(pointcluster[i][:,0],pointcluster[i][:,1],s=s[i],c=[colors[i]],alpha=0.6)

    if quivers is not None:
        for i in range(nq):
            ax.quiver(quivers[i][:,0],quivers[i][:,1],quivers[i][:,2],quivers[i][:,3],color=[colors[i]],scale=qscale)

    plt.show()


def SVD(points):
    # 二维,三维均适用
    # 二维直线,三维平面
    pts = points.copy()
    # 奇异值分解
    c = np.mean(pts, axis=0)
    A = pts - c # shift the points
    A = A.T #3*n
    u, s, vh = np.linalg.svd(A, full_matrices=False, compute_uv=True) # A=u*s*vh
    normal = u[:,-1]

    # 法向量归一化
    nlen = np.sqrt(np.dot(normal,normal))
    normal = normal / nlen
    # normal 是主方向的方向向量 与PCA最小特征值对应的特征向量是垂直关系
    # u 每一列是一个方向
    # s 是对应的特征值
    # c >>> 点的中心
    # normal >>> 拟合的方向向量
    return u,s,c,normal


def calcu_dis_from_ctrlpts(ctrlpts):
    if ctrlpts.shape[1]==4:
        return np.sqrt(np.sum((ctrlpts[:,0:2]-ctrlpts[:,2:4])**2,axis=1))
    else:
        return np.sqrt(np.sum((ctrlpts[:,[0,2]]-ctrlpts[:,[3,5]])**2,axis=1))


def estimate_normal_for_pos(pos,points,n):
    # estimate normal vectors at a given point
    pts = np.copy(points)
    tree = KDTree(pts, leaf_size=2)
    idx = tree.query(pos, k=n, return_distance=False, dualtree=False, breadth_first=False)
    #pts = np.concatenate((np.concatenate((pts[0].reshape(1,-1),pts),axis=0),pts[-1].reshape(1,-1)),axis=0)
    normals = []
    for i in range(0,pos.shape[0]):
        pts_for_normals = pts[idx[i,:],:]
        _,_,_,normal = SVD(pts_for_normals)
        normals.append(normal)
    normals = np.array(normals)
    return normals


def estimate_normals(points,n):
    pts = np.copy(points)
    tree = KDTree(pts, leaf_size=2)
    idx = tree.query(pts, k=n, return_distance=False, dualtree=False, breadth_first=False)
    #pts = np.concatenate((np.concatenate((pts[0].reshape(1,-1),pts),axis=0),pts[-1].reshape(1,-1)),axis=0)
    normals = []
    for i in range(0,pts.shape[0]):
        pts_for_normals = pts[idx[i,:],:]
        _,_,_,normal = SVD(pts_for_normals)
        normals.append(normal)
    normals = np.array(normals)
    return normals


def get_crack_ctrlpts(centers,normals,bpoints,hband=5,vband=2):
    # main algorithm to obtain crack width
    cpoints = np.copy(centers)
    cnormals = np.copy(normals)

    xmatrix = np.array([[0,1],[-1,0]])
    cnormalsx = np.dot(xmatrix,cnormals.T).T # the normal of x axis
    N = cpoints.shape[0]

    interp_segm = []
    widths = []
    for i in range(N):
        try:
            ny = cnormals[i]
            nx = cnormalsx[i]
            tform = np.array([nx,ny])
            bpoints_loc = np.dot(tform,bpoints.T).T
            cpoints_loc = np.dot(tform,cpoints.T).T
            ci = cpoints_loc[i]

            bl_ind = (bpoints_loc[:,0]-(ci[0]-hband))*(bpoints_loc[:,0]-ci[0])<0
            br_ind = (bpoints_loc[:,0]-ci[0])*(bpoints_loc[:,0]-(ci[0]+hband))<=0
            bl = bpoints_loc[bl_ind] # left points
            br = bpoints_loc[br_ind] # right points

            blt = bl[bl[:,1]>np.mean(bl[:,1])]
            if np.ptp(blt[:,1])>vband:
                blt = blt[blt[:,1]>np.mean(blt[:,1])]

            blb = bl[bl[:,1]<np.mean(bl[:,1])]
            if np.ptp(blb[:,1])>vband:
                blb = blb[blb[:,1]<np.mean(blb[:,1])]

            brt = br[br[:,1]>np.mean(br[:,1])]
            if np.ptp(brt[:,1])>vband:
                brt = brt[brt[:,1]>np.mean(brt[:,1])]

            brb = br[br[:,1]<np.mean(br[:,1])]
            if np.ptp(brb[:,1])>vband:
                brb = brb[brb[:,1]<np.mean(brb[:,1])]

            #bh = np.vstack((bl,br))
            #bmax = np.max(bh[:,1])
            #bmin = np.min(bh[:,1])

            #blt = bl[bl[:,1]>bmax-vband] # left top points
            #blb = bl[bl[:,1]<bmin+vband] # left bottom points

            #brt = br[br[:,1]>bmax-vband] # right top points
            #brb = br[br[:,1]<bmin+vband] # right bottom points


            t1 = blt[np.argsort(blt[:,0])[-1]]
            t2 = brt[np.argsort(brt[:,0])[0]]

            b1 = blb[np.argsort(blb[:,0])[-1]]
            b2 = brb[np.argsort(brb[:,0])[0]]


            interp1 = (ci[0]-t1[0])*((t2[1]-t1[1])/(t2[0]-t1[0]))+t1[1]
            interp2 = (ci[0]-b1[0])*((b2[1]-b1[1])/(b2[0]-b1[0]))+b1[1]

            if interp1-ci[1]>0 and interp2-ci[1]<0:
                widths.append([i,interp1-ci[1],interp2-ci[1]])

                interps = np.array([[ci[0],interp1],[ci[0],interp2]])

                interps_rec = np.dot(np.linalg.inv(tform),interps.T).T

                #show_2dpoints([bpointsxl_loc1,bpointsxl_loc2,bpointsxr_loc1,bpointsxr_loc2,np.array([ptsl_1,ptsl_2]),np.array([ptsr_1,ptsr_2]),interps,ci.reshape(1,-1)],s=[1,1,1,1,20,20,20,20])
                interps_rec = interps_rec.reshape(1,-1)[0,:]
                interp_segm.append(interps_rec)
        except:
            print("the %d-th was wrong" % i)
            continue
    interp_segm = np.array(interp_segm)
    widths = np.array(widths)
    # check
    # show_2dpoints([np.array([[ci[0],interp1],[ci[0],interp2]]),np.array([t1,t2,b1,b2]),cpoints_loc,bl,br],[10,20,15,2,2])
    return interp_segm, widths


path = "E:/Users/SubChange/Desktop/"


image = io.imread(path+"7Q3A9060-18.png", as_gray=True)
iw,ih = image.shape

blobs  = np.copy(image)
blobs[blobs<128] = 0
blobs[blobs>128] = 1

blobs = blobs.astype(np.uint8)
# Generate the data
#blobs = data.binary_blobs(200, blob_size_fraction=.2,
                          #volume_fraction=.35, seed=1)
# using scikit-image
## Compute the medial axis (skeleton) and the distance transform
#skel, distance = medial_axis(blobs, return_distance=True)
## Distance to the background for pixels of the skeleton
#dist_on_skel = distance * skel

# Compare with other skeletonization algorithms
skeleton = skeletonize(blobs)
#skeleton_lee = skeletonize(blobs, method='lee')
x, y = np.where(skeleton>0)
centers = np.hstack((x.reshape(-1,1),y.reshape(-1,1)))
normals = estimate_normals(centers,3)

# search contours of the crack
contours = measure.find_contours(blobs, 0.8)

bl = contours[0]
br = contours[1]

bpoints = np.vstack((bl,br))

#interp_segm, widths = get_crack_ctrlpts(centers,normals,bpoints,hband=2,vband=2)


bpixel = np.zeros((iw,ih,3),dtype=np.uint8)
bpoints = bpoints.astype(np.int)
bpixel[bpoints[:,0],bpoints[:,1],0] = 255

skeleton_pixel = np.zeros((iw,ih,3),dtype=np.uint8)
skeleton_pixel[skeleton,1] = 255

bpixel_and_skeleton = np.copy(bpixel)
bpixel_and_skeleton[skeleton,1] = 255


fig, axes = plt.subplots(1,3, figsize=(8, 8))
ax = axes.ravel()

ax[0].imshow(blobs, cmap=plt.cm.gray)
ax[0].axis('off')


ax[1].imshow(bpixel_and_skeleton)
#for contour in contours:
#    ax[1].plot(contour[:, 1], contour[:, 0], linewidth=2)

#for i in range(interp_segm.shape[0]):
#    ax[1].plot([interp_segm[i,1],interp_segm[i,3]],[interp_segm[i,0],interp_segm[i,2]],'-b')

#ax[1].set_title('medial_axis')
ax[1].axis('off')


# ================ small window ==================
pos = np.array([191, 291]).reshape(1,-1) # input (x,y) where need to calculate crack width
# pos = np.array([142, 178]).reshape(1,-1)

posn = estimate_normal_for_pos(pos,centers,3)

interps, widths2 = get_crack_ctrlpts(pos,posn,bpoints,hband=1.5,vband=2)


sx = pos[0,0] - 20
sy = pos[0,1] - 20

ax[2].imshow(bpixel_and_skeleton)

for i in range(interps.shape[0]):
    ax[2].plot([interps[i,1],interps[i,3]],[interps[i,0],interps[i,2]],c='c',ls='-',lw=5,marker='o',ms=8,mec='c',mfc='c')

ax[2].set_ylim(sx,sx+40)
ax[2].set_xlim(sy,sy+40)

#ax[2].set_title('skeletonize')
ax[2].axis('off')

print(interps)

fig.tight_layout()

plt.show()

结果

在这里插入图片描述

裂缝边缘线和骨架线

在这里插入图片描述

宽度计算结果

另外,附上另外一张图片,是整条裂缝的宽度计算结果:
在这里插入图片描述

更新

针对裂缝形状比较复杂,即不是单条裂缝,图像中有多条裂缝,且裂缝的宽度在一定的范围的,可以做如下改进:

针对裂缝坐标下的四个象限的裂缝边缘线的点,设置一个预估计的裂缝宽度,并在四个象限中,删除宽度超出预估裂缝宽度的边缘像素点,这样便能够筛选离中轴线最近的边缘线了,具体看下面代码,即添加 est_width参数。

def get_crack_ctrlpts(centers,normals,bpoints,hband=5,vband=2,est_width=0):
    # main algorithm to obtain crack width
    cpoints = np.copy(centers)
    cnormals = np.copy(normals)

    xmatrix = np.array([[0,1],[-1,0]])
    cnormalsx = np.dot(xmatrix,cnormals.T).T # the normal of x axis
    N = cpoints.shape[0]

    interp_segm = []
    widths = []
    for i in range(N):
        try:
            ny = cnormals[i]
            nx = cnormalsx[i]
            tform = np.array([nx,ny])
            bpoints_loc = np.dot(tform,bpoints.T).T
            cpoints_loc = np.dot(tform,cpoints.T).T
            ci = cpoints_loc[i]

            bl_ind = (bpoints_loc[:,0]-(ci[0]-hband))*(bpoints_loc[:,0]-ci[0])<0
            br_ind = (bpoints_loc[:,0]-ci[0])*(bpoints_loc[:,0]-(ci[0]+hband))<=0
            bl = bpoints_loc[bl_ind] # left points
            br = bpoints_loc[br_ind] # right points

            if est_width>0:
                # 下面的数值 est_width 是预估计的裂缝宽度
                half_est_width = est_width / 2
                blt = bl[(bl[:,1]-(ci[1]+half_est_width))*(bl[:,1]-ci[1])<0]
                blb = bl[(bl[:,1]-(ci[1]-half_est_width))*(bl[:,1]-ci[1])<0]
                brt = br[(br[:,1]-(ci[1]+half_est_width))*(br[:,1]-ci[1])<0]
                brb = br[(br[:,1]-(ci[1]-half_est_width))*(br[:,1]-ci[1])<0]
            else:
                blt = bl[bl[:,1]>np.mean(bl[:,1])]
                if np.ptp(blt[:,1])>vband:
                    blt = blt[blt[:,1]>np.mean(blt[:,1])]

                blb = bl[bl[:,1]<np.mean(bl[:,1])]
                if np.ptp(blb[:,1])>vband:
                    blb = blb[blb[:,1]<np.mean(blb[:,1])]

                brt = br[br[:,1]>np.mean(br[:,1])]
                if np.ptp(brt[:,1])>vband:
                    brt = brt[brt[:,1]>np.mean(brt[:,1])]

                brb = br[br[:,1]<np.mean(br[:,1])]
                if np.ptp(brb[:,1])>vband:
                    brb = brb[brb[:,1]<np.mean(brb[:,1])]

                # blt = bl[bl[:,1]>np.mean(bl[:,1])]
                # if np.ptp(blt[:,1])>vband:
                #    blt = blt[blt[:,1]<ci[1]+50]
                #    #blt = blt[blt[:,1]>np.mean(blt[:,1])] (外侧)
                #    #blt = blt[blt[:,1]<(np.max(blt[:,1])-0.5*np.ptp(blt[:,1]))] (内侧)

                # blb = bl[bl[:,1]<np.mean(bl[:,1])]
                # if np.ptp(blb[:,1])>vband:
                #    blb = blb[blb[:,1]>ci[1]-50]
                #    #blb = blb[blb[:,1]<np.mean(blb[:,1])]
                #    #blb = blb[blb[:,1]>(np.min(blb[:,1])+0.5*np.ptp(blb[:,1]))]

                # brt = br[br[:,1]>np.mean(br[:,1])]
                # if np.ptp(brt[:,1])>vband:
                #    brt = brt[brt[:,1]<ci[1]+50]
                #    #brt = brt[brt[:,1]>np.mean(brt[:,1])]
                #    #brt = brt[brt[:,1]<(np.max(brt[:,1])-0.5*np.ptp(brt[:,1]))]

                # brb = br[br[:,1]<np.mean(br[:,1])]
                # if np.ptp(brb[:,1])>vband:
                #    brb = brb[brb[:,1]>ci[1]-50]
                #    # brb = brb[brb[:,1]<np.mean(brb[:,1])]
                #    # brb = brb[brb[:,1]>(np.min(brb[:,1])+0.5*np.ptp(brb[:,1]))]

            #bh = np.vstack((bl,br))
            #bmax = np.max(bh[:,1])
            #bmin = np.min(bh[:,1])

            #blt = bl[bl[:,1]>bmax-vband] # left top points
            #blb = bl[bl[:,1]<bmin+vband] # left bottom points

            #brt = br[br[:,1]>bmax-vband] # right top points
            #brb = br[br[:,1]<bmin+vband] # right bottom points


            t1 = blt[np.argsort(blt[:,0])[-1]]
            t2 = brt[np.argsort(brt[:,0])[0]]

            b1 = blb[np.argsort(blb[:,0])[-1]]
            b2 = brb[np.argsort(brb[:,0])[0]]


            interp1 = (ci[0]-t1[0])*((t2[1]-t1[1])/(t2[0]-t1[0]))+t1[1]
            interp2 = (ci[0]-b1[0])*((b2[1]-b1[1])/(b2[0]-b1[0]))+b1[1]

            if interp1-ci[1]>0 and interp2-ci[1]<0:
                widths.append([i,interp1-ci[1],interp2-ci[1]])

                interps = np.array([[ci[0],interp1],[ci[0],interp2]])

                interps_rec = np.dot(np.linalg.inv(tform),interps.T).T

                #show_2dpoints([bpointsxl_loc1,bpointsxl_loc2,bpointsxr_loc1,bpointsxr_loc2,np.array([ptsl_1,ptsl_2]),np.array([ptsr_1,ptsr_2]),interps,ci.reshape(1,-1)],s=[1,1,1,1,20,20,20,20])
                interps_rec = interps_rec.reshape(1,-1)[0,:]
                interp_segm.append(interps_rec)
        except:
            print("the %d-th was wrong" % i)
            continue
    interp_segm = np.array(interp_segm)
    widths = np.array(widths)
    # check
    # show_2dpoints([np.array([[ci[0],interp1],[ci[0],interp2]]),np.array([t1,t2,b1,b2]),cpoints_loc,bl,br],[10,20,15,2,2])
    return interp_segm, widths

具体效果如下(注意骨架线和边缘线的提取与传参问题):
在这里插入图片描述


鉴于很多人询问怎么测量整条裂缝的宽度,这里我将代码进行了略微的调整,仍然是针对于最先给出的裂缝,可以直接运行的代码和结果如下,大家可以尝试看看效果,应该是可以直接跑的通的,请注意更换裂缝图像路径,跑通后大家再仔细看看里面是怎么调整的。个人水平有限,代码写的有点乱,请见谅,如果还不能跑通,可私信沟通

import numpy as np
from skimage import io
from skimage.morphology import medial_axis, skeletonize
from skimage import measure
from skimage import data
import matplotlib.pyplot as plt
from sklearn.neighbors import KDTree


def show_2dpoints(pointcluster,s=None,quivers=None,qscale=1):
    # pointcluster should be a list of numpy ndarray
    # This functions would show a list of pint cloud in different colors
    n = len(pointcluster)
    nmax = n
    if quivers is not None:
        nq = len(quivers)
        nmax = max(n,nq)
    
    colors = ['r','g','b','c','m','y','k','tomato','gold']
    if nmax < 10:
        colors = np.array(colors[0:nmax])
    else: 
        colors = np.random.rand(nmax,3)

    fig = plt.figure(num=1)
    ax = fig.add_subplot(1,1,1)

    if s is None:
        s = np.ones(n)*2

    for i in range(n):
        ax.scatter(pointcluster[i][:,0],pointcluster[i][:,1],s=s[i],c=[colors[i]],alpha=0.6)

    if quivers is not None:
        for i in range(nq):
            ax.quiver(quivers[i][:,0],quivers[i][:,1],quivers[i][:,2],quivers[i][:,3],color=[colors[i]],scale=qscale)

    plt.show()


def SVD(points):
    # 二维,三维均适用
    # 二维直线,三维平面
    pts = points.copy()
    # 奇异值分解
    c = np.mean(pts, axis=0)
    A = pts - c # shift the points
    A = A.T #3*n
    u, s, vh = np.linalg.svd(A, full_matrices=False, compute_uv=True) # A=u*s*vh
    normal = u[:,-1]

    # 法向量归一化
    nlen = np.sqrt(np.dot(normal,normal))
    normal = normal / nlen
    # normal 是主方向的方向向量 与PCA最小特征值对应的特征向量是垂直关系
    # u 每一列是一个方向
    # s 是对应的特征值
    # c >>> 点的中心
    # normal >>> 拟合的方向向量
    return u,s,c,normal


def calcu_dis_from_ctrlpts(ctrlpts):
    if ctrlpts.shape[1]==4:
        return np.sqrt(np.sum((ctrlpts[:,0:2]-ctrlpts[:,2:4])**2,axis=1))
    else:
        return np.sqrt(np.sum((ctrlpts[:,[0,2]]-ctrlpts[:,[3,5]])**2,axis=1))


def estimate_normal_for_pos(pos,points,n):
    """
    计算pos处的法向量.
    
    Input:
    ------
    pos: nx2 ndarray 需要计算法向量的位置.
    points: 骨架线的点集
    n: 用到的近邻点的个数
    
    Output:
    ------
    normals: nx2 ndarray 在pos位置处的法向量.
    """
    
    # estimate normal vectors at a given point
    pts = np.copy(points)
    tree = KDTree(pts, leaf_size=2)
    idx = tree.query(pos, k=n, return_distance=False, dualtree=False, breadth_first=False)
    #pts = np.concatenate((np.concatenate((pts[0].reshape(1,-1),pts),axis=0),pts[-1].reshape(1,-1)),axis=0)
    normals = []
    for i in range(0,pos.shape[0]):
        pts_for_normals = pts[idx[i,:],:]
        _,_,_,normal = SVD(pts_for_normals)
        normals.append(normal)
    normals = np.array(normals)
    return normals


def estimate_normals(points,n):
    """
    计算points表示的曲线上的每一个点法向量.
    等同于 estimate_normal_for_pos(points,points,n)

    Input:
    ------
    points: nx2 ndarray 曲线点集.
    n: 用到的近邻点的个数
    
    Output:
    ------
    normals: nx2 ndarray 在points曲线上的每一处的法向量.
    """
    
    pts = np.copy(points)
    tree = KDTree(pts, leaf_size=2)
    idx = tree.query(pts, k=n, return_distance=False, dualtree=False, breadth_first=False)
    #pts = np.concatenate((np.concatenate((pts[0].reshape(1,-1),pts),axis=0),pts[-1].reshape(1,-1)),axis=0)
    normals = []
    for i in range(0,pts.shape[0]):
        pts_for_normals = pts[idx[i,:],:]
        _,_,_,normal = SVD(pts_for_normals)
        normals.append(normal)
    normals = np.array(normals)
    return normals

def get_crack_ctrlpts(centers,normals,bpoints,hband=5,vband=2,est_width=0):
    # main algorithm to obtain crack width
    cpoints = np.copy(centers)
    cnormals = np.copy(normals)

    xmatrix = np.array([[0,1],[-1,0]])
    cnormalsx = np.dot(xmatrix,cnormals.T).T # the normal of x axis
    N = cpoints.shape[0]

    interp_segm = []
    widths = []
    for i in range(N):
        try:
            ny = cnormals[i]
            nx = cnormalsx[i]
            tform = np.array([nx,ny])
            bpoints_loc = np.dot(tform,bpoints.T).T
            cpoints_loc = np.dot(tform,cpoints.T).T
            ci = cpoints_loc[i]

            bl_ind = (bpoints_loc[:,0]-(ci[0]-hband))*(bpoints_loc[:,0]-ci[0])<0
            br_ind = (bpoints_loc[:,0]-ci[0])*(bpoints_loc[:,0]-(ci[0]+hband))<=0
            bl = bpoints_loc[bl_ind] # left points
            br = bpoints_loc[br_ind] # right points

            if est_width>0:
                # 下面的数值 est_width 是预估计的裂缝宽度
                half_est_width = est_width / 2
                blt = bl[(bl[:,1]-(ci[1]+half_est_width))*(bl[:,1]-ci[1])<0]
                blb = bl[(bl[:,1]-(ci[1]-half_est_width))*(bl[:,1]-ci[1])<0]
                brt = br[(br[:,1]-(ci[1]+half_est_width))*(br[:,1]-ci[1])<0]
                brb = br[(br[:,1]-(ci[1]-half_est_width))*(br[:,1]-ci[1])<0]
            else:
                blt = bl[bl[:,1]>np.mean(bl[:,1])]
                if np.ptp(blt[:,1])>vband:
                    blt = blt[blt[:,1]>np.mean(blt[:,1])]

                blb = bl[bl[:,1]<np.mean(bl[:,1])]
                if np.ptp(blb[:,1])>vband:
                    blb = blb[blb[:,1]<np.mean(blb[:,1])]

                brt = br[br[:,1]>np.mean(br[:,1])]
                if np.ptp(brt[:,1])>vband:
                    brt = brt[brt[:,1]>np.mean(brt[:,1])]

                brb = br[br[:,1]<np.mean(br[:,1])]
                if np.ptp(brb[:,1])>vband:
                    brb = brb[brb[:,1]<np.mean(brb[:,1])]

                # blt = bl[bl[:,1]>np.mean(bl[:,1])]
                # if np.ptp(blt[:,1])>vband:
                #    blt = blt[blt[:,1]<ci[1]+50]
                #    #blt = blt[blt[:,1]>np.mean(blt[:,1])] (外侧)
                #    #blt = blt[blt[:,1]<(np.max(blt[:,1])-0.5*np.ptp(blt[:,1]))] (内侧)

                # blb = bl[bl[:,1]<np.mean(bl[:,1])]
                # if np.ptp(blb[:,1])>vband:
                #    blb = blb[blb[:,1]>ci[1]-50]
                #    #blb = blb[blb[:,1]<np.mean(blb[:,1])]
                #    #blb = blb[blb[:,1]>(np.min(blb[:,1])+0.5*np.ptp(blb[:,1]))]

                # brt = br[br[:,1]>np.mean(br[:,1])]
                # if np.ptp(brt[:,1])>vband:
                #    brt = brt[brt[:,1]<ci[1]+50]
                #    #brt = brt[brt[:,1]>np.mean(brt[:,1])]
                #    #brt = brt[brt[:,1]<(np.max(brt[:,1])-0.5*np.ptp(brt[:,1]))]

                # brb = br[br[:,1]<np.mean(br[:,1])]
                # if np.ptp(brb[:,1])>vband:
                #    brb = brb[brb[:,1]>ci[1]-50]
                #    # brb = brb[brb[:,1]<np.mean(brb[:,1])]
                #    # brb = brb[brb[:,1]>(np.min(brb[:,1])+0.5*np.ptp(brb[:,1]))]

            #bh = np.vstack((bl,br))
            #bmax = np.max(bh[:,1])
            #bmin = np.min(bh[:,1])

            #blt = bl[bl[:,1]>bmax-vband] # left top points
            #blb = bl[bl[:,1]<bmin+vband] # left bottom points

            #brt = br[br[:,1]>bmax-vband] # right top points
            #brb = br[br[:,1]<bmin+vband] # right bottom points


            t1 = blt[np.argsort(blt[:,0])[-1]]
            t2 = brt[np.argsort(brt[:,0])[0]]

            b1 = blb[np.argsort(blb[:,0])[-1]]
            b2 = brb[np.argsort(brb[:,0])[0]]


            interp1 = (ci[0]-t1[0])*((t2[1]-t1[1])/(t2[0]-t1[0]))+t1[1]
            interp2 = (ci[0]-b1[0])*((b2[1]-b1[1])/(b2[0]-b1[0]))+b1[1]

            if interp1-ci[1]>0 and interp2-ci[1]<0:
                widths.append([i,interp1-ci[1],interp2-ci[1]])

                interps = np.array([[ci[0],interp1],[ci[0],interp2]])

                interps_rec = np.dot(np.linalg.inv(tform),interps.T).T

                #show_2dpoints([bpointsxl_loc1,bpointsxl_loc2,bpointsxr_loc1,bpointsxr_loc2,np.array([ptsl_1,ptsl_2]),np.array([ptsr_1,ptsr_2]),interps,ci.reshape(1,-1)],s=[1,1,1,1,20,20,20,20])
                interps_rec = interps_rec.reshape(1,-1)[0,:]
                interp_segm.append(interps_rec)
        except:
            print("the %d-th was wrong" % i)
            continue
    interp_segm = np.array(interp_segm)
    widths = np.array(widths)
    # check
    # show_2dpoints([np.array([[ci[0],interp1],[ci[0],interp2]]),np.array([t1,t2,b1,b2]),cpoints_loc,bl,br],[10,20,15,2,2])
    return interp_segm, widths


path = "e:/Users/subchange/Downloads/"


image = io.imread(path+"20210706162948837.png", as_gray=True)
iw,ih = image.shape

blobs  = np.copy(image)
#blobs[blobs<128] = 0
#blobs[blobs>128] = 1

blobs = blobs.astype(np.uint8)
# Generate the data
#blobs = data.binary_blobs(200, blob_size_fraction=.2,
                          #volume_fraction=.35, seed=1)
# using scikit-image
## Compute the medial axis (skeleton) and the distance transform
#skel, distance = medial_axis(blobs, return_distance=True)
## Distance to the background for pixels of the skeleton
#dist_on_skel = distance * skel

# Compare with other skeletonization algorithms
skeleton = skeletonize(blobs)
#skeleton_lee = skeletonize(blobs, method='lee')
x, y = np.where(skeleton>0)
centers = np.hstack((x.reshape(-1,1),y.reshape(-1,1)))

normals = estimate_normals(centers, 9) # 这个用于估计法向量的KNN

# search contours of the crack
contours = measure.find_contours(blobs, 0.8)

bl = contours[0]
br = contours[1]

bpoints = np.vstack((bl,br))

#interp_segm, widths = get_crack_ctrlpts(centers,normals,bpoints,hband=2,vband=2)


bpixel = np.zeros((iw,ih,3),dtype=np.uint8)
bpoints = bpoints.astype(np.int64)
bpixel[bpoints[:,0],bpoints[:,1],0] = 255

skeleton_pixel = np.zeros((iw,ih,3),dtype=np.uint8)
skeleton_pixel[skeleton,1] = 255

bpixel_and_skeleton = np.copy(bpixel)
bpixel_and_skeleton[skeleton,1] = 255



fig, axes = plt.subplots(1,2, figsize=(8, 8))
ax = axes.ravel()

ax[0].imshow(blobs, cmap=plt.cm.gray)
ax[0].axis('off')


ax[1].imshow(bpixel_and_skeleton)
#for contour in contours:
#    ax[1].plot(contour[:, 1], contour[:, 0], linewidth=2)

#for i in range(interp_segm.shape[0]):
#    ax[1].plot([interp_segm[i,1],interp_segm[i,3]],[interp_segm[i,0],interp_segm[i,2]],'-b')

#ax[1].set_title('medial_axis')
ax[1].axis('off')



interps, widths = get_crack_ctrlpts(centers, normals, bpoints, hband=2, vband=2, est_width=30)

interps_show = interps[np.random.choice(interps.shape[0], 120, replace=False),:] # 由于太多,这里随机采样120个测量位置,进行显示

for i in range(interps_show.shape[0]):
    ax[1].plot([interps_show[i,1],interps_show[i,3]],[interps_show[i,0],interps_show[i,2]],c='c', ls='-', lw=2, marker='o',ms=4,mec='c',mfc='c')



## ================ small window ==================
#pos = np.array([191, 291]).reshape(1,-1) # input (x,y) where need to calculate crack width
## pos = np.array([142, 178]).reshape(1,-1)

#posn = estimate_normal_for_pos(pos,centers,3)

#interps, widths2 = get_crack_ctrlpts(pos,posn,bpoints,hband=1.5,vband=2)


#sx = pos[0,0] - 20
#sy = pos[0,1] - 20

#ax[2].imshow(bpixel_and_skeleton)

#for i in range(interps.shape[0]):
#    ax[2].plot([interps[i,1],interps[i,3]],[interps[i,0],interps[i,2]],c='c',ls='-',lw=5,marker='o',ms=8,mec='c',mfc='c')

#ax[2].set_ylim(sx,sx+40)
#ax[2].set_xlim(sy,sy+40)

##ax[2].set_title('skeletonize')
#ax[2].axis('off')

#print(interps)

fig.tight_layout()

plt.show()

在这里插入图片描述

python 图像拟合圆并求圆心_基于图像处理的膨胀圆裂缝检测算法 点击蓝字关注我们注:内容来自刊物《无损检测》裂缝网裂缝会对房屋结构的防水性、承载力和耐久性产 生严重影响,因此对房屋裂缝的检测鉴定是结构检测、 危房鉴定和安全监测的重要内容。目前,常采用人工检测手段采集裂缝数据,但该手段具有速度慢、难度大 等缺点,因此随着待检测房屋数量的不断增加,传统的人工检测手段已经无法满足庞大的检测任务,已成为国内外学者研究 的热点。采用数字图像处理技术进行裂缝检测... 阅读详情

相关推荐

动态交通条件下路面立体重建技术解析与应用

立体视觉技术通过多相机系统捕捉物体三维信息,其核心原理是基于视差计算实现深度感知。在计算机视觉领域,立体匹配算法与三维重建技术结合,可高效获取高精度空间数据。针对交通场景的特殊性,动态干扰处理与全视场拼接算法成为技术关键,其中基于语义分割的标线对齐方案显著提升了路面检测精度。该技术已成功应用于高速公路智能监测系统,实现毫米级精度的路面裂缝与车辙识别,为道路预防性养护提供数据支撑。通过融合IMU传感器数据与深度学习模型,系统在80km/h车速下仍保持94.7%的特征匹配率,大幅提升交通基础设施检测效率。

culi3118的博客 557

python基于DeeplabV3Plus开发构建裂缝分割识别系统,并实现裂缝宽度计算测量

python基于DeeplabV3Plus开发构建裂缝分割识别系统,并实现裂缝宽度计算测量

Together_CZ的博客 3473

裂缝二维检测:裂缝的长度和宽度检测

这一节,我们需要去整合一下前面的代码,在进行长度和宽度的测量时,需要裂缝的类型、个数、面积的帮助。这一章是本专栏的核心部分。以上代码我均试过,由于本人能力有限,看不懂代码,最终还是没有采用,使用的是最普通检测宽度的方法,即求得骨架总长度后,用总面积求取平均宽度。

m0_62919535的博客 4237

R2AU-Net: 基于循环残差注意力和半监督学习范式的道路裂缝分割算法

基于道路裂缝分割的工作主要可分为两类,即基于传统图像处理算法和基于现代深度学习算法。今天给大家介绍的这篇工作本身没什么创新性,旨在传递给大家一个观点。就是现有的网络基础架构其实已经发展得很成熟了,无论是经典的 CNNs 还是近几年大火的 Transformer,现在新出的工作几乎都是大力出奇迹的节奏,通过堆积显卡跑消融实验发篇论文,真心不建议大多数小伙伴去卷这个方向。反而像今天这篇工作,笔者感觉真挺好,结合已有的工作探索一些新的“组合”,多去尝试解决一些实际的场景。

专注计算机视觉全栈知识分享 1427

【语义分割系列】基于改进Deeplabv3+的混凝土裂纹分割算法

本文在论文[1]的基础上进行复现,供具有相似任务的同学参考。首先,使用MobileNetV3作为轻量级主干,大幅降低模型参数量;其次,使用尺度内特征交互模块建模全局信息并引入基于归一化的注意力机制,促进多层次裂缝特征信息交互;此外,提取低层次高分辨率特征后引入混合注意力机制,更有效捕获细节特征;最后,设计C2f-SCConv模块,对融合后的高低层级特征流解码,减小计算冗余,提升对多尺度特征的感知能力。旨在帮助初学者或者科研经历较少的小伙伴快速实现项目或者完成相关科研任务。万字长文,直指毕设。

qq_42782701的博客 6634

【图像压缩】基于matlab小波变换图像压缩【含Matlab源码 229期】

小波变换图像压缩 完整的代码,方可运行;可提供运行操作视频!适合小白!

订阅付费专栏Matlab(奶茶价版),可赠送奶茶价版付费专栏指定代码1份; 2031

基于DeepLabV3实践路面、桥梁、基建裂缝裂痕分割

基于DeepLabV3实践路面、桥梁、基建裂缝裂痕分割

Together_CZ的博客 3479

【图像融合】基于matlab小波变换图像融合【含Matlab源码 392期】

小波变换图像融合 完整的代码,方可运行;可提供运行操作视频!适合小白!

订阅付费专栏Matlab(奶茶价版),可赠送奶茶价版付费专栏指定代码1份; 4207

基于骨架线正交投影的裂缝宽度精确测量:算法实现与工程实践

本文详细介绍了基于骨架线正交投影的裂缝宽度精确测量方法,通过正交骨架线法将二维裂缝宽度测量转化为一维距离计算,结合算法实现与工程实践,提升测量精度与效率。文章涵盖骨架线提取、法向量估计、局部坐标系构建等关键步骤,并针对复杂分支、噪声干扰等工程挑战提供解决方案,适用于桥梁、水坝等工程检测场景。

weixin_33508411的博客 310

【语音增强】基于matlab小波变换语音增强【含Matlab源码 296期】

小波变换语音增强 完整的代码,方可运行;可提供运行操作视频!适合小白!

订阅付费专栏Matlab(奶茶价版),可赠送奶茶价版付费专栏指定代码1份; 4403

【信息科学与工程学】【物理/化学科学和工程技术】知识体系081 磁学——系列二 电磁学——电磁学理论模型01

编号模型名称核心数学描述关键参数与物理意义应用场景与说明EM1.001​麦克斯韦微分方程组∇·D=ρ, ∇·B=0, ∇×E=-∂B/∂t, ∇×H=J+∂D/∂tD-电通密度(C/m²), B-磁通密度(T), E-电场强度(V/m), H-磁场强度(A/m)所有电磁现象的基本控制方程,集成电路电磁分析基础EM1.002​麦克斯韦积分方程组∮S D·dS=Q, ∮S B·dS=0, ∮C E·dl=-dΦ_B/dt, ∮C H·dl=I+dΦ_D/dtQ-电荷(C), Φ_B-磁通(Wb), I-电流(A

weixin_49199313的博客 1160

基于深度正交增强生成模型的路面裂缝分割

随着高分辨率路面图像为路面状况提供动态数字孪生,精准的裂缝分割成为构建智能养护系统的关键步骤。然而,大多数现有分割模型假设均匀采样条件并依赖固定参数,限制了其在多样化真实环境下的泛化能力。为应对这一挑战,我们提出一种深度正交增强生成模型(Deep Orthogonal-Enhanced Generative Model, DORGM)用于鲁棒的路面裂缝分割。所提出的框架引入两项关键创新:(1)正交约束模块,在潜在空间中强制特征解耦,将条件特异性噪声与固有裂缝模式分离以减少干扰;(2)软标签路由机制。

AI浩 664

【信息科学与工程学】【物理/化学和工程技术】【数学分析】第一百九十九篇 矢量分析和场论及高温合金——低空经济01

br>∗∗散度∗∗:\nabla \cdot \mathbf{F} = \frac{1}{h_1 h_2 h_3} \left[ \frac{\partial}{\partial u_1}(h_2 h_3 F_1) + \frac{\partial}{\partial u_2}(h_3 h_1 F_2) + \frac{\partial}{\partial u_3}(h_1 h_2 F_3) \right]。计算\frac{\partial \rho}{\partial t} = \psi^

weixin_49199313的博客 254

多尺度注意力机制在图像伪造检测与裂缝分割中的应用

多尺度注意力机制是计算机视觉中的关键技术,通过在不同尺度上动态分配注意力权重,有效捕捉图像中的关键特征。其核心原理是利用金字塔结构或并行分支处理多分辨率特征,结合注意力机制实现特征的自适应融合。这种技术在提升模型性能的同时,也面临着计算效率与部署成本的挑战。在图像伪造检测领域,多尺度注意力能够协同分析空间域和频率域特征,精准定位伪造痕迹;而在裂缝分割任务中,通过尺度自适应特征嵌入和方向注意力Transformer,显著提升了对不同宽度裂缝的检测能力。随着自监督学习和轻量化设计的发展,多尺度注意力机制正在从实

weixin_30458043的博客 439

C# OpenVINO Crack Seg 裂缝分割 裂缝检测 源码

C# OpenVINO Crack Seg 裂缝分割 裂缝检测 源码 博客地址:https://lw112190.blog.youkuaiyun.com/article/details/136376122

运算放大器电路仿真实践:从零实现完整示例

通过一个完整的运算放大器电路仿真示例,深入掌握电路仿真的关键步骤与技巧,帮助电子爱好者和工程师快速上手实际项目设计。

weixin_34725745的博客 1317

基于CNN的智慧桥梁缺陷识别系统开发与实践

卷积神经网络(CNN)作为深度学习在计算机视觉领域的核心技术,通过局部感知和权值共享机制高效提取图像特征。在工程实践中,CNN结合注意力机制可显著提升复杂背景下的目标检测精度。针对基础设施检测这一重要应用场景,基于多尺度特征融合算法和双注意力模块的改进U-Net架构,有效解决了桥梁表面微小裂缝的识别难题。该系统采用云端训练与边缘计算结合的部署方案,实现了毫米级裂缝检测和实时分析,在多个省级交通项目中达到96.3%的准确率,大幅提升桥梁巡检效率与安全性。关键技术包括ResNet主干网络、空间/通道注意力模块(

weixin_29057163的博客 290

5分钟搞定单细胞marker基因气泡图:scDotPlot保姆级教程(含Seurat适配)

本文提供了一份详细的scDotPlot R包使用教程,帮助用户在5分钟内完成单细胞marker基因气泡图的绘制。教程涵盖环境准备、Seurat和SingleCellExperiment对象的适配处理、进阶可视化技巧及自动化报告生成,特别适合生物信息学新手和临床研究者快速掌握这一高效的可视化工具。

weixin_29047545的博客 261

遥感图像变换域处理:傅里叶、小波与PCA实战指南

遥感图像处理中,空间域方法常受限于噪声耦合、波段冗余与纹理混叠等问题。变换域处理作为基础信号理论在地学影像中的延伸,通过频谱分解实现降维、去噪与特征解耦——傅里叶变换擅长周期性干扰(如大气条纹)的全局频域定位,小波变换凭借时频局部化能力精准提取多尺度地物纹理(如湿地植被结构),而PCA则面向高光谱数据提供统计最优的线性降维路径。其技术价值不仅在于提升SNR与压缩率,更在于增强下游分类、变化检测与物理反演模型的鲁棒性。本文聚焦遥感数字图像处理核心场景,结合Landsat、Sentinel及高光谱数据案例,详解

weixin_34006468的博客 428

强化学习入门8—深入理解DDPG

本文是强化学习入门系列的第八篇,前面我们讲Actor-Critic时提到了DDPG。DDPG是google DeepMind团队提出的一种用于输出确定性动作的算法,它解决了Actor-Critic 神经网络每次参数更新前后都存在相关性,导致神经网络只能片面的看待问题这一缺点。同时也解决了DQN不能用于连续性动作的缺点。

小菜羊的博客 4314

ANSYS Fluent中的声学模型:实用指南

文章摘要 本文介绍了ANSYS Fluent中三种气动声学模拟方法:Ffowcs-Williams & Hawkings(FWH)模型、宽带噪声源模型和波动方程模型。FWH模型适用于外部气动声学问题,需要瞬态流场数据来预测远场噪声;宽带模型基于稳态RANS模拟,可快速识别噪声源位置,适合早期设计阶段;波动方程模型直接求解声波方程,提供全域声压分布,但仅适用于低马赫数流动。文章详细说明了各模型的使用场景、设置步骤和关键参数配置,为工程应用提供了实用指南。

weixin_45793544的博客 1317
上一篇: Delaunay三角网 alpha shape 2D点集边缘线提取
下一篇: typora中图片转base64
SubChange
博客等级 码龄9年 104粉丝 9原创
评论 159
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值