python opencv卡尺测量边缘距离

举报
风吹稻花香 发表于 2021/12/25 00:35:22 2021/12/25
【摘要】 opencv 卡尺法 测量边缘距离 参考来源 :https://github.com/crackwitz/metrology-demo 前言 一、测量方法 二、测量步骤 1.获取直线的像素 2.高斯滤波平滑曲线 3.计算跳变幅度值 4.计算距离值 5.显示和保存图片 总结 前言 halcon中有按照直线找边缘测量距离的工具,但是...

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



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

测量的效果贴图


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

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

代码如下(示例):


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



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


  
  1. def sample_opencv(im, M, nsamples):
  2.     # use transform to get samples
  3.     # available: INTER_{NEAREST,LINEAR,AREA,CUBIC,LANCOS4)
  4.     samples = cv.warpAffine(im, M=M, dsize=(nsamples, 1), flags=cv.WARP_INVERSE_MAP | cv.INTER_CUBIC )
  5.     # flatten row vector
  6.     samples.shape = (-1,)
  7.     # INTER_CUBIC seems to break down beyond 1/32 sampling (discretizes).
  8.     # there might be fixed point algorithms at work
  9.     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

完整代码:
 


  
  1. #!/usr/bin/env python3
  2. import sys
  3. import argparse
  4. import numpy as np
  5. import cv2
  6. import scipy.ndimage
  7. ### "business logic" ###################################################
  8. def build_transform(p0, p1, stride=None, nsamples=None):
  9. "builds an affine transform with x+ along defined line"
  10. # use one of stride (in pixels) or nsamples (absolute value)
  11. (x0, y0) = p0
  12. (x1, y1) = p1
  13. dx = x1 - x0
  14. dy = y1 - y0
  15. length = np.hypot(dx, dy)
  16. if nsamples is not None:
  17. # stride = length / nsamples
  18. factor = 1 / nsamples
  19. else:
  20. if stride is None:
  21. stride = 1.0
  22. factor = stride / length
  23. nsamples = int(round(length / stride))
  24. # map: src <- dst (use WARP_INVERSE_MAP flag for warpAffine)
  25. H = np.eye(3, dtype=np.float64) # homography
  26. H[0:2, 0] = (dx, dy) # x unit vector
  27. H[0:2, 1] = (-dy, dx) # y unit vector is x rotated by 90 degrees
  28. H[0:2, 0:2] *= factor
  29. H[0:2, 2] = (x0, y0) # translate onto starting point
  30. # take affine part of homography
  31. assert np.isclose(a=H[2], b=(0, 0, 1)).all() # we didn't touch those but let's better check
  32. A = H[0:2, :]
  33. return (nsamples, A)
  34. def sample_opencv(im, M, nsamples):
  35. # use transform to get samples
  36. # available: INTER_{NEAREST,LINEAR,AREA,CUBIC,LANCOS4)
  37. samples = cv2.warpAffine(im, M=M, dsize=(nsamples, 1), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_CUBIC)
  38. # flatten row vector
  39. samples.shape = (-1,)
  40. # INTER_CUBIC seems to break down beyond 1/32 sampling (discretizes).
  41. # there might be fixed point algorithms at work
  42. return samples
  43. def sample_scipy(im, M, nsamples):
  44. # coordinates to this function are (i,j) = (y,x)
  45. # I could permute first and second rows+columns of M, or transpose input+output
  46. Mp = M.copy()
  47. Mp[(0, 1), :] = Mp[(1, 0), :] # permute rows
  48. Mp[:, (0, 1)] = Mp[:, (1, 0)] # permute columns
  49. samples = scipy.ndimage.interpolation.affine_transform(input=im, matrix=Mp, output_shape=(1, nsamples), order=2,
  50. # 1: linear (C0, f' is piecewise constant), 2: C1 (f' is piecewise linear), 3: C2... https://en.wikipedia.org/wiki/Smoothness
  51. mode='nearest' # border handling
  52. )
  53. # flatten row vector
  54. samples.shape = (-1,)
  55. return samples
  56. ### command line parsing utility functions #############################
  57. def parse_linestr(arg):
  58. pieces = arg.split(",")
  59. pieces = [float(el) for el in pieces]
  60. x0, y0, x1, y1 = pieces
  61. return ((x0, y0), (x1, y1))
  62. def parse_bool(arg):
  63. if isinstance(arg, bool):
  64. return arg
  65. if arg.lower() in ('yes', 'true', 't', 'y', '1'):
  66. return True
  67. elif arg.lower() in ('no', 'false', 'f', 'n', '0'):
  68. return False
  69. else:
  70. raise argparse.ArgumentTypeError(f'Boolean value expected, got {arg!r} instead')
  71. def parse_float(arg):
  72. import ast
  73. if '/' in arg:
  74. num, denom = arg.split('/', 1)
  75. num = ast.literal_eval(num)
  76. denom = ast.literal_eval(denom)
  77. result = num / denom
  78. else:
  79. result = ast.literal_eval(arg)
  80. return result
  81. ### main... ############################################################
  82. if __name__ == '__main__':
  83. # command line argument parsing
  84. # change defaults here
  85. parser = argparse.ArgumentParser()
  86. parser.add_argument("--picture", dest="fname", metavar="PATH", type=str, default="dish-1.jpg", help="path to picture file")
  87. parser.add_argument("--invert", type=parse_bool, default=True, metavar="BOOL", help="invert picture (cosmetic; distance between gradient extrema is absolute)")
  88. parser.add_argument("--line", type=parse_linestr, default=((1320, 2500), (1320, 2100)), metavar="X0,Y0,X1,Y1", help="line to sample on")
  89. parser.add_argument("--stride", type=parse_float, default=1 / 4, metavar="PX", help="stride in pixels to sample along line, fractions supported")
  90. parser.add_argument("--method", type=lambda s: s.lower(), default="opencv", help="sampling methods: SciPy (slower, smoother, default), OpenCV (faster, less smooth)")
  91. parser.add_argument("--sigma", type=float, default=2.0, metavar="PX", help="sigma for gaussian lowpass on sampled signal, before gradient is calculated")
  92. parser.add_argument("--verbose", type=parse_bool, default=True, metavar="BOOL", help="chatty or not")
  93. parser.add_argument("--display", type=parse_bool, default=True, metavar="BOOL", help="draw some plots")
  94. parser.add_argument("--saveplot", type=str, default="plot.png", metavar="PATH", help="save a picture (use '--saveplot=' to disable)")
  95. args = parser.parse_args()
  96. ########## here be dragons ##########
  97. if args.stride > 1:
  98. print(f"WARNING: stride should be <= 1, is {args.stride}")
  99. stride_decimals = max(0, int(np.ceil(-np.log10(args.stride))))
  100. if args.verbose: print("loading picture...", end=" ", flush=True)
  101. im = cv2.imread(args.fname, cv2.IMREAD_GRAYSCALE)
  102. imh, imw = im.shape[:2]
  103. if args.invert:
  104. im = 255 - im # invert
  105. im = im.astype(np.float32) # * np.float32(1/255)
  106. if args.verbose: print("done")
  107. # build transform
  108. p0, p1 = args.line
  109. nsamples, M = build_transform(p0, p1, stride=args.stride)
  110. if args.verbose: print(f"taking {nsamples} samples along line {p0} -> {p1}...", end=" ", flush=True)
  111. # pick one
  112. if args.method == 'opencv':
  113. samples = sample_opencv(im, M, nsamples) # does "normal" cubic (4x4 support points, continuous first derivative)
  114. elif args.method == 'scipy':
  115. samples = sample_scipy(im, M, nsamples) # does some fancy "cubic" with continuous higher derivatives
  116. else:
  117. assert False, "method needs to be opencv or scipy"
  118. if args.verbose: print("sampling done")
  119. # smoothing to remove noise
  120. if args.sigma > 0:
  121. if args.verbose: print(f"lowpass filtering with sigma = {args.sigma} px...", end=" ", flush=True)
  122. samples = scipy.ndimage.gaussian_filter1d(samples, sigma=args.sigma / args.stride)
  123. if args.verbose: print("done")
  124. # off-by-half in position because for values [0,1,1,0] this returns [+1,0,-1]
  125. gradient = np.diff(samples) / args.stride
  126. i_falling = np.argmin(gradient) # in samples
  127. i_rising = np.argmax(gradient) # in samples
  128. distance = np.abs(i_rising - i_falling) * args.stride # in pixels
  129. if args.verbose:
  130. print(f"distance: {distance:.{stride_decimals}f} pixels")
  131. else:
  132. print(distance)
  133. # this was the result. algorithm is done.
  134. # now follows displaying code
  135. if args.display:
  136. gradient *= 255 / np.abs(gradient).max()
  137. # plot signal
  138. plot = cv2.plot.Plot2d_create(np.arange(nsamples, dtype=np.float64), samples.astype(np.float64))
  139. plot.setMinY(256 + 32)
  140. plot.setMaxY(-32)
  141. plot.setMinX(0)
  142. plot.setMaxX(nsamples)
  143. plot.setGridLinesNumber(5)
  144. plot.setShowText(False) # callout for specific point, setPointIdxToPrint(index)
  145. plot.setPlotGridColor((64,) * 3)
  146. canvas1 = plot.render()
  147. # plot gradient
  148. plot = cv2.plot.Plot2d_create(np.arange(nsamples - 1) + 0.5, gradient.astype(np.float64))
  149. plot.setMinY(256 + 64)
  150. plot.setMaxY(-256 - 64)
  151. plot.setMinX(0)
  152. plot.setMaxX(nsamples)
  153. plot.setGridLinesNumber(5)
  154. plot.setShowText(False) # callout for specific point, setPointIdxToPrint(index)
  155. plot.setPlotGridColor((64,) * 3)
  156. canvas2 = plot.render()
  157. # arrange vertically
  158. canvas = np.vstack([canvas1, canvas2]) # 600 wide, 800 tall
  159. # draw lines at edges (largest gradients)
  160. # plots are 600x400 pixels... and there's no way to plot multiple or plot lines in "plot space"
  161. px_falling = int(600 * (i_falling + 0.5) / nsamples)
  162. px_rising = int(600 * (i_rising + 0.5) / nsamples)
  163. cv2.line(canvas, (px_falling, 0), (px_falling, 400 * 2), color=(255, 0, 0))
  164. cv2.line(canvas, (px_rising, 0), (px_rising, 400 * 2), color=(255, 0, 0))
  165. # some text to describe the picture
  166. 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)
  167. 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)
  168. 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)
  169. # save for posterity
  170. if args.saveplot:
  171. cv2.imwrite(args.saveplot, canvas)
  172. if args.display:
  173. cv2.imshow("plot", canvas)
  174. if args.verbose:
  175. print("press Ctrl+C in the terminal, or press any key while the imshow() window is focused")
  176. while True:
  177. keycode = cv2.waitKey(100)
  178. if keycode == -1:
  179. continue
  180. # some key...
  181. if args.verbose:
  182. print(f"keycode: {keycode}")
  183. cv2.destroyAllWindows()
  184. break



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

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

使用过程报错:

 module 'cv2' has no attribute 'plot'

文章来源: blog.csdn.net,作者:AI视觉网奇,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/jacke121/article/details/122118816

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。