Документация Engee
Notebook

Построение модели объектива

Построим модель идеального объектива при помощи пакета GeometricalOptics.jl. Покажем, как вывести несколько графиков распространения пучков света на одно полотно и реализуем процедуру оптимизации лучей на этом графике, чтобы сделать график наиболее информативным.

Параметры оптической системы

Для указания параметров объектива потребуется подключить (а, возможно, и установить) библиотеку GeometricalOptics.

In [ ]:
Pkg.add( url="https://github.com/airspaced-nk5/GeometricalOptics.jl#master" )
Pkg.add( "Optim" )
In [ ]:
using GeometricalOptics
gr(); # Библиотека лучше работает с этим механизмом отрисовки графиков

Теперь мы можем задать несколько сферических линз и плоскость изображения:

Объект Тип Материал Индекс рефракции Радиус R1 (мм) Радиус R2 (мм) Толщина Расстояние до след.пов.
Линза двояковыпуклая SK16 1.6204 22 430 3 6
Линза двояковогнутая F2 1.6200 22 20 1 6
Линза двояковыпуклая SK16 1.6204 78 16 3 34

Последним элементом оптической системы является плоскость изображения.

Единица измерения (миллиметр) не играет особой роли в расчетах пока мы не учитываем дифракцию и длину волны.

Толщина линзы равна длине пути, который луч света проходит через линзу, если идет по оптической оси.

Под радиусами R1 и R2 понимаются радиус кривизны каждой стороны каждой линзы: R1 характеризует радиус кривизны "входной" стороны, расположенной ближе к источнику света, R2 – выходная сторона линзы, расположенная ближе к плоскости изображения.

Опишем эту оптическую систему и построим небольшую визуализацию:

In [ ]:
surfsList = [ spherical, spherical,
              spherical, spherical,
              spherical, spherical,
              zplane]

p_s = 10; # Расстояние от левого края графика до первой преломляющей поверхности
stations = p_s .+ [ 0., 3., 9., 10., 16., 19., 53. ]
radii = [ 22., -430., -22., 20., 78., -16., 0.0 ]
coeffsList = [ [s, r] for (s,r) in zip(stations, radii)]

# Материал линз, по порядку: SK16, F2, SK16
nList = [ 1., 1.6204,
          1., 1.6200,
          1., 1.6204,
          1.]

optSta = opticalstack( coeffsList, surfsList, nList );

test_bundle = bundle( [0.], (-3:3:3), 0., 0., 0. );
p_lens = optSta( test_bundle; rend = "YZ", halfdomain=3.5 )

plot!( ylimits=(-5,5), aspect_ratio=:none, size=(600,200) )
Out[0]:

Небольшая визуализация позволила нам увидеть все заданные элементы.

Нацеливание лучей в апертурную диафрагму

Теперь предположим, что в системе есть диафрагма, расположенная в 2 мм после вогнутой линзы.

Мы хотим пропустить пучки света через нее, задав определенный угол падения, и убедиться, что они сфокусируются на плоскости изображения.

Мы могли бы разбить модель на две части и моделировать распространение от диафрагмы в оба направления (одна модель для поверхностей [6:end], другая для [5:-1:1]). Но поскольку лучи должны быть параллельны только при подлете к первой линзе (в при прохождении диафрагмы они не будут параллельны друг другу), то нам пришлось бы искать угол каждого луча при проохождении через диафрагму.

Пойдем другим путем и "оптимизируем" два параметра: ширину пучка и точку на оси Y, из которой направляется центральный луч пучка. Наша цель – найти параметры пучка, лучи которого проходят точно через диафрагму.

In [ ]:
angles = [0.0, 0.1, 0.2];             # углы каждого из пучков лучей
colors = [:blue, :green, :red];       # каждый пучок характеризуется своим цветом
ypositions_init = [ 0., 3.0, 5.0];    # приблизительное положение точки отправки центральных лучей по оси Y
bundle_halfwidth = 3;                 # радиус пучков света

p = plot();                           # нанесем несколько графиков

for (angle, ypos, pc, plot_surface) in zip(angles, ypositions_init, colors, [true, false, false])
    b = bundle([0.], range(-bundle_halfwidth,bundle_halfwidth,length=3) .- ypos, 0., angle, 0.)
    optSta( b; rend = "YZ", color = pc, plobj = p, halfdomain=5.5, issurfplot=plot_surface )
end

plot!( p, p_s.+[12., 12.], [-2.5, 2.5], lw=2, lc=:black, markershape=:diamond, markercolor=:white );
plot!( p, ylimits=(-8,8), size=(900,400) )
Out[0]:

