注意:不讲实现原理,也没有做UI,精度就玩玩的级别,记得打(尽量柔和的)光。
博主是一名机械设计制造及其自动化专业的学生,以前在车间上课时总需要挑选特定尺寸的毛坯作为被加工工件,奈何本人较懒,所以就有了码这么一个py文件出来助我偷懒的想法。
完整的文件(某U加速“学术资源”可以访问):
喜欢用Pycharm还是Anaconda或其它都可以,没有关系。
因为摄像头使用的只是普通的家用摄像头(某夕夕个位数包邮),所以在码程序之前需要准备一个尺寸精度较高(尽量高)的参照物来获取欧氏距离和真实长度的比率。
穷得只能3D打印的屑博主:10mm³,20mm³,30mm³
from scipy.spatial.distance import euclidean # 用来计算端点之间的欧氏距离
import numpy as np
import imutils
import time
import cv2
if __name__ == '__main__':
camera_type = set_camera_type() # 设置相机类型
filter_area, reference_points = reference_processing() # 创建被过滤面积值
rate = rate_calculation() # 计算欧氏距离与实际距离的比率
real_time_processing() # 实现实时测量
这段函数是为了方便程序能够在内置相机或外置相机之间来回切换工作。如果确定仅使用外置相机的情况下可以忽略这一步。
def set_camera_type():
while True:
try:
set_type = int(input('摄像头调用(输入数字代号:0.内置,1.外置):'))
except ValueError:
delay('输入参数类型错误')
continue
else:
if (set_type < 0) or (set_type > 1):
delay('输出参数不在范围内')
continue
elif set_type == 0:
print('选择:内置摄像头')
else:
print('选择:外置摄像头')
break
return set_type
如确认仅使用外置相机时将camera_type设置为‘1’,cv2.CAP_DSHOW为可选参数,在相机调用过程中出现不知名报错时试着加入。关于第二个if,是防止遇到窗口关闭了但又没有完全关闭的情况而导致的堵塞。
def call_camera():
camera = cv2.VideoCapture(camera_type, cv2.CAP_DSHOW)
if camera.isOpened() is False:
print('摄像头调用失败')
raise AssertionError
else:
while True:
frame = camera.read()[1] # 返回捕获到的RGB
image = cv2.flip(frame, 1, dst=None)
cv2.imshow('Camera', image)
if (cv2.waitKey(1) > -1) or (cv2.getWindowProperty('Camera', cv2.WND_PROP_VISIBLE) < 1.0): # 设置关闭条件
cv2.destroyWindow('Camera')
break
return image
cv2.Canny中的min_val与max_val参数值的大小可以判断是否为边,且值越小,拾取到的边缘信息就越多。
def get_points(image):
gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
gaussian_blur = cv2.GaussianBlur(gray_image, (5, 5), 0)
min_val, max_val = 50, 100
margin = cv2.Canny(gaussian_blur, min_val, max_val)
open_margin = cv2.dilate(margin, None, iterations=15) # 开运算,如果有纯色平台iteration可以小一些
contours = cv2.findContours(open_margin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
points = imutils.grab_contours(contours)
return points
这一部分程序的用处主要是绘制框架与数据计算。程序前期在对参照物对象拍照时需要绘制框架呈现出被选中的对像,在程序后期除了要绘制框架外,还要通过比率计算真实长度、面积,最后在绘制框架的同时把计算结果也显示出来。
def draw_frame(image, points, tag):
if tag == 0:
for point in points:
min_area = cv2.minAreaRect(point)
min_area_point = cv2.boxPoints(min_area) # 获取最小外接矩阵的四个端点
int_point = [min_area_point.astype('int')]
cv2.drawContours(image, int_point, -1, (0, 0, 255), 1)
return min_area_point
else:
for point in points:
min_area = cv2.minAreaRect(point)
min_area_point = cv2.boxPoints(min_area)
left_point, right_point = min_area_point[0], min_area_point[1]
X = left_point[0] + int(abs(right_point[0] - left_point[0]) / 2) # 获取顶部中点X坐标
Y = left_point[1] + int(abs(right_point[1] - left_point[1]) / 2) # 获取顶部中点Y坐标
int_point = [min_area_point.astype('int')]
cv2.drawContours(image, int_point, -1, (0, 0, 255), 1) # 绘制边框
radius = (euclidean(left_point, right_point) / 2) / rate # 获取半径
area = int((3.1415926 * pow(radius, 2)))
# 展示面积信息
cv2.putText(image, '{}'.format(area), (int(X), int(Y)), cv2.FONT_HERSHEY_SIMPLEX, 5, (0, 0, 255), 5)
我的检测对象一般以圆形为主,所以只需要取出最左、最右的两个点坐标就能够用于计算了
min_area_point = cv2.boxPoints(min_area) # 获取最小外接矩阵的四个端点
left_point, right_point = min_area_point[0], min_area_point[1] # 获取两处端点的信息
比率是参照物两点在度量空间内两点距离和真实距离的比值,本项目后期所有的计算尺寸均由欧氏距离比上比率得出。
def rate_calculation():
delay('计算比率')
left_point, right_point = reference_points[0], reference_points[1]
length_euclidean = euclidean(left_point, right_point) # 计算欧氏距离
while True:
try:
length_reference = int(input('输入参照物长度(mm):'))
except ValueError:
delay('输入参数类型错误')
continue
else:
if length_reference <= 0:
delay('参数不可小于或等于0')
continue
else:
break
rate = length_euclidean / length_reference # 比率计算
print('(参照物)欧氏长度:{}mm'.format(length_euclidean))
print('(参照物)实际长度:{}mm'.format(length_reference))
print('长度比率:{}'.format(rate))
return rate
在调试过程中有多次遇到过拍照后参照物选取不正确,为了防止这一情况的出现就设置了while循环,只有在手动确认参照物被正常框选的情况下才能进入下一步。
值得关注的是selected_points 的筛选方式是采用了将筛选面积不断加一,直到只剩下参照物对象的方式,即len(selected_points) = 1。
def reference_processing():
circulation = True
while circulation:
image = call_camera()
points = get_points(image) # 图像处理
selected_points = [] # 创建被筛选的轮廓数据的容器
# --------按面积大小筛选轮廓--------
filter_area = 1
while True:
[selected_points.append(i) for i in points if cv2.contourArea(i) > filter_area]
if len(selected_points) > 1:
selected_points.clear() # 清空内容,为下一次存储数据用
filter_area += 1
else:
break
reference_area_point = draw_frame(image, selected_points, 0)
while True:
cv2.imshow('reference', image)
if (cv2.waitKey(1) > -1) or (cv2.getWindowProperty('reference', cv2.WND_PROP_VISIBLE) < 1.0):
cv2.destroyWindow('reference')
break
while circulation:
try:
tag = str(input('是否是理想参照物(Y/N):'))
except ValueError:
delay('输入参数类型错误')
continue
else:
if (tag == 'Y') or (tag == 'y'):
circulation = False
break
elif (tag == 'N') or (tag == 'n'):
break
return filter_area, reference_area_point
这段就不多说了,和前面基本一样的原理。
def real_time_processing():
print('进入实时测量,按下回车键结束程序')
camera = cv2.VideoCapture(camera_type, cv2.CAP_DSHOW)
while True:
frame = camera.read()[1]
image = cv2.flip(frame, 1, dst=None)
points = get_points(image) # 获取所有参照物的端点
selected_points = []
[selected_points.append(i) for i in points if cv2.contourArea(i) > filter_area] # 筛选后的端点
draw_frame(image, selected_points, 1) # 绘制边框
cv2.imshow('Camera', image)
if (cv2.waitKey(1) > -1) or (cv2.getWindowProperty('Camera', cv2.WND_PROP_VISIBLE) < 1.0):
cv2.destroyWindow('Camera')
break
参照物拍照:(指方为圆)
实时测量:(指方为圆),这里摄像头高度发生了变化,拍摄角度也出现误差,可以采用只存储最小值数据尽量保证精度。
验证:(高度发生变化出现的误差为-4)