python opencv卡尺测量边缘距离

x33g5p2x  于2021-12-24 转载在 Python  
字(9.6k)|赞(0)|评价(0)|浏览(485)

opencv 卡尺法 测量边缘距离
参考来源 :https://github.com/crackwitz/metrology-demo

前言
一、测量方法
二、测量步骤
1.获取直线的像素
2.高斯滤波平滑曲线
3.计算跳变幅度值
4.计算距离值
5.显示和保存图片
总结
前言
halcon中有按照直线找边缘测量距离的工具,但是opencv中没有类似的工具可以直接实现该测量方式,参考网上的实现方式,可以实现。

测量的效果贴图

一、测量方法
根据测量线的两个端点,获取直线的像素,然后进行滤波过滤噪点,计算跳变幅度,获取最大最小值的位置,计算距离

二、测量步骤
1.获取直线的像素
将直线的通过仿射变换转成1D的图像,可以采用亚像素的算法,先获取仿射变换的矩阵

代码如下(示例):

def build_transform(p0, p1, stride=None, nsamples=None):
    "builds an affine transform with x+ along defined line"
    # use one of stride (in pixels) or nsamples (absolute value)
    (x0, y0) = p0
    (x1, y1) = p1
    dx = x1 - x0
    dy = y1 - y0
    length = np.hypot(dx, dy)
    if nsamples is not None:
        #stride = length / nsamples
        factor = 1 / nsamples
    else:
        if stride is None:
            stride = 1.0
        factor = stride / length
        nsamples = int(round(length / stride))
    # map: src <- dst (use WARP_INVERSE_MAP flag for warpAffine)
    H = np.eye(3, dtype=np.float64) # homography
    H[0:2, 0] = (dx, dy) # x unit vector
    H[0:2, 1] = (-dy, dx) # y unit vector is x rotated by 90 degrees
    x=H[0:2, 0:2]
    H[0:2, 0:2] *= factor
    H[0:2, 2] = (x0, y0) # translate onto starting point
    # take affine part of homography
    assert np.isclose(a=H[2], b=(0,0,1)).all() # we didn't touch those but let's better check
    A = H[0:2, :]
    return (nsamples, A)

然后再采用变换的方法获取图像的像素值

def sample_opencv(im, M, nsamples):
    # use transform to get samples
    # available: INTER_{NEAREST,LINEAR,AREA,CUBIC,LANCOS4)
    samples = cv.warpAffine(im, M=M, dsize=(nsamples, 1), flags=cv.WARP_INVERSE_MAP | cv.INTER_CUBIC )
    # flatten row vector
    samples.shape = (-1,)
    # INTER_CUBIC seems to break down beyond 1/32 sampling (discretizes).
    # there might be fixed point algorithms at work
    return samples

2.高斯滤波平滑曲线
samples = scipy.ndimage.gaussian_filter1d(samples, sigma=args.sigma / args.stride)
1
3.计算跳变幅度值

off-by-half in position because for values [0,1,1,0] this returns [+1,0,-1]

gradient = np.diff(samples) / args.stride

4.计算距离值
    i_falling = np.argmin(gradient) # in samples
    i_rising = np.argmax(gradient) # in samples
    distance = np.abs(i_rising - i_falling) * args.stride # in pixels

完整代码:
 

#!/usr/bin/env python3

import sys
import argparse
import numpy as np
import cv2
import scipy.ndimage

### "business logic" ###################################################

def build_transform(p0, p1, stride=None, nsamples=None):
    "builds an affine transform with x+ along defined line"
    # use one of stride (in pixels) or nsamples (absolute value)

    (x0, y0) = p0
    (x1, y1) = p1

    dx = x1 - x0
    dy = y1 - y0

    length = np.hypot(dx, dy)

    if nsamples is not None:
        # stride = length / nsamples
        factor = 1 / nsamples

    else:
        if stride is None:
            stride = 1.0

        factor = stride / length
        nsamples = int(round(length / stride))

    # map: src <- dst (use WARP_INVERSE_MAP flag for warpAffine)
    H = np.eye(3, dtype=np.float64)  # homography

    H[0:2, 0] = (dx, dy)  # x unit vector
    H[0:2, 1] = (-dy, dx)  # y unit vector is x rotated by 90 degrees

    H[0:2, 0:2] *= factor

    H[0:2, 2] = (x0, y0)  # translate onto starting point

    # take affine part of homography
    assert np.isclose(a=H[2], b=(0, 0, 1)).all()  # we didn't touch those but let's better check
    A = H[0:2, :]

    return (nsamples, A)

