简单的相机对焦

65 阅读3分钟

硬件

海康相机x1 伺服电机x1 ABB的PLCx1 PC机x1

实现逻辑

通过伺服电机控制相机行程,同时相机不断采集图像来进行清晰度分析,

实现原理

使用PLC控制相机行程,调整相机到采集物体的距离,同时相机不断采集图像。

通过拉普拉斯算子计算图像的边缘,然后对求出边缘的图像求均值,通常来讲,边缘越清晰,对其使用拉普拉斯算子求边缘后再求均值得到的结果越大。

将每一张图像采集时相机的位移和其计算出的图像边缘均值作为x轴,y轴来拟合图像曲线得到一条近似于二次函数的图像,对此二次函数求极值便能简单地实现对焦。后发现该曲线只有接近最佳相机位移的区间的值可以近似拟合为二次函数,距离相机景深较远时边缘均值随相机位移的变化较小。因此选择直接求极值点。

应用场景

该方法只能用于相机采集的图像较为简单,背景较为单一且不变的情景下。采集物体还需占据相机大部分画幅。

代码实现

import cv2 as cv
import os
import exp_packages.pybind11.libcamframe as libcam
from imutils import paths
from exp_packages.plc_modbus.abb_plc import ABB_PLC
import time
import numpy as np


# 获得图像边缘均值
def get_picture_sharpness(image):
    image_gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)

    # image_sobel = cv.Sobel(image_gray, cv.CV_16U, 1, 1)
    # image_var = cv.mean(image_sobel)[0]

    image_laplacian = cv.Laplacian(image_gray, cv.CV_16U, 1, 1)
    image_la_var = cv.mean(image_laplacian)[0]

    return image_la_var


# 计算二次函数图像极值点
def cal_quadratic_max_point(x_points, y_points):
    a, b, c = np.polyfit(x_points, y_points, 2)
    max_x, max_y = round(b / (-2 * a), 2), round((4 * a * c - b ** 2) / (4 * a), 2)
    print("x:{0},y:{1}".format(max_x, max_y))
    return max_x, max_y


# 将相机位置置零
def camera_pos_set_zero(abb_camera):
    abb_camera.write_position(0.0)
    abb_camera.write_position_control(0)
    abb_camera.write_position_control(1)
    time.sleep(0.5)
    abb_camera.write_position_control(0)


# 控制相机移动,得到相机位置列表和对应图像边缘均值列表
def camera_pos_lap(camera, abb_camera):
    # 相机距离,边缘均值列表,相机初始位置,相机最大位置的初始化
    pos_list = []
    lap_list = []
    camera_pos_set_zero(abb_camera)
    start = abb_camera.read_actual_position()
    print("start:{}".format(start))
    max_distance = 60
    # 循环获取相机位移和图像边缘均值
    while start < max_distance:
        if camera.init() != 1:
            max_mean = 0
            max_index = ''
            # 电动控制相机位移,睡眠时间越久位移越长
            abb_camera.write_jog_control(1)
            time.sleep(0.3)
            abb_camera.write_jog_control(0)
            res = camera.read()
            pos = abb_camera.read_actual_position()
            laplacian_mean = get_picture_sharpness(res.frame)
            pos_list.append(round(pos, 2))
            lap_list.append(round(laplacian_mean, 2))
            print('pos: {}  laplacian_mean: {}'.format(pos, laplacian_mean))
            # 每次操作间隔时间
            time.sleep(0.5)
    abb_camera.close()
    camera.release()
    print("pos_list:{},lap_list:{}".format(pos_list, lap_list))
    return pos_list, lap_list


if __name__ == '__main__':
    # 相机初始化和PLC初始化
    camera = libcam.CamFrame("192.168.2.155", 700, 10, 2432, 2048)
    abb_camera_1 = ABB_PLC("192.168.100.10", 502, device_name='cam_1')
    abb_camera_2 = ABB_PLC("192.168.100.10", 502, device_name='cam_2')
    abb_camera_3 = ABB_PLC("192.168.100.10", 502, device_name='cam_3')
    
    # 后续验证该方法得出的值偏差很大,并不可靠
    pos, lap = camera_pos_lap(camera, abb_camera_2)
    print("Most clear:{}".format(cal_quadratic_max_point(pos, lap)))
    # print('Best image is {}, value is {}'.format(max_index, max_mean))
    
    # 使用matlib库画二次拟合曲线验证
    import matplotlib.pyplot as plt
    from scipy.optimize import curve_fit

    x = np.array(pos_list)
    y = np.array(lap_list)

    params, cov = curve_fit(quad_func, x, y)

    x_smooth = np.linspace(min(x), max(x), 100)
    y_smooth = quad_func(x_smooth, *params)

    plt.scatter(x, y, label="Data")
    plt.plot(x_smooth, y_smooth, label="Fit")
    plt.legend()
    plt.show()

实际图像

image.png

图1

如图1,可以看到根据二次函数拟合并不符合使用需求,曲线只有接近极值时才勉强近似于二次函数,因此在此决定放弃使用二次函数,直接取用列表中能采集到的最大值。