Если приблизить изображение диафрагмы, мы увидим, что наше первое приближение не очень далеко от желательного, но для точного расчета нужно немного передвинуть точки, откуда отправляются пучки лучей.

Угол пучка к оптической оси не изменится. Мы просто ищем, как расположлить пучок таким образом, чтобы он прошел ровно через диафрагму. Это полезно для расчета размера сенсора и для других интересных результатов.

In [ ]:
plot!( xlimits=(p_s .+ [10, 14]), ylimits=(-3,3), aspect_ratio=:none, size=(500,500) )
Out[0]:

Теперь создадим процедуру оптимизации.

In [ ]:
using Optim

# Новая конфигурация, которая "заканчивается" диафрагмой (хотя можно добавить zplane между любыми линзами)
optSta2 = opticalstack( [coeffsList[1:4]; [[p_s+12.]]], [surfsList[1:4]; zplane], [nList[1:4] ; 1.0] );

# Функция, которую мы будем оптимизировать
function f(x, in_angle)
    b_hw = x[1]       # радиус пучка
    ypos = x[2]       # смещение по Y центра пучка
    
    b = bundle([0.], range(-b_hw,b_hw,length=3) .- ypos, 0., in_angle, 0.);
    
    # Изучим пятно на пересечении пучка с 6-ой поверхностью
    t_info = GeometricalOptics.trace_extract_terminus( optSta2( b ), 6, coord="y" );
    
    # Два критерия оптимизации: обе границы светового пятна должны совпадать с размером диафрагмы
    q1 = abs( minimum( t_info ) - (-2.5))
    q2 = abs( maximum( t_info ) - ( 2.5)) 
    return q1 + q2
end

angles_hw_list = []
y_positions_list = []
for (angle,y_pos_init) in zip(angles, ypositions_init)
    # Выполняем оптимизацию для каждого нужного нам значения угла по-отдельности
    res = optimize(x -> f(x, angle), [bundle_halfwidth, y_pos_init])
    angle_optim, ypos_optim = Optim.minimizer( res )
    push!( angles_hw_list, angle_optim )
    push!( y_positions_list, ypos_optim )
end

# Выведем таблицу результатов оптимизации
display([["Направление луча"; "Ширина пучка"; "Отправная точка по Y"] [angles angles_hw_list y_positions_list]'] )
3×4 Matrix{Any}:
 "Направление луча"       0.0         0.1      0.2
 "Ширина пучка"           3.1232      3.13102  3.15749
 "Отправная точка по Y"  -5.40173e-8  2.44082  4.98689

После оптимизации оптическая система выглядит следующим образом:

In [ ]:
p = plot(); # Полотно для нанесения нескольких графиков

for (angle, ypos, b_hw, pc, plot_surface) in zip(angles, y_positions_list, angles_hw_list, colors, [true, false, false])
    b = bundle([0.], range(-b_hw,b_hw,length=3) .- ypos, 0., angle, 0.)
    optSta( b; rend = "YZ", color = pc, plobj = p, halfdomain=5.5, issurfplot=plot_surface )
end

plot!( p, p_s.+[12., 12.], [-2.5, 2.5], lw=2, lc=:black, markershape=:diamond, markercolor=:white );
plot!( p, ylimits=(-8,8), size=(900,400) )

Более пристальное изучение диафрагмы позволяет увидеть правильное положение светового пятна.

In [ ]:
plot!( xlimits=(p_s .+ [10, 14]), ylimits=(-3,3), aspect_ratio=:none, size=(500,500) )
Out[0]:

Размеры линз в библиотеке GeometricalOptics.jl наглядно не учитываются. В некоторых случаях, например при создании многочленов Цернике, прохождение луча за пределами определенной области, вызывает предусмотренную разработчиками ошибку.

Заключение

Если обернуть функцию расчета оптической схемы в процедуру оптимизации, можно автоматизировать многие шаги проектирования оптической системы. Пучки состоят из отдельных лучей света, а их параметры на любом сечении системы можно получить при помощи функции trace_extract_terminus и ей подобных.

При помощи небольшого количества дополнительного кода можно будет производить расчеты диапазона автофокусировки и других задач.

Источник данных

[1] Построение идеальной оптики в Zemax [Электронный ресурс] // Научно-популярный журнал "Мир 3D world" : сайт. URL: http://mir-3d-world.ipo.spb.ru/2016/3dworld_4_2016.pdf (дата обращения: 05.11.2024).