def sample_opencv(im, M, nsamples):
    # use transform to get samples
    # available: INTER_{NEAREST,LINEAR,AREA,CUBIC,LANCOS4)
    samples = cv2.warpAffine(im, M=M, dsize=(nsamples, 1), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_CUBIC)

    # flatten row vector
    samples.shape = (-1,)

    # INTER_CUBIC seems to break down beyond 1/32 sampling (discretizes).
    # there might be fixed point algorithms at work

    return samples

def sample_scipy(im, M, nsamples):
    # coordinates to this function are (i,j) = (y,x)
    # I could permute first and second rows+columns of M, or transpose input+output
    Mp = M.copy()
    Mp[(0, 1), :] = Mp[(1, 0), :]  # permute rows
    Mp[:, (0, 1)] = Mp[:, (1, 0)]  # permute columns

    samples = scipy.ndimage.interpolation.affine_transform(input=im, matrix=Mp, output_shape=(1, nsamples), order=2,
        # 1: linear (C0, f' is piecewise constant), 2: C1 (f' is piecewise linear), 3: C2... https://en.wikipedia.org/wiki/Smoothness
        mode='nearest'  # border handling
    )

    # flatten row vector
    samples.shape = (-1,)

    return samples

### command line parsing utility functions #############################

def parse_linestr(arg):
    pieces = arg.split(",")
    pieces = [float(el) for el in pieces]
    x0, y0, x1, y1 = pieces
    return ((x0, y0), (x1, y1))

def parse_bool(arg):
    if isinstance(arg, bool):
        return arg
    if arg.lower() in ('yes', 'true', 't', 'y', '1'):
        return True
    elif arg.lower() in ('no', 'false', 'f', 'n', '0'):
        return False
    else:
        raise argparse.ArgumentTypeError(f'Boolean value expected, got {arg!r} instead')

def parse_float(arg):
    import ast

    if '/' in arg:
        num, denom = arg.split('/', 1)
        num = ast.literal_eval(num)
        denom = ast.literal_eval(denom)
        result = num / denom

    else:
        result = ast.literal_eval(arg)

    return result

### main... ############################################################

