Программа для оцифровки графиков, чертежей, рисунков: алгоритмы проекта «Репетитор: математика»

  • Tutorial

Содержание


Вступительное слово
Принцип работы
Описание программы
Финальный код программы
Преимущества работы с оцифрованными функциями на примерах
Эпилог



Вступительное слово


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

Для того, чтобы получить данные, нужно “оцифровать” такой график (или графический объект), другими словами, нужно получить набор абсцисс и ординат точек графика — далее над ними можно будет производить различные манипуляции: построить новый (качественный) график, производить вычисления, переведя его в новый формат (например, построив сплайн) и пр.

В проекте «Репетитор: математика» (почитайте статью на Хабрахабре — «Репетитор: математика» для подготовки к ЕГЭ и ВПР — от идеи до релиза. Рассказ об уникальном образовательном проекте) мы встретились с этой проблемой в двух основных видах:

  • “оцифровка” графика для того, чтобы сделать его соответствующим нашему стилю или просто сделать так, чтобы он выглядел прилично;
  • получение набора базовых точек для построения геометрических чертежей, гистограмм и пр. на основе авторского рисунка от руки (или с использованием простейших графических систем).

В этом посте приведен код созданной для этого функции graphicsDigitizing, а также кратко рассказывается о том, как она устроена. Также можно посмотреть как она работает вживую.

Принцип работы


Работа программы довольно проста. Необходимо поместить цифровую иллюстрацию (скан, фото, скриншот, другая картинка) на некоторое поле на “первый” слой. Далее выполняется еёручная обработка:

  • подбор рабочей области (прямоугольник) с заданием “реальных” координат его левой нижней и правой верхней вершины — другими словами области определения и значений той функции (графика, набора точек), который мы получим в результате;
  • простановка опорных точек, по которым будет построен B-сплайн заданной степени;
  • предпросмотр результата;
  • вывод результата в вычисляемом виде для дальнейшей работы.

Мы выбрали B-сплайны по нескольким причинам:

  • они дают гибкие кривые (если степень B-сплайна выше 1-й),
  • дают компактные аналитические представления кривых (кривые Безье — это вырожденный случай B-сплайнов, в которых каждая точка, если говорить просто, влияет на весь вид кривой);
  • просты в работе (реализованы во многих пакетах и не так сложны, если их потребуется запрограммировать с нуля самостоятельно).

На рисунке ниже показан набор точек (опорные точки), набор B-сплайнов 1-й, 2-й и 3-й степеней, а также кривая Безье (в показанной случае это по сути B-сплайн 7-й степени).



Для создания программы оцифровки графиков — как и для многих других задач — мы используем язык Wolfram Language.

Описание программы


Конечно, мы не будем описывать, как работает по шагам весь код программы, представленной ниже, это было бы очень долго и, как нам кажется, тех, кого заинтересует рассматриваемый вопрос или, более того, тех перед кем он остро стоит — сами смогут разобраться во всех “винтиках” кода, благо, он краток.

Мы же обратим внимание на его главные элементы.

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

CompoundExpression, ClearAll, SetDelayed, Optional, Pattern, Blank, Image, ImageResize, List, Rule, ColorSpace, With, Set, ImageDimensions, CreateDialog, ExpressionCell, DynamicModule, Grid, DefaultButton, DialogReturn, If, Less, Length, None, Drop, Transpose, Part, Span, ReplaceAll, Rescale, All, Min, Max, RuleDelayed, Real, Round, CopyToClipboard, BSplineCurve, SplineDegree, TabView, Row, Slider, Dynamic, Times, Pi, Power, ImageSize, Small, Slider2D, SetterBar, InputField, FieldSize, Alignment, Left, Spacings, Automatic, LocatorPane, Framed, Graphics, Inset, Cos, Sin, EdgeForm, Directive, AbsoluteThickness, RGBColor, Opacity, Yellow, Rectangle, Dashed, Blue, PlotRange, Plus, LocatorAutoCreate, True, Center, Top, Axes, AspectRatio, Initialization, Null, Condition, String, FileExistsQ, Import, BlankNullSequence, Echo, Inactive, Nothing

Одной из основных функций в работе данной программы является функция DynamicModule для создания интерактивных пространств и объектов. С её помощью происходит «оживление» всей конструкции.

Функция Grid необходима для организации пространства — она позволяет строить таблицы различной формы и размещать в их ячейках контент: тексты, слайдеры, иллюстрации, интерактивные объекты и т. д.

Функции LocatorPane, Slider, Slider2D, InputField служат для того, чтобы сделать интерактивные элементы — поле с выбором точек, слайдеры (одно- и двумерные, соответственно), поле ввода текста (для области определения функции).

Для «рисования» графическими примитивами служит функция Graphics (и если нужно в 3D — то Graphics3D).

И, конечно, самой важной здесь является функция BSplineCurve, которая позволяет представить набор точек в виде готовой кривой B-сплайна.



На видео ниже вы можете посмотреть то, как работает программа вживую:



Финальный код программы


Итоговая функция на самом деле не так велика, как могло бы показаться. Хотя это, конечно, прямое следствие больших возможностей Wolfram Language:

Посмотреть код


ClearAll[graphicsDigitizing];

graphicsDigitizing[image_Image: ImageResize[Image[{{{1,1,1}}},ColorSpace->"RGB"],{600,600}]]:=With[
{im=ImageResize[image,600]},

With[
{imdim=ImageDimensions[im]},

CreateDialog[{
ExpressionCell[
DynamicModule[
{degree,xmin=0,xmax=1,ymin=0,ymax=1,currentFunction,u,unew,udiap,\[Alpha],\[Beta]1,\[Beta]2},
Grid[
{
{
DefaultButton[
"Скопировать сплайн в буфер обмена",
DialogReturn[
With[
{var1=If[
Length[u]<4,
None,
unew=Drop[u,2];
udiap=Transpose[u[[1;;2]]];
Transpose[
{
Rescale[unew[[All,1]],{Min[udiap[[1]]],Max[udiap[[1]]]},{xmin,xmax}],
Rescale[unew[[All,2]],{Min[udiap[[2]]],Max[udiap[[2]]]},{ymin,ymax}]}
]/.x_Real:>Round[x,0.01]],var2=degree},
CopyToClipboard[BSplineCurve[var1,SplineDegree->var2]]]
]]
},

{
TabView[
{"Оригинал"->Grid[
{
{
Grid[
{
{
Row[{"Угол поворота:",Slider[Dynamic@\[Alpha],{-Pi/6,Pi/6},ImageSize->Small]}," "]
},
{
Row[{"Отступы:",Slider2D[Dynamic[\[Beta]1],{0.2 Max[imdim] {-1,-1},0.2 Max[imdim] {1,1}}],Slider2D[Dynamic[\[Beta]2],{0.2 Max[imdim] {-1,-1},0.2 Max[imdim] {1,1}}]}," "]
},
{
Row[{"Степень сплайна:",SetterBar[Dynamic@degree,{0,1,2,3,4}]}," "]
},
{
Grid[{{"\!\(\*SubscriptBox[\(x\), \(min\)]\) =",
InputField[Dynamic[xmin],FieldSize->{3,1}],"\!\(\*SubscriptBox[\(x\), \(max\)]\) =",
InputField[Dynamic[xmax],FieldSize->{3,1}]},{"\!\(\*SubscriptBox[\(y\), \(min\)]\) =",
InputField[Dynamic[ymin],FieldSize->{3,1}],"\!\(\*SubscriptBox[\(y\), \(max\)]\) =",
InputField[Dynamic[ymax],FieldSize->{3,1}]}}]
}},
Alignment->Left,
Spacings->{Automatic,1}],

LocatorPane[
Dynamic@u,
Framed@Dynamic@Graphics[{Inset[im,{0,0},{0,0},imdim,{Cos[\[Alpha]],Sin[\[Alpha]]}],
{EdgeForm[Directive[AbsoluteThickness[3],RGBColor[{214,0,0}/255]]],
Opacity[0.2,Yellow],
Rectangle[u[[1]],u[[2]]]},
{Dashed,Blue,AbsoluteThickness[4],
If[Length[u]<4,BSplineCurve[u[[1;;2]],SplineDegree->2],BSplineCurve[Drop[u,2],SplineDegree->degree]]
}},
PlotRange->Transpose[{\[Beta]1,imdim+\[Beta]2}],ImageSize->600],LocatorAutoCreate->True]
}
},Alignment->{Center,Top}
],
"Результат"->Dynamic@Graphics[
{Blue,AbsoluteThickness[2],
If[Length[u]<4,
BSplineCurve[u[[1;;2]],SplineDegree->2],
BSplineCurve[unew=Drop[u,2];
udiap=Transpose[u[[1;;2]]];
Transpose[
{Rescale[unew[[All,1]],{Min[udiap[[1]]],Max[udiap[[1]]]},{xmin,xmax}],
Rescale[unew[[All,2]],{Min[udiap[[2]]],Max[udiap[[2]]]},{ymin,ymax}]}],
SplineDegree->degree]]},
PlotRange->{{xmin,xmax},{ymin,ymax}},
ImageSize->600,
Axes->True,
AspectRatio->imdim[[2]]/imdim[[1]]]
},Alignment->Center]
}
}
],
Initialization:>(u={{0,0},imdim};\[Alpha]=0;
degree=3;\[Beta]1=0.1 Max[imdim] {-1,-1};\[Beta]2=0.1 Max[imdim] {1,1})]
]}];]];