if __name__ == '__main__':
    # command line argument parsing
    # change defaults here

    parser = argparse.ArgumentParser()
    parser.add_argument("--picture", dest="fname", metavar="PATH", type=str, default="dish-1.jpg", help="path to picture file")
    parser.add_argument("--invert", type=parse_bool, default=True, metavar="BOOL", help="invert picture (cosmetic; distance between gradient extrema is absolute)")
    parser.add_argument("--line", type=parse_linestr, default=((1320, 2500), (1320, 2100)), metavar="X0,Y0,X1,Y1", help="line to sample on")
    parser.add_argument("--stride", type=parse_float, default=1 / 4, metavar="PX", help="stride in pixels to sample along line, fractions supported")
    parser.add_argument("--method", type=lambda s: s.lower(), default="opencv", help="sampling methods: SciPy (slower, smoother, default), OpenCV (faster, less smooth)")
    parser.add_argument("--sigma", type=float, default=2.0, metavar="PX", help="sigma for gaussian lowpass on sampled signal, before gradient is calculated")
    parser.add_argument("--verbose", type=parse_bool, default=True, metavar="BOOL", help="chatty or not")
    parser.add_argument("--display", type=parse_bool, default=True, metavar="BOOL", help="draw some plots")
    parser.add_argument("--saveplot", type=str, default="plot.png", metavar="PATH", help="save a picture (use '--saveplot=' to disable)")
    args = parser.parse_args()

    ########## here be dragons ##########

    if args.stride > 1:
        print(f"WARNING: stride should be <= 1, is {args.stride}")

    stride_decimals = max(0, int(np.ceil(-np.log10(args.stride))))

    if args.verbose: print("loading picture...", end=" ", flush=True)
    im = cv2.imread(args.fname, cv2.IMREAD_GRAYSCALE)
    imh, imw = im.shape[:2]
    if args.invert:
        im = 255 - im  # invert
    im = im.astype(np.float32)  # * np.float32(1/255)
    if args.verbose: print("done")

    # build transform
    p0, p1 = args.line
    nsamples, M = build_transform(p0, p1, stride=args.stride)

    if args.verbose: print(f"taking {nsamples} samples along line {p0} -> {p1}...", end=" ", flush=True)

    # pick one
    if args.method == 'opencv':
        samples = sample_opencv(im, M, nsamples)  # does "normal" cubic (4x4 support points, continuous first derivative)
    elif args.method == 'scipy':
        samples = sample_scipy(im, M, nsamples)  # does some fancy "cubic" with continuous higher derivatives
    else:
        assert False, "method needs to be opencv or scipy"

    if args.verbose: print("sampling done")

    # smoothing to remove noise
    if args.sigma > 0:
        if args.verbose: print(f"lowpass filtering with sigma = {args.sigma} px...", end=" ", flush=True)
        samples = scipy.ndimage.gaussian_filter1d(samples, sigma=args.sigma / args.stride)
        if args.verbose: print("done")

    # off-by-half in position because for values [0,1,1,0] this returns [+1,0,-1]
    gradient = np.diff(samples) / args.stride

    i_falling = np.argmin(gradient)  # in samples
    i_rising = np.argmax(gradient)  # in samples

    distance = np.abs(i_rising - i_falling) * args.stride  # in pixels

    if args.verbose:
        print(f"distance: {distance:.{stride_decimals}f} pixels")
    else:
        print(distance)

    # this was the result. algorithm is done.
    # now follows displaying code

    if args.display:
        gradient *= 255 / np.abs(gradient).max()

        # plot signal
        plot = cv2.plot.Plot2d_create(np.arange(nsamples, dtype=np.float64), samples.astype(np.float64))
        plot.setMinY(256 + 32)
        plot.setMaxY(-32)
        plot.setMinX(0)
        plot.setMaxX(nsamples)
        plot.setGridLinesNumber(5)
        plot.setShowText(False)  # callout for specific point, setPointIdxToPrint(index)
        plot.setPlotGridColor((64,) * 3)
        canvas1 = plot.render()
        # plot gradient
        plot = cv2.plot.Plot2d_create(np.arange(nsamples - 1) + 0.5, gradient.astype(np.float64))
        plot.setMinY(256 + 64)
        plot.setMaxY(-256 - 64)
        plot.setMinX(0)
        plot.setMaxX(nsamples)
        plot.setGridLinesNumber(5)
        plot.setShowText(False)  # callout for specific point, setPointIdxToPrint(index)
        plot.setPlotGridColor((64,) * 3)
        canvas2 = plot.render()

        # arrange vertically
        canvas = np.vstack([canvas1, canvas2])  # 600 wide, 800 tall

        # draw lines at edges (largest gradients)
        # plots are 600x400 pixels... and there's no way to plot multiple or plot lines in "plot space"
        px_falling = int(600 * (i_falling + 0.5) / nsamples)
        px_rising = int(600 * (i_rising + 0.5) / nsamples)
        cv2.line(canvas, (px_falling, 0), (px_falling, 400 * 2), color=(255, 0, 0))
        cv2.line(canvas, (px_rising, 0), (px_rising, 400 * 2), color=(255, 0, 0))

        # some text to describe the picture
        cv2.putText(canvas, f"{nsamples * args.stride:.0f} px, {p0} -> {p1}", (10, 350), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), thickness=1, lineType=cv2.LINE_AA)

        cv2.putText(canvas, f"stride {args.stride} px, {nsamples} samples, sigma {args.sigma}", (10, 350 + 35), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), thickness=1, lineType=cv2.LINE_AA)

        cv2.putText(canvas, f"distance: {distance:.{stride_decimals}f} px", (10, 350 + 70), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (255, 255, 255), thickness=1, lineType=cv2.LINE_AA)

        # save for posterity
        if args.saveplot:
            cv2.imwrite(args.saveplot, canvas)

        if args.display:
            cv2.imshow("plot", canvas)

            if args.verbose:
                print("press Ctrl+C in the terminal, or press any key while the imshow() window is focused")

            while True:
                keycode = cv2.waitKey(100)

                if keycode == -1:
                    continue

                # some key...

                if args.verbose:
                    print(f"keycode: {keycode}")

                cv2.destroyAllWindows()
                break

总结
提示:显示的程序包含了opencv pilo,这个需要引入opencv-contrib-python模块:

原文链接:https://blog.csdn.net/hong3731/article/details/119649418

使用过程报错:

module 'cv2' has no attribute 'plot'

相关文章