graphicsDigitizing[image_String/;FileExistsQ[image]]:=graphicsDigitizing[Import[image]];

graphicsDigitizing[x___]:=(Echo[Inactive[graphicsDigitizing][x],"Не подходящие параметры: "];
Nothing)

Преимущества работы с оцифрованными функциями на примерах


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

Например, пусть дан график:



Давайте оцифруем его (см. видео выше) и произведем вычисления.

Для начала только заменим головную часть полученного в ходе работы программы выражения с BSplineCurve на BSplineFunction, которая строит аналитическое выражение с которым можно уже производить вычисления:



Единственный недостаток — это то, что такая функция нормирована на 1, т. е. функция f(t) при изменении t от 0 до 1 пробежит все свои значения, которыми являются точки B-сплайна:



Однако, с этим легко бороться. Достаточно построить интерполяционный полином:



После этого уже можно делать, что угодно.

Построить график, скажем, от 1 до 9:



Проинтегрировать функцию по всей области задания:



Построить график производной:



Найти длину кривой:



Или же аналитическое представление кривой (в параметрическом виде):


Код с иллюстрации
pts=Rationalize[{{0.01`,10.92`},{0.19`,16.36`},{0.34`,20.76`},{0.55`,25.17`},{0.96`,29.57`},{1.9100000000000001`,34.49`},{2.74`,38.63`},{3.38`,42.09`},{3.97`,44.03`},{4.47`,49.77`},{4.88`,56.5`},{5.29`,63.24`},{5.65`,69.19`},{6.0600000000000005`,74.63`},{6.53`,78.52`},{7.0200000000000005`,83.96000000000001`},{7.46`,86.55`},{8.02`,89.4`},{8.26`,86.55`},{8.67`,82.4`},{9.21`,80.85000000000001`},{9.57`,79.81`},{9.93`,79.81`}},1/100];

ptsL=Length[pts];

splineDegree=3;

knots=ConstantArray[0,splineDegree]~Join~Subdivide[ptsL-splineDegree]~Join~ConstantArray[1,splineDegree];

FullSimplify@Simplify[
Sum[
pts[[i+1]]PiecewiseExpand[BSplineBasis[{splineDegree,knots},i,t]],
{i,0,ptsL-1}]
]

А можно, конечно, и просто облагородить изначальный график:



Эпилог


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

Статья их цикла статей о проекте «Репетитор: математика» для подготовки к ЕГЭ и ВПР.
Trinity Digital & Баласс Group
50.40
Компания
Share post

Comments 2

    +1
    Исходя из моего опыта изложенного в этой статье данная программа явно имеет следующие недостатки:
    1. Нет окна лупы, для более точного позиционирования курсора при расстановке точек
    2. Отсутствует возможность выбора логарифмического масштаба осей
    3. Нет инструмента для построения дополнительной сетки, для оценки точности установки опорных точек и совпадения сетки графика с рассчитанной сеткой
    Пример из программы GetData Graph Digitize
    image
    Но эти недостатки с лихвой перекрываются тем фактом, что данная программа позволяет полностью перенести работу по оцифровке графиков в Wolfram Mathematica.
      0
      То что вы написали — это не сложно в реализации. Код программы можно расширять легко и добавлять нужный вам функционал. Для наших задач то о чем вы написали просто не нужно. Однако, когда я работал со своими знакомыми химиками — для них да, я помогал создавать программный комплекс на основе Wolfram Language для, в том числе, описанных вами задач. В науке и инженерии это, конечно, важно.

    Only users with full accounts can post comments. Log in, please.