<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Компьютерное зрение на С++. ФМЛ №239. 2021/2022</title>
    <description>Компьютерное зрение на С++. ФМЛ №239. 2021/2022</description>
    <link>http://polarnick.com/239/2021/index.html</link>
    <atom:link href="http://polarnick.com/blogs/239/2021/feed_ru.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Sun, 12 Jul 2026 17:53:14 +0000</pubDate>
    <lastBuildDate>Sun, 12 Jul 2026 17:53:14 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
    
      <item>
        <title>Урок 26: April Tag - этап 2: декодирование</title>
        <description>&lt;h1 id=&quot;два-этапа-детекция-и-декодирование&quot;&gt;Два этапа: детекция и декодирование&lt;/h1&gt;

&lt;p&gt;В &lt;a href=&quot;https://april.eecs.umich.edu/media/pdfs/olson2011tags.pdf&quot;&gt;статье&lt;/a&gt; две ключевые части:&lt;/p&gt;

&lt;p&gt;1) Детекция тэга (попытка найти темные четырехугольники - это внешние границы)&lt;/p&gt;

&lt;p&gt;2) Система декодирования обнаруженного тэга (в тэге можно хранить числа чтобы различать один тэг от другого)&lt;/p&gt;

&lt;p&gt;В &lt;a href=&quot;/blogs/239/2021/school239_11_2021_2022/2022/05/02/lesson25-april-tag.html&quot;&gt;прошлой статье&lt;/a&gt; обсуждается первый этап детекции.&lt;/p&gt;

&lt;p&gt;Сегодня же мы обсудим только второй этап - детекции, т.е. обнаружения тэгов.&lt;/p&gt;

&lt;p&gt;Итак предполагается что &lt;strong&gt;мы нашли много черных четырехугольников&lt;/strong&gt; - предполагаемых тэгов. Теперь надо для каждого из них решить две задачи:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Правда ли что это тэг?&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Декодировать уникальный идентификатор этого тэга&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;вытаскиваем-внутреннюю-часть-тэга&quot;&gt;Вытаскиваем внутреннюю часть тэга&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;We compute the 3×3 homography matrix that projects 2D
points in homogeneous coordinates from the tag’s coordinate
system ... to the 2D image coordinate system.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Итак раз мы знаем у тэга границы, то мы знаем его углы, находим матрицу гомографии \(H\) как делали это раньше (при сопоставлении SIFT-ключевых точек).&lt;/p&gt;

&lt;p&gt;Но теперь у нас всего четыре “ключевые” точки - это углы тэга на картинке которые хотят перейти в новую систему координат - в углы идеального квадрата:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/05/apriltag/06homography.jpg&quot; alt=&quot;Homography&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Теперь мы работаем с правой картинкой. Обратите внимание что там может быть не тэг а полная ерунда в случае если это &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ложно-положительное&lt;/code&gt; срабатывание.&lt;/p&gt;

&lt;p&gt;Давайте бинаризируем наш тэг - теперь все яркие пиксели белые, а все темные пиксели - черные. Затем рассечем наш квадрат на 8 линий и 8 столбцов.&lt;/p&gt;

&lt;p&gt;В каждой образовавшейся клетке либо \(0\) либо \(1\) - в зависимости от того каких пикселей больше - черных или белых.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/05/apriltag/07decoding.jpg&quot; alt=&quot;Homography&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Заметьте что крайние ячейки всегда должны быть заполнены нулями (это черный край помогающий нашему первому этапу - обнаружению тега).&lt;/p&gt;

&lt;p&gt;Мы можем например считать что это не тег, если хотя бы одна из крайних ячеек не засчиталась как \(0\) (хотя должна быть равна \(0\) в случае корректного тэга).&lt;/p&gt;

&lt;h1 id=&quot;кодировка&quot;&gt;Кодировка&lt;/h1&gt;

&lt;p&gt;Пусть у нас есть &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;6x6&lt;/code&gt; бит (из внутренних клеток). Выпишем их в ряд - получим &lt;strong&gt;36 битное число&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Теперь речь пойдет не про обработку картинок, а про то как кодировать информацию с учетом того что информация могла быть передана с ошибками (ошибками декодирования или шумом в фотографии или это может быть вызвано несовершенством алгоритма).&lt;/p&gt;

&lt;p&gt;Более того - мы хотим понимать в случае когда полученные биты не имеют смысла и являются шумом - т.е. хотим отдать долг ложным срабатываниям обнаружения тега - и поняв что это на самом деле не тег а ошибка - проигнорировать его.&lt;/p&gt;

&lt;p&gt;Как же тогда кодировать числа?&lt;/p&gt;

&lt;h1 id=&quot;lexicode---устойчивость-к-ошибкам&quot;&gt;Lexicode - устойчивость к ошибкам&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;We propose the use of a modified lexicode [24]. Classical
lexicodes are parameterized by two quantities: the number
of bits n in each codeword and the minimum Hamming
distance between any two codewords d. Lexicodes can
correct ⌊(d − 1)/2⌋ bit errors and detect d/2 bit errors.
For convenience, we will denote a 36 bit encoding with a
minimum Hamming distance of 10 (for example) as a 36h10
code
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Итак они предлагают из всех возможных &lt;strong&gt;36 битных чисел&lt;/strong&gt; оставить для использования только такие, что между любыми двумяи из них есть хотя бы \(d\) разных бит.&lt;/p&gt;

&lt;p&gt;В таком случае предложенная кодировка сможет гарантировать следующие свойства:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Результат будет корректен если у нас меньше чем \((d-1)/2\) ошибок в битах&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Ошибка в результате будет обнаружена если у нас меньше чем \(d/2\) ошибок в битах&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Для удобства кодировку на &lt;strong&gt;36 битах&lt;/strong&gt; с \(d=10\) они называют &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;36h10&lt;/code&gt; (буква h - от слова Hamming, т.к. число разных бит - это расстояние Хэмминга).&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Lexicodes derive their name from the heuristic used to
generate valid codewords: candidate codewords are considered
in lexicographic order (from smallest to largest), adding
new codewords to the codebook when they are at least a
distance d from every codeword previously added to the
codebook. While very simple, this scheme is often very close
to optimal [25].
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Как же найти подходящие нам &lt;strong&gt;36 битные числа&lt;/strong&gt;? Как составить словарь?&lt;/p&gt;

&lt;p&gt;Предлагается жадным образом добавлять очередное число в словарь только если это слово не сломает правило про минимальное рассточние Хэмминга между словами (хотя бы \(d\) разных бит).&lt;/p&gt;

&lt;h1 id=&quot;устойчивость-к-повороту-тэга&quot;&gt;Устойчивость к повороту тэга&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;In the case of visual fiducials, the coding scheme must be
robust to rotation. In other words, it is critical that when
a tag is rotated by 90, 180, or 270 degrees, that it still
have a Hamming distance of d from every other code. The
standard lexicode generation algorithm does not guarantee
this property. However, the standard generation algorithm
can be trivially extended to support this: when testing a
new candidate codeword, we can simply ensure that all four
rotations have the required minimum Hamming distance.
The fact that the lexicode algorithm can be easily extended
to incorporate additional constraints is an advantage of our
approach.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Но обратите так же внимание что тэг может поворачиваться, мы хотим не путать из-за этого что за число нам показывают.&lt;/p&gt;

&lt;p&gt;В таком случае жадное порождение словарика надо лишь слегка расширить - слово добавляется только если оно и 3 его слова-побратима (полученных поворотами на 90 градусов) тоже проходят проверку на минимальное Хэмминг расстояние со всеми остальными словами (включая их слова-побратимы).&lt;/p&gt;

&lt;h1 id=&quot;устойчивость-к-встречаемому-в-природе&quot;&gt;Устойчивость к встречаемому в природе&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Some codewords, despite satisfying the Hamming distance
constraint, are poor choices. For example, a code word
consisting of all zeros would result in a tag that looks like
a single black square. Such simple geometric patterns commonly
occur in natural scenes, resulting in false positives.
The ARTag encoding system, for example, explicitly forbids
two codes because they are too likely to occur by chance
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Некоторые кодовые слова могут быть не специальным тэгом - а просто совпадением. Например тэг полностью из нулей - это просто черный квадрат, в таком случае например стол может быть ошибочно интерпретирован как такой ненадежный тэг.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Rather than identify problematic tags manually, we further 
modify the lexicode generation algorithm by rejecting
candidate codewords that result in simple geometric patterns.
Our metric is based on the number of rectangles required to
generate the tag’s 2D pattern.
For example, a solid pattern
requires just one rectangle, while a black-white-black stripe
would require two rectangles (one large black rectangle with
a smaller white rectangle drawn second). 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Чтобы автоматически оценить “простоту” или “сложность” рассматриваемого паттерна - они решают задачу “сколько прямоугольников нужно чтобы нарисовать такой тэг”.&lt;/p&gt;

&lt;h1 id=&quot;итого&quot;&gt;Итого&lt;/h1&gt;

&lt;p&gt;Итого мы используем подмножество &lt;strong&gt;36-битных чисел&lt;/strong&gt; такое что между любыми двумя кодами есть большая разница - много бит выглядят по-разному. Кроме того все используемые коды выглядят достаточно сложно чтобы мы крайне мало шансов имели встретить что-то в мире случайно оказавшееся похожим на корректный тэг.&lt;/p&gt;

&lt;p&gt;Хотя генерация словаря корректных тэгов с учетом всех описанных деталей может быть вычислительно тяжелой и медленной - это не страшно, т.к. достаточно сделать это один раз и сохранить перечень получившихся кодов в файл-словарь.&lt;/p&gt;

&lt;p&gt;Пример сколько корректных тэгов при допустимом минимальном расстоянии Хэмминга \(d=9\) и \(d=10\) между кодами в нашем словариками:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/05/apriltag/08uniqueCodesTable.png&quot; alt=&quot;Homography&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 02 May 2022 21:03:00 +0000</pubDate>
        <link>http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/05/02/lesson26-april-tag-decoding.html</link>
        <guid isPermaLink="true">http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/05/02/lesson26-april-tag-decoding.html</guid>
        
        
        <category>blogs</category>
        
        <category>239</category>
        
        <category>2021</category>
        
        <category>school239_11_2021_2022</category>
        
      </item>
    
    
    
      <item>
        <title>Урок 25: April Tag - этап 1: обнаружение</title>
        <description>&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://april.eecs.umich.edu/software/apriltag&quot;&gt;Сайт AprilTag&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Статья которую мы будем реализовывать - &lt;a href=&quot;https://april.eecs.umich.edu/media/pdfs/olson2011tags.pdf&quot;&gt;AprilTag: A robust and flexible visual fiducial system, Edwin Olson, 2011&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Мы обсудим как решить следующую задачу - пусть мы можем нарисовать (обычно распечатать на листе бумаги или пластика) какие-то специальные картинки
(тэги), возможно даже несколько - каждая из этих картинок имеет свой идентификатор (код, по сути просто уникальное число).
И теперь мы хотим эти картинки быстро и надежного распознавать на фотографии. В т.ч. хотим понимать какой из тэгов где.&lt;/p&gt;

&lt;p&gt;Можно было бы решать это через ключевые точки общего назначения (&lt;strong&gt;SIFT&lt;/strong&gt; и т.п.), но они работают довольно медленно и кроме того оказывается что
если разработать специализированный тэг - то он будет гораздо лучше обнаружаться и позволит надежно работать даже в сложных условиях.&lt;/p&gt;

&lt;p&gt;Пример как выглядят &lt;strong&gt;AprilTag&lt;/strong&gt; (обратите внимание что справа на тэги наложены расшифрованные идентификаторы - числа которые были декодированы из обнаруженных тэгов):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/05/apriltag/01example.jpg&quot; alt=&quot;AprilTag examples&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;два-этапа-детекция-и-декодирование&quot;&gt;Два этапа: детекция и декодирование&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Our system is composed of two major components: the tag
detector and the coding system. In this section, we describe
the detector whose job is to estimate the position of possible
tags in an image. Loosely speaking, the detector attempts to
find four-sided regions (“quads”) that have a darker interior
than their exterior. The tags themselves have black and white
borders in order to facilitate this (see Fig. 2).
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Итак, в статье две ключевые части:&lt;/p&gt;

&lt;p&gt;1) Детекция тэга (попытка найти темные четырехугольники - это внешние границы)&lt;/p&gt;

&lt;p&gt;2) Система декодирования обнаруженного тэга (в тэге можно хранить числа чтобы различать один тэг от другого)&lt;/p&gt;

&lt;p&gt;Сегодня мы обсудим только первый этап - детекции, т.е. обнаружения тэгов.&lt;/p&gt;

&lt;h1 id=&quot;не-хотим-пропустить-тэг-пусть-и-ценой-обнаружения-иллюзий&quot;&gt;Не хотим пропустить тэг пусть и ценой обнаружения иллюзий&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Note that the quad detector is designed to have a very
low false negative rate, and consequently has a high false
positive rate. We rely on the coding system (described in
the next section) to reduce this false positive rate to useful
levels.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Любопытное решение - детектор планировалось сделать так чтобы он максимально редко выдавал &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ложно-отрицательный&lt;/code&gt; результат
(т.е. редко не обнаруживал тэг, пусть и ценой перегиба в сторону обнаружения тэга там где его нет - т.е. пусть и ценой частого
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ложно-положительного&lt;/code&gt; результата).&lt;/p&gt;

&lt;p&gt;Но раз мы почти всегда видим тег там где он есть, и довольно часто видим тег там где его нет - как с этим работать?
Как не страдать от иллюзий? Давайте просто полагаться что на второй ключевой части - системе декодирования - мы поймем
что это не тег, а какая-то случайная ерунда, и просто отсечем такие ложно-положительные результаты.&lt;/p&gt;

&lt;p&gt;Т.о. с одной стороны мы почти никогда не пропускаем тэги (редки &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ложно-отрицательные&lt;/code&gt; случаи) пусть и ценой частых &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ложно-положительных&lt;/code&gt; случаев,
которые мы устраняем на следующем этапе обработки - на этапе декодирования номера тэга мы планируем понять что это не тэг а какая-то ерунда.&lt;/p&gt;

&lt;p&gt;P.S. на тему ложно-негативных и т.п. результатов:&lt;/p&gt;

&lt;p&gt;Бывают положительные и отрицательные результаты работы алгоритма:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Положительный&lt;/code&gt; (не обязательно это хорошо) - нашли тег на картинке (при этом он там может быть на самом деле, или это иллюзия)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Отрицательный&lt;/code&gt; (не обязательно это плохо) - не нашли тег на картинке (при этом его там может не быть на самом деле, или же есть но мы его не заметили)&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;А с учетом того что &lt;strong&gt;на самом деле&lt;/strong&gt; на картинке - результат работы алгоритма может быть поделен на &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;истинные&lt;/code&gt; и &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ложные&lt;/code&gt; (когда ошибся), вот и выходит:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Истинно-положительный&lt;/code&gt; - когда алгоритм нашел тэг на картинке, и это правильный ответ, нашел действительно тэг а не какую-то ерунду&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ложно-положительный&lt;/code&gt; - когда алгоритм думал что нашел тэг на картинке, но это неправильный ответ, а алгоритм нашел что-то (может быть) похожее на тэг, но не тэг&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Истинно-отрицательный&lt;/code&gt; - когда алгоритм решил что на картинке нет тэга, и это правильный ответ&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ложно-отрицательный&lt;/code&gt; - когда алгоритм думает что на картинке нет тэга, но это не так - тэг есть, просто алгоритм его не заметил (т.е. ошибся)&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;3-обнаружаем-границы-отрезки&quot;&gt;3) Обнаружаем границы-отрезки&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/05/apriltag/02detection.jpg&quot; alt=&quot;Detecting line segments&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;..., computes the gradient direction and magnitude at every pixel
(see Fig. 3) and agglomeratively clusters the pixels into
components with similar gradient directions and magnitudes.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Итак в целом обнаружение границ-отрезков задающих четырехугольник тэга находится через нахождение по-пиксельных &lt;strong&gt;градиентов&lt;/strong&gt; и
постепенного объединения их в компоненты (&lt;strong&gt;кластеризация&lt;/strong&gt;) на основе похожести направления и силы градиентов.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;The clustering algorithm ...: a graph is created in which
each node represents a pixel. Edges are added between
adjacent pixels with an edge weight equal to the pixels’
difference in gradient direction.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;1) Итак создается граф: вершина=пиксель, ребра=смежные пиксели, длина ребра=разница в направлении градиентов смежных пикселей&lt;/p&gt;

&lt;p&gt;Изначально каждый пиксель в своей компоненте (в своем кластере).&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;These edges are then sorted and
processed in terms of increasing edge weight: for each edge,
we test whether the connected components that the pixels
belong to should be joined together.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2) Все ребра сортируются и обрабатываются по возрастанию их длины, т.е. сначала обрабатываем пары пикселей с похожими градиентами.&lt;/p&gt;

&lt;p&gt;Для очередного ребра мы делаем следующее:&lt;/p&gt;

&lt;p&gt;2.1) Смотрим какие пиксели он соединяет, смотрим в каких двух компонентах эти пиксели состоят&lt;/p&gt;

&lt;p&gt;2.2) Хотим решить надо ли объединить эти две компоненты связности друг с другом в одну общую компоненту связности
(т.е. мы пытаемся понять - эти два набора пикселей - один и тот же связный отрезок, или два разных)&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Given a component n,
we denote the range of gradient directions as D(n) and the
range of magnitudes as M(n). Put another way, D(n) and
M(n) are scalar values representing the difference between
the maximum and minimum values of the gradient direction
and magnitude respectively.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2.3) У каждой компоненты связности \(n\) есть множество пикселей которые в нее входят, у каждого пикселя есть свой градиент.
Тогда у компоненты есть \(D(n)\)=диапазон направлений (\(D\)=Direction=направление) этих градиентов,
\(M(n)\)=диапазон силы, т.е. длины (\(M\)=Magnitude) этих градиентов.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Given two components n and m,
we join them together if both of the conditions below are
satisfied
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/05/apriltag/03formula1.png&quot; alt=&quot;Components join criteria&quot; /&gt;&lt;/p&gt;

&lt;p&gt;2.4) Итак мы объединяем две компоненты связности если после их объединения диапазон направлений градиентов и диапазон силы градиентов
не сильно увеличиться &lt;strong&gt;у обеих&lt;/strong&gt; компонент (поэтому справа минимум).&lt;/p&gt;

&lt;p&gt;Не сильно увеличится - задается вторым слагаемым в приведенных выше формулах, и по сути это константа деленная на
суммарное число пикселей. \(K_D=100\) (в градусах) и \(K_M=1200\) (в пикселях).&lt;/p&gt;

&lt;p&gt;Обратите внимание - зачем нам делать дозволенное увеличение диапазонов все меньше и меньше по мере увеличения компоненты?
Что будет в начале? Компоненты из одиночных пикселей - мы их объединяем если градиенты хоть сколько-то не в противоположном направлении (плюс-минус 50 градусов - дозволенная разница).
Что будет в конце? Огромные компоненты там где истинные отрезки (с четким пониманием какое направление и сила градиентов, т.е. малые диапазоны), и много мелких компонент в остальных местах.&lt;/p&gt;

&lt;p&gt;Благодаря таким формулам-критериям огромные компоненты (по сути отрзеки) не будут объединяться с мелкими компонентами (по сути шум).
За исключением случая когда эти мелкие компоненты покрывают продолжение отрезка этих огромных компонент (т.е. случая когда их градиенты похожи на градиенты большой компоненты).&lt;/p&gt;

&lt;p&gt;Т.е. чем больше у нас пикселей в компоненте - тем сильнее мы уверены что диапазон градиентов определн правильно, а значит разрешаются только объединения
которые очень слабо увеличивают диапазон (отсюда деление на общее число пикселей).&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;This gradient-based clustering method is sensitive to noise
in the image: even modest amounts of noise will cause local
gradient directions to vary, inhibiting the growth of the components.
The solution to this problem is to low-pass filter the image
... We recommend a value of σ = 0.8.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Обратите внимание что даже малый шум может существенно влиять на направления градиентов, а наш алгоритм к такому чувствителен.
Достаточно слегка поблюрить картинку чтобы это побороть.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Once the clustering operation is complete, line segments
are fit to each connected component using a traditional
least-squares procedure, weighting each point by its gradient
magnitude (see Fig. 3). We adjust each line segment so that
the dark side of the line is on its left, and the light side is
on its right. In the next phase of processing, this allows us
to enforce a winding rule around each quad.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2.5) Когда все ребра обработаны и компоненты связности определены -  в каждой компоненты связности надо найти “общий отрезок-прямую”.
Предлагается это сделать с помощью &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%BD%D0%B0%D0%B8%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D1%85_%D0%BA%D0%B2%D0%B0%D0%B4%D1%80%D0%B0%D1%82%D0%BE%D0%B2&quot;&gt;метода наименьших квадратов&lt;/a&gt;,
и это стандартный хороший метод, но давайте сделаем это просто и наивно - через RANSAC.&lt;/p&gt;

&lt;p&gt;Для удобства в следующем этапе обработки удобно фиксировать направление отрезка так чтобы “слева” от направленного отрезка была черная зона, а справа - белая.
Тогда мы знаем что тэги (а они черные) огибаются &lt;strong&gt;против часовой стрелки&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/05/apriltag/04windingRule.jpg&quot; alt=&quot;Winding rule&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;4-обнаружаем-четырехугольники&quot;&gt;4) Обнаружаем четырехугольники&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/05/apriltag/05clustersResults.jpg&quot; alt=&quot;Clusters results&quot; /&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;At this point, a set of directed line segments have been
computed for an image. The next task is to find sequences
of line segments that form a 4-sided shape, i.e., a quad. The
challenge is to do this while being as robust as possible to
occlusions and noise in the line segmentations.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Итак, у нас есть компоненты связности задающие отдельно взятые отрезки на нашей картинке.
Из них мы хотим собрать четырехугольники которые могли бы оказаться искомыми тэгами.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Our approach is based on a recursive depth-first search
with a depth of four: each level of the search tree adds an
edge to the quad. At depth one, we consider all line segments.
At depths two through four, we consider all of the line
segments that begin “close enough” to where the previous
line segment ended and which obey a counter-clockwise
winding order. Robustness to occlusions and segmentation
errors is handled by adjusting the “close enough” threshold:
by making the threshold large, significant gaps around the
edges can be handled. Our threshold for “close enough” is
twice the length of the line plus five additional pixels. This
is a large threshold which leads to a low false negative rate,
but also results in a high false positive rate.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Предлагается делать перебор который немного напоминает поиск в глубину (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DFS&lt;/code&gt;) - мы хотим найти четыре отрезка, вложенно перебирая их и отсекая комбинации которые нам не подходят.&lt;/p&gt;

&lt;p&gt;Критерий отсечения:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Вот мы выбрали первый отрезок - какой отрезок подходит на роль второго (следующего за первым)? Тот у которого начало &lt;strong&gt;недалеко&lt;/strong&gt; от конца первого. Здесь нам очень помогает &lt;strong&gt;порядок обхода&lt;/strong&gt; сделанный ранее, иначе мы бы не знали где у отрезка “начало” а где “конец”&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Тэги темные, мы их обходим &lt;strong&gt;против часовой стрелки&lt;/strong&gt;, значит у следующего отрезка направление должно заворачивать налево (относительно предыдущего)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Вот мы выбрали все четыре отрезка - надо проверить что четвертый отрезок закончился где-то &lt;strong&gt;недалеко&lt;/strong&gt; от начала первого&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Чем больше это самое &lt;strong&gt;недалеко&lt;/strong&gt; - тем более чувствителен алгоритм обнаружения - чаще находит тэги, но и чаще находит мусор.&lt;/p&gt;

&lt;p&gt;В статье используют &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;удвоенную длину отрезка плюс 5 пикселей&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Обратите внимание что перебирать отрезки чье начало недалеко от конца предыдущего отрезка - можно очень быстро (используя алгоритмические структуры вроде &lt;a href=&quot;https://ru.wikipedia.org/wiki/K-d-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE&quot;&gt;Kd-деревьев&lt;/a&gt;), но для упрощения предлагается просто явно пробегать по всем отрезкам с зажатым порогом.&lt;/p&gt;

&lt;p&gt;Кроме того вероятно из компонент связности нас интересуют только те у которых достаточно большое число пикселей.&lt;/p&gt;
</description>
        <pubDate>Mon, 02 May 2022 21:02:00 +0000</pubDate>
        <link>http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/05/02/lesson25-april-tag.html</link>
        <guid isPermaLink="true">http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/05/02/lesson25-april-tag.html</guid>
        
        
        <category>blogs</category>
        
        <category>239</category>
        
        <category>2021</category>
        
        <category>school239_11_2021_2022</category>
        
      </item>
    
    
    
      <item>
        <title>Урок 24: многопоточность - OpenMP</title>
        <description>&lt;p&gt;Для C++ существует API позволяющее очень красиво распараллелить вычисления на многоядерную архитектуру процессора - &lt;strong&gt;OpenMP&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Это не единственная модель вычислений, часто удобно просто явным образом запустить отдельный поток вычислений, или явно работать с защелкой (lock), или использовать другие примитивы.
Но сегодня мы обсудим один из самых популярных и часто используемых вариантов в случае когда речь идет о C++ и обработке картинок - распараллеливание обработки в цикле.&lt;/p&gt;

&lt;p&gt;P.S. не забудьте компилировать это задание в &lt;a href=&quot;/blogs/239/2021/school239_11_2021_2022/2021/10/05/lesson5-disjoint-set.html&quot;&gt;Release&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;распараллеливание-цикла&quot;&gt;Распараллеливание цикла&lt;/h1&gt;

&lt;p&gt;Представим что есть цикл который увеличивает каждый элемент вектора в два раза:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Вот такое указание скажет компилятору что мы хотим выполнить цикл в несколько потоков чтобы использовать все ядра (и виртуальные потоки) процессора.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;#pragma omp parallel for // omp = OpenMP, parallel = запустить потоки, for = распределить по ним индексы этого цикла
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Иначе говоря каждый индекс &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt; для которого этот цикл должен быть выполнен - достанется для выполнения одному из потоков.&lt;/p&gt;

&lt;h1 id=&quot;критическая-секция&quot;&gt;Критическая секция&lt;/h1&gt;

&lt;p&gt;Что если мы хотим суммировать все числа массива? В чем проблема такого, казалось бы, естественного решения?&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;totalSum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#pragma omp parallel for // parallel = запустить потоки, for = распределить по ним индексы этого цикла
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;totalSum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;В том что здесь происходит состояние гонки при считывании и записи текущего значения &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;totalSum&lt;/code&gt;. Ведь мы одновременно делает это из разных потоков.&lt;/p&gt;

&lt;p&gt;Как это можно исправить? Правильно, воспользовавшись &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lock&lt;/code&gt;-ом (защелкой, по сути замком на двери уборной), который будет гарантировать нам что лишь один из потоков сейчас работает с переменной &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;totalSum&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;totalSum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// используем long long (64-битное число) чтобы при суммировании int - не переполнился результат&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#pragma omp parallel for
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;#pragma omp critical // omp = OpenMP, critical = это критическая секция кода, в нее потоки заходят по одному
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;totalSum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Какова будет производительность (скорость работы) такого кода? Почему?&lt;/p&gt;

&lt;p&gt;Как бы это сделать лучше?&lt;/p&gt;

&lt;h1 id=&quot;reduction&quot;&gt;Reduction&lt;/h1&gt;

&lt;p&gt;Очень часто хочется делать подобные операции как в прошлой функции - объединение результата между разными потоками.&lt;/p&gt;

&lt;p&gt;Т.е. по сути хочется применить некую операцию между результатами работы всех потоков, такое преобразование называется &lt;strong&gt;редукция&lt;/strong&gt; (&lt;strong&gt;reduction&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Причем операции бывают разные - например суммирование (или перемножение, взятие максимума, взятие минимума).&lt;/p&gt;

&lt;p&gt;Если мы укажем что потоки должны провести редукцию над &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;totalSum&lt;/code&gt;, то каждый поток сделает свою личную копию этой переменной, будет суммировать свои элементы в эту копию (по сути накапливая свою частичную сумму), и когда весь цикл будет обработан - все потоки соберутся, покажут друг другу накопленное значение этой переменной, и объединив результаты - запишут в оригинальную переменную &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;totalSum&lt;/code&gt; общий результат.&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;totalSum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#pragma omp parallel for reduction(+: totalSum) // в скобочках идут параметры редукции - сначала идет операция, затем - название переменной-аккумулятора
&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;totalSum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Насколько быстро работает такой код относительно однопоточной версии и версии с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;critical&lt;/code&gt; секцией? Почему?&lt;/p&gt;

&lt;p&gt;Какие еще операции ложаться на эту парадигму? Какое свойство от них требуется? Ложиться ли например на эту идею операция вычитания? А взятие наибольшего общего делителя?&lt;/p&gt;

&lt;h1 id=&quot;самописный-reduction&quot;&gt;Самописный Reduction&lt;/h1&gt;

&lt;p&gt;Что нужно чтобы суметь написать редукцию самостоятельно (просто из любопытства и чтобы научиться писать более сложные вещи)?&lt;/p&gt;

&lt;p&gt;1) Запустить каждый поток&lt;/p&gt;

&lt;p&gt;2) В каждом потоке завести аккумулятор для частичной суммы (его личная копилка)&lt;/p&gt;

&lt;p&gt;3) Пройтись по циклу и в каждом потоке добавлять элементы в свою переменную&lt;/p&gt;

&lt;p&gt;4) Просуммировать все частичные суммы в общую переменную&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;totalSum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;threadsN&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;#pragma omp parallel // ЗДЕСЬ НЕТ for, но есть parallel = говорим что эту секцию хочется запустить для каждого потока процессора
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;threadId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;#pragma omp critical // в критической секции рассчитаемся по номерам потоков и выведем в консоль что такой-то поток был запущен
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;    
        &lt;span class=&quot;n&quot;&gt;threadId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;threadsN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Thread #&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;threadId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; started...&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;threadsN&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;threadSum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;#pragma omp for // ЗДЕСЬ НЕТ parallel, т.к. это ключевое слово говорит &quot;запускай потоки&quot;, но потоки уже запущены,
&lt;/span&gt;    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// осталось лишь распределить среди них вычислительную рабочую нагрузку&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;threadSum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// почему здесь не нужна критическая секция?&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;cp&quot;&gt;#pragma omp critical
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;totalSum&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;threadSum&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Thread #&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;threadId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot; finished!&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;А зачем здесь первая критическая секция? Нельзя ли ее сделать без синхронизации? Попробуйте.&lt;/p&gt;

&lt;p&gt;А зачем здесь вторая критическая секция? Нельзя ли ее сделать без синхронизации? Попробуйте.&lt;/p&gt;

&lt;h1 id=&quot;как-распределяются-вычисления&quot;&gt;Как распределяются вычисления?&lt;/h1&gt;

&lt;p&gt;Как выяснить какие индексы цикла достаются потоку при вычислении? Попробуйте придумать эксперимент который позволит это выяснить опытным путем.&lt;/p&gt;
</description>
        <pubDate>Mon, 25 Apr 2022 21:02:00 +0000</pubDate>
        <link>http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/04/25/lesson24-openmp.html</link>
        <guid isPermaLink="true">http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/04/25/lesson24-openmp.html</guid>
        
        
        <category>blogs</category>
        
        <category>239</category>
        
        <category>2021</category>
        
        <category>school239_11_2021_2022</category>
        
      </item>
    
    
    
      <item>
        <title>Урок 23: многопоточность (lock, критическая секция)</title>
        <description>&lt;h1 id=&quot;однопоточоная-модель-вычислений&quot;&gt;Однопоточоная модель вычислений&lt;/h1&gt;

&lt;p&gt;Все это время мы мыслили в модели вычислений когда программа шаг за шагом выполняет одну инструкцию за другой.&lt;/p&gt;

&lt;p&gt;Могут быть команды которые меняют порядок выполнения инструкций:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;for&lt;/code&gt; позволяет выполнять некоторый набор последовательных команд пока выполняется некоторое правило (предикат),&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; позволяет выполнять набор команд только в некоторых случаях,&lt;/li&gt;
  &lt;li&gt;вызов функции может выполнить прыжок (в ассемблере для этого используется инструкция &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jump&lt;/code&gt;) исполнения команд в другое место&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;выглядит это примерно так - есть набор инструкций, есть указатель на инструкцию выполняемую в данный момент (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;instruction pointer&lt;/code&gt;) и оперативная память которую эти инструкции меняют:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/04/multithreading/1singlethreaded.jpg&quot; alt=&quot;однопоточная модель вычислений&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;многопоточная-модель-вычислений&quot;&gt;Многопоточная модель вычислений&lt;/h1&gt;

&lt;p&gt;Но скорость работы программы определяется тем сколько команд в секунду выполняет процессор.
В какой-то мере эта скорость определяется частотой процессора.
Например процессор с частотой &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3 GHz&lt;/code&gt; может выполнять примитивные команды со скоростью около &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3 миллиарда примитивных операций в секунду&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;И такая частота работы процессора была достигнута уже около 2004 года, и с тех пор росла несущественно, хотя до 2004 года довольно много лет (условно 10-20 лет)
частота процессора росла быстро - примерно в два раза каждые два года. См. так же &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%BA%D0%BE%D0%BD_%D0%9C%D1%83%D1%80%D0%B0&quot;&gt;Закон Мура&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Как же продолжать получать достигать увеличения мощности процессоров? Ведь это необходимо например для серверного рынка.&lt;/p&gt;

&lt;p&gt;Давайте скажем что одновременно у нас выполняется много программ, пользователь может одновременно смотреть youtube, слушать музыку, играть в сапера и т.п., все эти программы работают параллельно.
Что если создать в процессоре несколько вычислительных ядер - каждое из которых будет работать как мы привыкли - в модели &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;однопоточного вычисления&lt;/code&gt; - &lt;strong&gt;независимо&lt;/strong&gt; от других ядер?
Мы получим ускорение, ведь теперь не придется делить силы одного ядра на все эти программы, мы получили увеличение общей вычислений мощности во столько раз сколько ядер в процессоре.&lt;/p&gt;

&lt;p&gt;Но что если мы хотим ускорить работу одной отдельно взятой программы? Что если мы хотим часть задачи считать в одном потоке (на одном ядре), другую часть задачи считать в другом потоке (на другом ядре) и за счет этого получить ускорение?
Тогда нам нужно уметь обмениваться данными между этими потоками - например чтобы находить общий ответ в вычисляемой задаче (представьте что мы хотим просуммировать числа массива).
Для этого у нас уже есть оперативная память, но возникает одна большая проблема.&lt;/p&gt;

&lt;h1 id=&quot;проблема-гонки-race-condition&quot;&gt;Проблема гонки (Race condition)&lt;/h1&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Пример с Jenga&lt;/code&gt; Представьте что у нас есть Вася и Петя. Каждый из них достиг невиданных высот в мастерстве игры в Дженгу:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/04/multithreading/2jenga.jpg&quot; alt=&quot;Jenga&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Каждый из них всегда делал свой ход безукоризненно, всегда грамотно выбирал какую деревяшку можно вытащить, и успешно ее вытаскивал!&lt;/p&gt;

&lt;p&gt;Но что если Вася и Петя сделают ход абсолютно синхронно? Что если они выберут две деревяшки с одного и того же уровня, или хуже того - просто одну и ту же деревяжку?&lt;/p&gt;

&lt;p&gt;Тогда они помешают друг другу и результат может быть непредсказуем. Было бы лучше им сначала решить кто ходит первый, а кто второй. 
Тем самым гарантировав что в любой момент с башней взаимодействует не больше одного игрока, а значит никто ему не помешает.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Пример с банком&lt;/code&gt; Представим что есть два банкомата и многоликий Коля. Этот многоликий Коля может одновременно быть везде и нигде. Взаимодействовать абсолютно синхронно с любым числом предметов, даже с банкоматами.&lt;/p&gt;

&lt;p&gt;При этом банк из-за большого числа клиентов все транзакции из банкоматов выполняет на разных ядрах процессора (их могут быть тысячи).&lt;/p&gt;

&lt;p&gt;В таком случае может случиться следующее:&lt;/p&gt;

&lt;p&gt;1) У Коли была 1000 рупий на счету&lt;/p&gt;

&lt;p&gt;2) Он синхронно с банкомата А снял 100 рупий, и с банкомата Б снял 200 рупий&lt;/p&gt;

&lt;p&gt;3) Каждое снятие выполняется на отдельном вычислительном ядре процессора, но то сколько денег на счету Коли хранится в одной и той же переменной &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;счет&lt;/code&gt; в оперативной памяти&lt;/p&gt;

&lt;p&gt;4) Кроме того каждое снятие это три инструкции процессора:&lt;/p&gt;

&lt;p&gt;4.1) считать информацию из оперативной памяти о количестве денег на счете&lt;/p&gt;

&lt;p&gt;4.2) проверить достаточно ли денег&lt;/p&gt;

&lt;p&gt;4.3) посчитать сколько денег осталось&lt;/p&gt;

&lt;p&gt;4.4) сохранить в оперативной памяти новое число на счете&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/04/multithreading/3bankomats.jpg&quot; alt=&quot;Гонка банкоматов&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Но в таком случае на счете сохранится то ли 900 рупий, то ли 800 рупий. Банку очень не понравится такая многопоточность!&lt;/p&gt;

&lt;p&gt;Подобная ошибка, или даже скорее - подобная ситуация и состояние называется &lt;strong&gt;состоянием гонки (Race condition)&lt;/strong&gt;.
Ведь два потока буквально соревнуются “кто первый!”, и ирония в том что побеждает скорее тот кто последний -
ведь именно его результаты будут сохранены в памяти. При этом обычно результат зависит от воли случая, и в этом коварство гонки - она может приводить к сложно отслеживаемым и трудновоспроизводимым (а значит тяжко отлаживаемым) ошибкам в коде.&lt;/p&gt;

&lt;h1 id=&quot;что-делать-lock-и-критическая-секция&quot;&gt;Что делать (lock и критическая секция)&lt;/h1&gt;

&lt;p&gt;Оказывается существует инструмент который позволяет указать что эту часть программы может выполнять только один поток.
Например если мы хотим просуммировать все элементы массива - давайте скажем что один поток считает левую половину, а второй считает вторую половину.
Но нам нужно будет результаты их работы просуммировать. Тогда давайте добавлять их частичную сумму к переменной с общим ответом. Но делать это нужно обязательно &lt;strong&gt;без состояния гонки&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Справиться с этим поможет инструмент lock (в переводе - защелка) и он позволяет сказать что мы делаем какую-то опасную инструкцию (добавление нашей частичной суммы к общему ответу), мы боимся гонки, мы боимся что другой поток тоже будет добавлять частичную сумму и все испортиться.&lt;/p&gt;

&lt;p&gt;А значит давайте скажем что в один и тот же момент времени добавлять частичную сумму к результату может только один поток.&lt;/p&gt;

&lt;p&gt;Иначе говоря давайте создадим специальную комнату, выполняя команды в которой мы имеем гарантии что никто другой в этой комнате нам не помешает.
Что-то вроде уборной - защелка на входе (отсюда название &lt;strong&gt;lock&lt;/strong&gt;) позволяет быть уверенным что нам не помешают.
В коде секция с командами которые проводятся с такой гарантией называется &lt;strong&gt;критической секцией&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/04/multithreading/4critical_sum.jpg&quot; alt=&quot;Критическая секция для суммирования половин массива&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Благодаря тому что суммирование частичных сумм выполняется из критической секции - сначала один из потоков сделает свои дела, т.е.
узнает какая была общая сумма (пока что &lt;strong&gt;0&lt;/strong&gt;), затем посчитает новую сумму (т.е. &lt;strong&gt;0+a&lt;/strong&gt;) и сохранит ее в оперативной памяти.
И лишь затем, дождавшись когда один поток выполнит свою задачу - другой поток постучавшись и увидев что уборная свободна зайдет и сделает свои дела.
Т.е. узнает какая общая сумма на данный момент (увидит новую сумму - частичную сумму сохраненную первым потоком), посчитает новую сумму (т.е. добавит свой результат) и сохранит ее в опреативной памяти.&lt;/p&gt;

</description>
        <pubDate>Tue, 19 Apr 2022 21:02:00 +0000</pubDate>
        <link>http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/04/19/lesson23-multithreading.html</link>
        <guid isPermaLink="true">http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/04/19/lesson23-multithreading.html</guid>
        
        
        <category>blogs</category>
        
        <category>239</category>
        
        <category>2021</category>
        
        <category>school239_11_2021_2022</category>
        
      </item>
    
    
    
      <item>
        <title>Урок 22: ретуширование фотографии (быстро и качественно, PatchMatch)</title>
        <description>&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/2/0task_example.png&quot; alt=&quot;Task example&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Постановка задачи и базовой идеи:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Хотим залатать дырку в картинке (дырка задается маской)&lt;/li&gt;
  &lt;li&gt;Вместо залатывания монотонным цветом - хотим чтобы выглядело естественно&lt;/li&gt;
  &lt;li&gt;Поэтому залатываем дырку беря наиболее подходящие доноры-заплатки из других частей картинки:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/2/1solution_idea_donors.png&quot; alt=&quot;Idea of donors and shifts&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Основные идеи предыдущей попытки:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Давайте хранить в каждом пикселе &lt;strong&gt;стрелочку&lt;/strong&gt; как на иллюстрации выше - указание откуда берется донор-заплатка&lt;/li&gt;
  &lt;li&gt;Научимся замерять &lt;strong&gt;качество&lt;/strong&gt; заплатки через сумму разницы цветов в окрестности патча (например &lt;strong&gt;7х7&lt;/strong&gt; пикселей)&lt;/li&gt;
  &lt;li&gt;Будем рассматривать новые случайные &lt;strong&gt;стрелочки&lt;/strong&gt; и заменять старые на новые если в таком случае &lt;strong&gt;качество растет&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Как можно коротко охарактеризовать такую попытку? По своей сути это что-то вроде перебора: чем дольше мы ждем - тем больше вариантов будет рассмотрено, а значит больше шансов найти хороший ответ.&lt;/p&gt;

&lt;p&gt;Но как увеличить шансы найти хороший ответ за минимальное число попыток? Т.е. как увеличить &lt;strong&gt;качество&lt;/strong&gt; и при этом &lt;strong&gt;ускорить&lt;/strong&gt;?&lt;/p&gt;

&lt;h1 id=&quot;идея-1-заметим-что-доноры-заплатки-согласованы-propagationраспространение&quot;&gt;Идея 1: заметим что доноры-заплатки согласованы (&lt;strong&gt;Propagation&lt;/strong&gt;=распространение)&lt;/h1&gt;

&lt;p&gt;Если есть какое-то хорошее сопоставление - то почти наверняка такую же стрелочку стоит провести и для наших соседей. Иначе говоря наши соседи хотят сопоставляться с соседями нашего сопоставления:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/2/2solution_idea_propagation.png&quot; alt=&quot;Idea about coherency&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Как это реализовать? Давайте предложим пикселю вместо случайной гипотезы, вместо случайного смещения - смещение которое на данный момент сохранено как лучшее у пикселя слева от нас.
Оценим качество такого смещения для нас, по сути - примерим его на себя, если нам подошло, если наше качество выросло - то надо сохранить для себя такое смещение.&lt;/p&gt;

&lt;p&gt;А кто еще может вдохновить нас такими идеями? Например сосед сверху, сосед справа и сосед снизу.&lt;/p&gt;

&lt;p&gt;Из этого вытекает идея этапа &lt;strong&gt;Propagation&lt;/strong&gt; (в переводе - распространение):&lt;/p&gt;

&lt;p&gt;1) Идем по всем строчкам &lt;strong&gt;сверху вниз&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;2) Идем по всем пикселям строчки &lt;strong&gt;слева направо&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;3) Смотрим на смещения у соседей слева и сверху - если они улучшают наше качество - сохраняем их у себя&lt;/p&gt;

&lt;p&gt;Таким образом все хорошие идеи которые случайным образом угадались будут распространяться вниз и вправо насколько это возможно.&lt;/p&gt;

&lt;p&gt;Чтобы было симметрично и хорошие идеи распространялись так же и вверх и влево - давайте запускать в т.ч. &lt;strong&gt;обратный Propagation&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;1) Идем по всем строчкам &lt;strong&gt;снизу вверх&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;2) Идем по всем пикселям строчки &lt;strong&gt;справа налево&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;3) Смотрим на смещения у соседей справа и снизу - если их смещение улучшает наше качество - берем лучшее из них себе&lt;/p&gt;

&lt;h1 id=&quot;идея-2-как-улучшить-случайные-гипотезы-refinementуточнение&quot;&gt;Идея 2: как улучшить случайные гипотезы? (&lt;strong&gt;Refinement&lt;/strong&gt;=уточнение)&lt;/h1&gt;

&lt;p&gt;Изначально еще до &lt;strong&gt;Propagation&lt;/strong&gt; нам надо выбрать хоть какие-то сопоставления - их давайте сделаем случайными.&lt;/p&gt;

&lt;p&gt;Но после прохода &lt;strong&gt;Propagation&lt;/strong&gt; когда мы вдохновились решениями соседей и все хорошие сопоставления распространились до куда могут - как генерировать новые идеи?&lt;/p&gt;

&lt;p&gt;Можно просто накинуть для каждого пикселя новое случайное сопоставление, и если оно лучше по &lt;strong&gt;качеству&lt;/strong&gt; чем то что у него было до сих пор - заменить.&lt;/p&gt;

&lt;p&gt;Но как например заложить в алгоритм идею навроде &lt;em&gt;у нас хорошее сопоставление взятое от соседа, но нам бы его чуть изменить, чуть сместить, чуть уточнить под себя&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;Давайте брать не просто случайное сопоставление, а несколько гипотез в экспоненциально убывающих окнах:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Случайное сопоставление вокруг нашего пикселя в радиусе ширины картинки (по сути - случайная гипотеза во всей картинке)&lt;/li&gt;
  &lt;li&gt;Случайное сопоставление вокруг нашего пикселя в половине радиусы ширины картинки (в какой половинке?)&lt;/li&gt;
  &lt;li&gt;Случайное сопоставление … в радиусе равном четвертинке ширины картинки (в какой четвертинке?)&lt;/li&gt;
  &lt;li&gt;Случайное сопоставление … в окрестности \(\frac{1}{8}\) ширины картинки (вокруг чего?)&lt;/li&gt;
  &lt;li&gt;Случайное сопоставление … в окрестности \(\frac{1}{16}\) ширины картинки (вокруг чего?)&lt;/li&gt;
  &lt;li&gt;…&lt;/li&gt;
  &lt;li&gt;Случайное сопоставление … в окрестности &lt;strong&gt;1 пикселя вокруг нашего текущего сопоставления&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Т.е. мы накидываем случайные гипотезы все ближе и ближе к нашей текущей гипотезе:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/2/3solution_idea_refinement.png&quot; alt=&quot;Idea about coherency&quot; /&gt;&lt;/p&gt;

&lt;p&gt;И берем лучшую из них (или оставляем старое сопоставление если оно победило &lt;strong&gt;по качеству&lt;/strong&gt;).&lt;/p&gt;

&lt;p&gt;Этот этап называется &lt;strong&gt;Refinement&lt;/strong&gt; = уточнение/улучшение решения.&lt;/p&gt;

&lt;h1 id=&quot;идея-3-лучше-начать-с-картинки-в-меньшем-разрешении-coarse-to-fine--от-грубого-к-детальному&quot;&gt;Идея 3: лучше начать с картинки в меньшем разрешении (Coarse-to-Fine = от грубого к детальному)&lt;/h1&gt;

&lt;p&gt;В маленькой картинке проще перебрать все гипотезы и в целом окно сопоставления (например &lt;strong&gt;7х7&lt;/strong&gt;) видит контекст лучше и надежнее.&lt;/p&gt;

&lt;p&gt;Так давайте сначала залатаем дырку в низком разрешении и на базе этого залатывания получим неплохой стартовый вариант для следующего разрешения:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/2/4solution_idea_coarse_to_fine.png&quot; alt=&quot;Idea about coarse to fine&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;общая-структура-кода&quot;&gt;Общая структура кода&lt;/h1&gt;

&lt;p&gt;1) Взяли картинку, взяли маску.&lt;/p&gt;

&lt;p&gt;2) Построили пирамиду из разных разрешений этой картинки (по сути - много раз уменьшаем ее в два раза).&lt;/p&gt;

&lt;p&gt;3) Построили пирамиду из разных разрешений этой маски (уменьшаем в два раза).&lt;/p&gt;

&lt;p&gt;4) На каждом уровне детализации картинки+маски:&lt;/p&gt;

&lt;p&gt;4.1) Если это первый уровень - генерируем случайные сопоставления&lt;/p&gt;

&lt;p&gt;4.2) Если это не первый уровень - сопоставления берем с предыдущего уровня&lt;/p&gt;

&lt;p&gt;4.3) Выполняем сколько-то итераций, на каждой итерации:&lt;/p&gt;

&lt;p&gt;4.3.1) Выполняем &lt;strong&gt;Propagation&lt;/strong&gt; (т.е. распространяем хорошие гипотезы) - вниз и вправо если итерация четная, вверх и влево если итерация нечетная&lt;/p&gt;

&lt;p&gt;4.3.2) Выполняем &lt;strong&gt;Refinement&lt;/strong&gt; (т.е. уточнения текущей гипотезы случайными пертурбациями в экспоненциально умешающемся окне поиска)&lt;/p&gt;

&lt;h1 id=&quot;технические-детали&quot;&gt;Технические детали&lt;/h1&gt;

&lt;p&gt;1) Как построить пирамиду картинок разного разрешения? Пример:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;cv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mat&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;...;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Image resolution: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cols&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;x&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;endl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;std&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pyramid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// здесь будем хранить пронумерованные версии картинки разного разрешения&lt;/span&gt;
                              &lt;span class=&quot;c1&quot;&gt;// нулевой уровень - самая грубая, последний уровень - самая детальная&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;cv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mat&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;original&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PYRAMID_MIN_SIZE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// до какой поры уменьшать картинку? давайте уменьшать пока картинка больше 20 пикселей&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rows&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PYRAMID_MIN_SIZE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cols&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PYRAMID_MIN_SIZE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// или пока больше (2 * размер окна для оценки качества)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;pyramid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pyramid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;begin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// мы могли бы воспользоваться push_back но мы хотим вставлять картинки в начало вектора&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;cv&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pyrDown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// эта функция уменьшает картинку в два раза&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2) Что делать если мы сопоставляем патч \(A\) вокруг пикселя с патчем-донором \(B\) вокруг пикселя на которого указывает сопоставление?&lt;/p&gt;

&lt;p&gt;2.1) Если патч-донор \(B\) оказался заползающим на отмаскированную територию, т.е. пытается взять те цвета которые мы пытаемся менять и улучшать - давайте просто выдадим ему очень плохое качество (хуже чем у любого другого сопоставления которое не выходит за пределы картинки, чтобы такое сопоставление всегда проигрывало и никогда не бралось).&lt;/p&gt;

&lt;p&gt;2.2) Если патч-донор \(B\) оказался за пределами картинки хотя бы одним пикселем - это плохой донор, он обрезан, его лучше не брать, давайте тоже просто выдадим ему очень плохое качество (хуже чем у любого другого сопоставления которое не выходит за пределы картинки, чтобы такое сопоставление всегда проигрывало и никогда не бралось).&lt;/p&gt;

&lt;p&gt;2.3) Если патч \(A\) оказался за пределами картинки хотя бы одним пикселем - это плохая окрестность вокруг пикселя который мы хотим залатать. Что тогда с ним делать? Не бросать же. Так давайте просто не учитывать те пиксели из окрестности, что выходят за пределы картинки. Или давайте вместо их разницы цветов (ведь у них нет цвета) добавлять какую-нибудь константу. От значения этой константы сопоставление-победитель не изменится.&lt;/p&gt;

&lt;p&gt;3) Как организовать код так чтобы &lt;strong&gt;Propagation&lt;/strong&gt; стадия была написана без копи-пасты один раз? Чтобы не было варианта про вперед (вниз и вправо) и второго варианта про назад (вверх и влево)? Если нет идей - позовите и обсудим. Вот код подсказка:&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;isForward&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iFirst&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iLast&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// на самом деле это индекс ПОСЛЕ последнего, в данной иллюстрации у нас в массиве якобы N элементов&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iStep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isForward&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;// если мы хотим протий по массиву в обратном направлении&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;iFirst&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;N&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// то начинаем с конца&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;iLast&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;// заканчиваем как только дошли до элемента сразу за самым первым &lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;iStep&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;     &lt;span class=&quot;c1&quot;&gt;// и шаг теперь не плюс 1, а минус 1, т.к. мы идем назад (уменьшаем индекс)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iFirst&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iLast&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iStep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iPrevious&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iStep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iCurrent&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iNext&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;iStep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;4) Какую метрику для оценки качества сопоставления патча лучше использовать? Можно использовать сумму абсолютных разниц (&lt;strong&gt;SAD&lt;/strong&gt; = Sum of Absolute Differences), но гораздо лучше работает сумма квадратов разниц (&lt;strong&gt;SSD&lt;/strong&gt; = Sum of Squared Differences), т.к. за отклонение цвета штраф обладает квадратичной (более сильной и строгой) зависимостью а не линейной.&lt;/p&gt;

&lt;p&gt;5) Как обновить найденные сопоставления при переходе от одного уровня детальности к следующему? Подумайте как обновить каждую стрелочку-сопоставление? Во-первых, каждый пиксель расчетверился, значит каждый из этих новых пикселей хочет вдохновиться старой стрелочкой сопоставлением. Во-вторых, картинка увеличилась вдвое, значит все расстояния увеличились вдвое, значит и стрелочку надо домножить на два.&lt;/p&gt;

&lt;p&gt;6) Как ускорить еще сильнее? Заметьте что стрелочки нам нужны не для всех пикселей, но на самом деле и не только для тех что отмаскированы… Подумайте почему? Или закодьте и посмотрите как изменится разница? Это очень интересный вопрос, &lt;strong&gt;позовите меня&lt;/strong&gt; и его будет очень полезно обсудить.&lt;/p&gt;

&lt;p&gt;7) Что делать если я не понимаю всю ценность в освоении тайных навыков paint-мастерства? Не хочу изучать Paint.NET и аналоги? Не понимаю что мне все-равно позже надо будет изучить все эти рисования со слоями и прозрачностью для создания презентаций и прочих визуализаций? Тогда вот &lt;a href=&quot;/static/2022/03/retouch/2/2_mask.png&quot;&gt;готовая маска&lt;/a&gt; для датасета с клумбой.&lt;/p&gt;

&lt;h1 id=&quot;ссылки&quot;&gt;Ссылки&lt;/h1&gt;

&lt;p&gt;Оригинальная статья представившая этот алгоритм:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://gfx.cs.princeton.edu/pubs/Barnes_2009_PAR/&quot;&gt;Сайт&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://gfx.cs.princeton.edu/pubs/Barnes_2009_PAR/patchmatch.pdf&quot;&gt;Статья&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/fMe19oTz6vk?t=54&quot;&gt;Видео на youtube&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://youtu.be/NH0aEp1oDOI?t=63&quot;&gt;Видео с идеями на каких картинках можно потестировать&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;примеры&quot;&gt;Примеры&lt;/h1&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/2/9_res_1.png&quot; alt=&quot;PatchMatch retouching: flowers results&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/2/9_res_2.png&quot; alt=&quot;PatchMatch retouching: bricks wall results&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/2/9_res_3.png&quot; alt=&quot;PatchMatch retouching: old photo restoration results&quot; /&gt;&lt;/p&gt;

</description>
        <pubDate>Mon, 04 Apr 2022 21:02:00 +0000</pubDate>
        <link>http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/04/04/lesson22-image-retouching-coarse-to-fine.html</link>
        <guid isPermaLink="true">http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/04/04/lesson22-image-retouching-coarse-to-fine.html</guid>
        
        
        <category>blogs</category>
        
        <category>239</category>
        
        <category>2021</category>
        
        <category>school239_11_2021_2022</category>
        
      </item>
    
    
    
      <item>
        <title>Урок 21: ретуширование фотографии (залатывание дыр)</title>
        <description>&lt;p&gt;Давайте посмотрим на такой кадр из фильма:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/1_original.png&quot; alt=&quot;Image with mic&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Снизу слева заметен микрофон, что если мы хотим его удалить?&lt;/p&gt;

&lt;p&gt;Давайте для начала создадим &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;картинку-маску&lt;/code&gt; которая сохранит наше мнение о том &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;какие пиксели надо удалить&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;1) Дома скачайте и установите &lt;a href=&quot;https://www.getpaint.net/&quot;&gt;paint.net&lt;/a&gt; и &lt;strong&gt;обязательно создайте свою картинку-маску для второго примера в репозитории&lt;/strong&gt;, на уроке же воспользуйтесь моей маской для микрофона и других тестов (маски уже есть в репозитории).&lt;/p&gt;

&lt;p&gt;2) Откройте картинку &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2_original.jpg&lt;/code&gt; в &lt;strong&gt;paint.net&lt;/strong&gt; и добавьте слой поверх картинки - там мы будем рисовать нашу маску:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/generating_mask/01_add_layer.png&quot; alt=&quot;Add layer&quot; /&gt;&lt;/p&gt;

&lt;p&gt;3) Теперь аккуратно белым карандашом очертите контур вокруг микрофона:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/generating_mask/02_draw_around_mic.png&quot; alt=&quot;Add layer&quot; /&gt;&lt;/p&gt;

&lt;p&gt;4) Теперь залейте белым цветом весь микрофон:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/generating_mask/03_fill_mic.png&quot; alt=&quot;Fill mic with white&quot; /&gt;&lt;/p&gt;

&lt;p&gt;5) Оригинальная картинка нам больше не нужна - уберите ее слой и залейте все остальное черным цветом:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/generating_mask/04_fill_around_mic.png&quot; alt=&quot;Fill black around mic&quot; /&gt;&lt;/p&gt;

&lt;p&gt;6) Сохраните получившуюся черно-белую картинку через &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;File-&amp;gt;Save As-&amp;gt;-&amp;gt;OK-&amp;gt;Flatten&lt;/code&gt; (обязательно укажите название файла и его расширение):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/generating_mask/05_save_as.png&quot; alt=&quot;Fill black around mic&quot; /&gt;&lt;/p&gt;

&lt;p&gt;7) Проверьте что все в порядке с картинками в папке репозитория, что маска идеально накладывается на микрофон (еще можно открыть картинку и нажимая &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;стрелочки влево-вправо&lt;/code&gt;, или &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PageUp-PageDown&lt;/code&gt; помигать между картинками):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/generating_mask/06_check_images.png&quot; alt=&quot;Fill black around mic&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;алгоритм&quot;&gt;Алгоритм&lt;/h1&gt;

&lt;p&gt;1) Создаем картинку в которой для каждого пикселя храним смещение - указание откуда мы берем донора для ретуширования, откуда берем заплатку. Изначально смещение &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(dx=0, dy=0)&lt;/code&gt; означает что донор мы сами. Но в отмаскированных пикселях надо найти кого-то другого неотмаскированного из другой части картинки.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/retouch/shifts.png&quot; alt=&quot;Shifts&quot; /&gt;&lt;/p&gt;

&lt;p&gt;2) Картинка хранящая такие смещения:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;будет хранить два целых числа - относительно смещение по оси x (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dx&lt;/code&gt;, т.е. смещение номера колонки) и относительное смещение по оси y (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dy&lt;/code&gt;, т.е. смещение номера строчки)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;тип этой картинки - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CV_32SC2&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;заполнена она изначально парами из нулей - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cv::Scalar(0, 0)&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;а какого она разрешения?&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;каждый элемент в такой картинке смещений обладает типом не &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cv::Vec3b&lt;/code&gt; как было с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RGB-картинками&lt;/code&gt;, а &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cv::Vec2i&lt;/code&gt; (i=int)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;поэтому чтобы извлечь какой-то элемент надо написать: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cv::Vec2i dxy = shifts.at&amp;lt;cv::Vec2i&amp;gt;(j, i);&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;3) Теперь давайте просто много раз генерировать случайную гипотезу-смещение, и сравнивать ее качество с качеством смещения которое мы уже сохранили в этом пикселе.&lt;/p&gt;

&lt;p&gt;4) Как оценивать качество? Приложим окрестность вокруг пикселя к окрестности вокруг пикселя на который указывает смещение.&lt;/p&gt;

&lt;p&gt;5) В коде приведет какой-то набросок кода который может подсказать общую структуру кода.&lt;/p&gt;
</description>
        <pubDate>Mon, 14 Mar 2022 21:02:00 +0000</pubDate>
        <link>http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/03/14/lesson21-image-retouching.html</link>
        <guid isPermaLink="true">http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/03/14/lesson21-image-retouching.html</guid>
        
        
        <category>blogs</category>
        
        <category>239</category>
        
        <category>2021</category>
        
        <category>school239_11_2021_2022</category>
        
      </item>
    
    
    
      <item>
        <title>Урок 20: сшивка панорамы по шву найденному быстрой Дейкстрой</title>
        <description>&lt;p&gt;Пусть мы взяли две картинки:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/diff/pano1_hanging.png&quot; alt=&quot;Panorama pair&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Пусть мы &lt;strong&gt;поняли насколько отличаются их цвета в одном и том же пикселе:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/diff/pano1_diff_hanging.jpg&quot; alt=&quot;Panorama diff&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Давайте проложим кратчайший путь по этой карте различий из нижнего левого угла всего пространства панорамы в верхний левый угол:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/seam/5panoOptimalSeam.jpg&quot; alt=&quot;Panorama dijkstra seam&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Что нам для этого надо учесть? Как свести задачу к Дейкстре из прошлого задания?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Давайте максимально приблизим к решению из задания про “найти выход в лабиринте-картинке”&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Достаточно создать картинку-лабиринт (см. функцию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;buildTheMaze(...)&lt;/code&gt;) из нашей картинки &lt;strong&gt;насколько отличаются их цвета в одном и том же пикселе&lt;/strong&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;А в пикселях лабиринта где хотя бы одна из наложенных картинок отсутствует (т.е. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isPixelEmpty(...)&lt;/code&gt;, т.е. черный пиксель) - скажем что цена прохождения очень высокая - например &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const int BIG_PENALTY = 100000;&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;После чего (см. функцию &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;findBestSeam(...)&lt;/code&gt;) из каждого пикселя давайте проведем ребро вверх и ребро вправо с длинной равной значению содержащемуся в пикселе лабиринта&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;И наконец найдем Дейкстрой кратчайший путь из вершины &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;start&lt;/code&gt; соответствующей нижнему левому углу всего пространства панорамы, в вершину &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;finish&lt;/code&gt; соответствующую верхнему правому углу всей панорамы&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Теперь надо как-то построить красивую панораму так чтобы все что слева от этого шва - было взято из первой картинки, а все что справа от этого шва - было взято из правой картинки.&lt;/p&gt;

&lt;p&gt;Давайте визуализируем и построим для начала такую картинку - в каждом пикселе черный цвет (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_NO_DATA = 0&lt;/code&gt;) если этого пикселя нет ни на одной картинке, темно-серый (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_FROM_PANO0 = 100&lt;/code&gt;) если этот пиксель должен быть взят с первой картинки, светло-серый (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_FROM_PANO1 = 200&lt;/code&gt;) если со второй:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/seam/6sourceId.jpg&quot; alt=&quot;Panorama image source id&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Как построить такую картинку? Давайте начнем с того чтобы заполнить всю картинку &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_NO_DATA&lt;/code&gt;, затем запретим пересекать шов - заполним все пиксели принадлежащие шву значением &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_IS_ON_SEAM = 1&lt;/code&gt;, затем с помощью поиска в ширину заполним пиксели относящиеся к первой картинке (т.е. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_FROM_PANO0&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;1) Изначально отметим все пиксели как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_NO_DATA&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;2) Теперь запретим пересекать шов - заполним все пиксели принадлежащие шву значением &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_IS_ON_SEAM = 1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;3) Дальше давайте воспользуемся поиском в ширину (&lt;strong&gt;BFS&lt;/strong&gt;) чтобы распространить влияние первой картинки начиная с верхнего левого угла вплоть до шва-границы&lt;/p&gt;

&lt;p&gt;4) Отметим верхний левый пиксель как &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_FROM_PANO0&lt;/code&gt; и добавим его в текущую волну распространения (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curWave&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;5) Выполняем в цикле &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;while&lt;/code&gt; до тех пока в текущей волне распространения (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curWave&lt;/code&gt;) есть хотя бы один пиксель:&lt;/p&gt;

&lt;p&gt;5.1) Смотрим на каждый пиксель из текущей волны распространения и смотрим в цикле на каждый из четырех соседей (слева, сверху, справа, снизу)&lt;/p&gt;

&lt;p&gt;5.2) Если сосед (&lt;strong&gt;n&lt;/strong&gt;eighbor - сосед, поэтому пусть &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nx, ny&lt;/code&gt;) за пределами картинки - ничего с ним не делаем&lt;/p&gt;

&lt;p&gt;5.3) Если сосед пуст в первой картинке - в него тоже не надо распространять влияние первой картинки - ничего с ним не делаем&lt;/p&gt;

&lt;p&gt;5.4) Если это пиксель лежащий на шве (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_IS_ON_SEAM&lt;/code&gt;) - в него тоже не надо распространять влияние первой картинки - ничего с ним не делаем&lt;/p&gt;

&lt;p&gt;5.5) Если это пиксель который уже принадлежащий первой картинке (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_FROM_PANO0&lt;/code&gt;) - ничего с ним не делаем&lt;/p&gt;

&lt;p&gt;5.6) Иначе - отмечаем соседний пиксель &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nx, ny&lt;/code&gt; как принадлежащий первой картинке (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_FROM_PANO0&lt;/code&gt;) и добавляем его в следующую волну обработки &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nextWave&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;5.7) Когда мы обработали все пиксели текущей волны - новая волна готова, берем ее вместо текущей: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curWave = nextWave;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;6) Все остальные пиксели будем заполнять со второй картинки - значит все кто не &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_FROM_PANO0&lt;/code&gt; и при этом не пусты на второй картинке - должны быть заполнены &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PIXEL_FROM_PANO1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Наконец давайте сделаем красивую панораму с учетом этой картинки указывающей откуда какую картинку брать:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/seam/7newPano.jpg&quot; alt=&quot;Panorama stitched result&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Как это сделать? пройти по всем пикселям, посмотреть из какой картинки брать цвет - и взять соответственно :)&lt;/p&gt;

&lt;h1 id=&quot;сделайте-свою-панораму&quot;&gt;Сделайте свою панораму&lt;/h1&gt;

&lt;p&gt;Сделайте две фотографии при этом убрав два объекта на фоторой фотографии, примерно так:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/seam/my_photo_pair.png&quot; alt=&quot;Panorama stitched result&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Голубая вертикаль показывает где прошла граница другой фотографии.&lt;/p&gt;

&lt;p&gt;Когда снимаете первую фотографию положите объект (зеленого Единорога) там где будет проходить граница второй фотографии.&lt;/p&gt;

&lt;p&gt;Не двигайте телефон в пространстве, камера вашего телефона должна быть в той же точке пространства, просто слегка поверните телефон вокруг оси проходящей через камеру вашего телефона. Иначе говоря - вам надо избегать параллакса.&lt;/p&gt;

&lt;p&gt;Когда снимаете вторую фотографию уберите старый объект и положите новый объект (фиолетовое нечто, или можете просто переложить объект) там где проходит граница первой фотографии.&lt;/p&gt;

&lt;p&gt;Подумайте где должен был бы пройти шов, проверьте где он на самом деле прошел. А что будет если оба эти объекта большие и наложатся?&lt;/p&gt;

&lt;p&gt;Чтобы сохранить результат на github - разверните все подпапки в &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lesson17/resultsData&lt;/code&gt; -&amp;gt; выделите все картинки (кликнув по самой верхней, а затем зажав Shift и кликнув по самой нижней) -&amp;gt; правая кнопка мыши -&amp;gt; Git -&amp;gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Add&lt;/code&gt;.&lt;/p&gt;

&lt;h1 id=&quot;как-ускорить-дейкстру&quot;&gt;Как ускорить Дейкстру&lt;/h1&gt;

&lt;p&gt;1) &lt;a href=&quot;/blogs/239/2021/school239_11_2021_2022/2021/10/05/lesson5-disjoint-set.html&quot;&gt;Использовать компиляцию с оптимизациями&lt;/a&gt;, т.е. компилировать Release или RelWithDebInfo&lt;/p&gt;

&lt;p&gt;2) В самом начале считанные картинки уменьшить в разрешении - см. сколько раз они уменьшаются - переменная &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int downscale = 2;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;3) И наконец самый хороший шаг - ускорить Дейкстру. В ней тормозит этап “ищем самую ближайшую вершину”, его можно ускорить используя приоритетную очередь которая за быстро умеет выдавать минимальный элемент (он в ней всегда лежит первым) и в эту приоритетную очередь можно за быстро добавлять новые элементы:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Создайте эту приоритетную очередь (очередь по английски - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;queue&lt;/code&gt;): &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::set&amp;lt;std::pair&amp;lt;int,int&amp;gt;&amp;gt; queue;&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Каждый элемент в этой очереди - пара, первое число в паре - приоритет (в нашем случае “расстояние от старта”), второе число - номер вершины&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Изначально в очередь нужно добавить стартовую вершину с приоритетом 0: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;queue.insert( std::pair&amp;lt;int,int&amp;gt;(0, start));&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Чтобы достать (и удалить из очереди) минимальный элемент (тоже пару): &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::pair&amp;lt;int, int&amp;gt; min_pair = queue.extract(queue.begin()).value();&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Чтобы узнать из минимального элемента что за вершина в нем: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;the_closest_one = min_pair.second;&lt;/code&gt; (не забудьте проверить - а не обработана ли уже эта вершина, ведь мы одну и ту же вершину могли добавить в очередь несколько раз, подумайте об этом!)&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;И наконец каждый раз когда у какой-то вершины меняется дистанция - ее нужно добавить в очередь с этой новой дистанцией как приоритет: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;queue.insert(std::pair&amp;lt;int, int&amp;gt;(vertex_new_distance, vertex));&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Удобнее всего обкатать ускоренную Дейкстру в прошлом задании про лабиринты, когда заработает - можно убрать уменьшение картинок в этом задании - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int downscale = 0;&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 08 Mar 2022 21:02:00 +0000</pubDate>
        <link>http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/03/08/lesson20-pano-stitching.html</link>
        <guid isPermaLink="true">http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/03/08/lesson20-pano-stitching.html</guid>
        
        
        <category>blogs</category>
        
        <category>239</category>
        
        <category>2021</category>
        
        <category>school239_11_2021_2022</category>
        
      </item>
    
    
    
      <item>
        <title>Урок 19: панорама и визуализация качества склейки</title>
        <description>&lt;p&gt;Давайте возьмем опять две кратинки и снова сошьем их в панораму:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/diff/pano1_hanging.png&quot; alt=&quot;Panorama pair&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Но как оценить качество склейки? Как визуализировать насколько хорошо одна картинка наложилась на другую?&lt;/p&gt;

&lt;p&gt;Давайте просто &lt;strong&gt;визуализируем насколько отличаются их цвета в одном и том же пикселе:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/diff/pano1_diff_hanging.jpg&quot; alt=&quot;Panorama diff&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Упражнение 1&lt;/code&gt; Подумайте почему разница выглядит так?&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Упражнение 2&lt;/code&gt; Почему в левой части перекрытия картинка светлее а в правой части - темнее?&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Упражнение 3&lt;/code&gt; Какая в таком случае панорама будет выглядеть более бесшовной - правая картинка поверх левой или наоборот (левая картинка поверх правой)?&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Упражнение 4&lt;/code&gt; А где вообще заметнее всего в панораме что это две картинки? Это можно было бы назвать &lt;strong&gt;швом или разрезом&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Упражнение 5&lt;/code&gt; Подумайте - а можно ли было бы еще лучше как-то проложить этот шов кроме как примитивно выбирая какую из картинок класть поверх другой?&lt;/p&gt;

&lt;p&gt;Давайте посмотрим еще на примеры:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/diff/pano2_hike.png&quot; alt=&quot;Panorama pair&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/diff/pano2_diff_hike.jpg&quot; alt=&quot;Panorama diff&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Упражнение 6&lt;/code&gt; Почему почти вся зона перекрытия выглядит черной?&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Упражнение 7&lt;/code&gt; Что можно тогда предположить о заметности шва? Он сильно заметен или нет?&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/diff/pano3_aero.jpg&quot; alt=&quot;Panorama pair&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/03/diff/pano3_diff_aero.jpg&quot; alt=&quot;Panorama diff&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Упражнение 8&lt;/code&gt; Как бы вы сформулировали в каких частях картинки на этом примере наибольшая разница цветов?&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Упражнение 9&lt;/code&gt; Если вы обратили внимание на дом и кран, то как бы вы сформулировали как должен быть проложен &lt;strong&gt;шов/разрез&lt;/strong&gt; относительно таких ярко выделяющихся и по-разному выглядящих объектов?&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Упражнение 10&lt;/code&gt; Есть идеи какой алгоритм мог бы проложить этот &lt;strong&gt;шов&lt;/strong&gt; автоматически так чтобы его суммарная заметность была минимальна?&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Упражнение 11&lt;/code&gt; Сфотографируйте пару фотографий так чтобы у них было 30% перекрытия и при этом в зоне перекрытия присутствовал объект симулирующий “проблему” (как дом и кран) - например просто положите объект когда делаете первую фотографию, а перед тем как делать вторую фотографию - уберите этот объект.&lt;/p&gt;
</description>
        <pubDate>Tue, 01 Mar 2022 21:02:00 +0000</pubDate>
        <link>http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/03/01/lesson19-pano-diff.html</link>
        <guid isPermaLink="true">http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/03/01/lesson19-pano-diff.html</guid>
        
        
        <category>blogs</category>
        
        <category>239</category>
        
        <category>2021</category>
        
        <category>school239_11_2021_2022</category>
        
      </item>
    
    
    
      <item>
        <title>Урок 18: поиск кратчайшего пути методом Дейкстры и лабиринты</title>
        <description>&lt;p&gt;Предлагается реализовать алгоритм поиска кратчайших путей в графе &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%94%D0%B5%D0%B9%D0%BA%D1%81%D1%82%D1%80%D1%8B&quot;&gt;методом Дейкстры&lt;/a&gt; (если что - можно найти много статей про этот алгоритм в интернете).&lt;/p&gt;

&lt;p&gt;Граф представляем ввиде:&lt;/p&gt;

&lt;p&gt;1) Вершины пронумерованы от \(0\) до \(N-1\) (где \(N\) - число вершин), для каждой вершины будем хранить:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Расстояние от точки старта до нее по кратчайшему найденному на данный момент пути. Т.е. мы пытаемся найти кратчайшие пути из стартовой точки до каждой другой точки. Изначально эти расстояния - бесконечность для всех вершин кроме самой стартовой.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Пометку обработали ли мы уже эту вершину, иначе говоря проверили ли все исходящие из этой вершины пути для того чтобы попытаться найти какие-то новые кратчайшие пути до каких-то вершин.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;На равне с кратчайшим расстояним самого быстрого пути нам хотелось бы знать сам путь. Для этого достаточно в каждой вершине хранить ссылку на предыдущую вершину с кратчайшего пути, т.е. достаточно помнить последний шаг - тогда отшагнув по нему назад - можно будет спросить еще один предыдущий шаг у этой предыдущей вершины.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;2) Ребра хранятся списками смежности:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Каждое ребро знает откуда и куда идет и знает свою длинну (т.е. насколько длинный маршрут надо преодолеть чтобы переместиться по этому ребру).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;У каждой вершины есть вектор в котором хранятся все ребра которые исходят из этой вершины.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Получается список смежности - это вектор размера \(N\) в котором каждый элемент - векторов ребер (список ребер исходящих из вершины). Ведь вершины пронумерованы от \(0\) до \(N-1\). Т.е. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;std::vector&amp;lt;std::vector&amp;lt;Edge&amp;gt;&amp;gt; edges(nvertices);&lt;/code&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;алгоритм-дейкстры&quot;&gt;Алгоритм Дейкстры&lt;/h1&gt;

&lt;p&gt;1) Создали список смежности (т.к. записали для каждой вершины список ее ребер, обратите внимание что граф неориентированный, т.е. ребра симметричные).&lt;/p&gt;

&lt;p&gt;2) Создали вектор в котором у каждой вершины хранится длина кратчайшего пути от старта до нее. Изначально все значения равны константе бесконечности &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INF&lt;/code&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;const int INF = std::numeric_limits&amp;lt;int&amp;gt;::max();&lt;/code&gt; (самое большое значение &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;int&lt;/code&gt;). Значение кратчайшего расстояния для начальной вершины выставляем в 0.&lt;/p&gt;

&lt;p&gt;3) Создали вектор в котором у каждой вершины хранится флажок распространяли ли мы пути от нее, т.е. обработана ли уже вершины. Изначально никакя вершина не обработана.&lt;/p&gt;

&lt;p&gt;4) Создали вектор указывающий для каждой вершины куда (в какую вершину) надо отшагнуть назад чтобы прошагать задом наперед кратчайший путь. Изначально пути неизвестны, а в таком случае давайте укажем несуществующую точку - \(-1\).&lt;/p&gt;

&lt;p&gt;5) Пока прогресс не прекратится:&lt;/p&gt;

&lt;p&gt;5.1) Ищем из вершин которые еще не обработаны (из которых еще не продолжили пути) - самую близкую к началу (и достижимую), выбрали ее - теперь это Избранная Вершина.&lt;/p&gt;

&lt;p&gt;5.2) Если такой нет (т.е. все вершины обработаны или все необработанные вершины недостижимы) - прогресс прекратился - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;break;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;5.3) Смотрим куда ведет каждое ребро исходящее из Избранной Вершины, если мы можем продлив путь нашей Избранной Вершины улучшить текущий ответ для соседней вершины (т.е. куда ведет очередное ребро) - делаем это (обновив ее кратчайшее расстояние и указав что шаг назад из нее надо делать в Избранную Вершину).&lt;/p&gt;

&lt;p&gt;5.4) Помечаем Избранную Вершину обработанной (т.е. что она уже нанесла все добро своим соседям что могла).&lt;/p&gt;

&lt;p&gt;6) Проверяем какой ответ лежит в вершине до которой мы хотели найти путь.&lt;/p&gt;

&lt;p&gt;7) Если там лежит не бесконечность - то можем восстановить этот путь прошагав назад по ссылкам на предыдующую вершину на кратчайшем пути. Просто шагаем пока не дойдем до начальной вершины, ведь из начальной вершины предыдущий шаг будет \(-1\).&lt;/p&gt;

&lt;h1 id=&quot;задание-1-дейкстра&quot;&gt;Задание 1: Дейкстра&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/CompVisionCourses/CPPExercises2021/tree/main/lesson15/src/mainDijkstra.cpp&quot;&gt;mainDijkstra.cpp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Реализуйте метод Дейкстры описанный выше.&lt;/p&gt;

&lt;p&gt;Чтобы проверить корректность:&lt;/p&gt;

&lt;p&gt;1) Запустите на всех четырех тестах из папки &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;data/mazesText/N.in&lt;/code&gt; (просто &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ctrl+C -&amp;gt; Ctrl+V -&amp;gt; Enter&lt;/code&gt; в консоль после запуска приложения) - результат должен совпасть с &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;N.out&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;2) Зарегистрируйтесь и отправьте свое решение на &lt;a href=&quot;https://codeforces.com/problemset/problem/20/C?locale=ru&quot;&gt;CodeForces&lt;/a&gt; - да, мы ищем Избранную Вершину очень медленно и не эффективно, но нас это устраивает, поэтому &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Превышено ограничение времени на тесте 31&lt;/code&gt; - считайте успешным выполнением.&lt;/p&gt;

&lt;h1 id=&quot;задание-2-лабиринт&quot;&gt;Задание 2: Лабиринт&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/CompVisionCourses/CPPExercises2021/tree/main/lesson15/src/mainMaze.cpp&quot;&gt;mainMaze.cpp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Теперь давайте сделаем из этого что-нибудь классное, например найдем кратчайший путь в лабиринте:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/02/maze/mazeGreenFill.png&quot; alt=&quot;Maze green fill&quot; /&gt;&lt;/p&gt;

&lt;p&gt;1) Теперь граф надо придумать самим - давайте для начала придумаем как пронумеровать вершины графа - давайте нумеровать пиксель в строчке \(j\) и колонке \(i\) как \(j*w+i\), где \(w\) - ширина картинки.&lt;/p&gt;

&lt;p&gt;2) Надо придумать какие у нас ребра - давайте соединять пиксели соседние друг с другом (т.е. у каждого пикселя кроме тех что на краю - четыре соседа).&lt;/p&gt;

&lt;p&gt;3) А какие пропускные способности у ребер? Давайте они будут равны тому насколько яркий пиксель. Тогда в темных пикселях мы разрешаем двигаться дешево, а в белых - дорого (ведь это стенки).&lt;/p&gt;

&lt;p&gt;4) Добавьте отрисовку кратчайшего пути:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/02/maze/mazeRedPath.png&quot; alt=&quot;Maze shortest path&quot; /&gt;&lt;/p&gt;

&lt;p&gt;5) Но как сделать так чтобы логика “какая пропускная способность у ребра” не зависела черно-белый или бело-черный у нас лабиринт? Чтобы работало даже если тропинка покрашена в синий цвет?&lt;/p&gt;

&lt;p&gt;6) НЕ ЗАБУДЬТЕ протестировать все лабиринты (второй и третий - отличаются, пятый - не обязательный) и сохранить на диск результирующие картинки чтобы я мог легко и быстро на них посмотреть.&lt;/p&gt;

&lt;h1 id=&quot;мелочи&quot;&gt;Мелочи&lt;/h1&gt;

&lt;p&gt;1) Не забудьте компилировать с оптимизациями (а не в &lt;strong&gt;Debug&lt;/strong&gt;) - см. &lt;a href=&quot;/blogs/239/2021/school239_11_2021_2022/2021/10/05/lesson5-disjoint-set.html&quot;&gt;Как ускорить программу&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2) НЕ ЗАБУДЬТЕ протестировать все лабиринты (второй и третий - отличаются, пятый - не обязательный) и сохранить на диск результирующие картинки чтобы я мог легко и быстро на них посмотреть.&lt;/p&gt;
</description>
        <pubDate>Tue, 15 Feb 2022 21:02:00 +0000</pubDate>
        <link>http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/02/15/lesson18-dijkstra-maze.html</link>
        <guid isPermaLink="true">http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/02/15/lesson18-dijkstra-maze.html</guid>
        
        
        <category>blogs</category>
        
        <category>239</category>
        
        <category>2021</category>
        
        <category>school239_11_2021_2022</category>
        
      </item>
    
    
    
      <item>
        <title>Урок 17: сопоставление картинок SIFT/ORB для интерактивного AR через вебкамеру</title>
        <description>&lt;p&gt;В &lt;a href=&quot;/blogs/239/2021/school239_11_2021_2022/2022/01/25/lesson16-images-stitching&quot;&gt;предыдущем задании&lt;/a&gt; мы обсудили как работает &lt;strong&gt;RANSAC&lt;/strong&gt; + методы фильтрации шумных сопоставлений.&lt;/p&gt;

&lt;p&gt;Давайте теперь на базе этого сделает примерно такое приложение:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/2022/02/stitching/nowebcamera.jpg&quot; alt=&quot;Demo without webcamera&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Но еще и работающее через вебкамеру.&lt;/p&gt;

&lt;p&gt;Итого план задания - выполнять &lt;a href=&quot;https://github.com/CompVisionCourses/CPPExercises2021/blob/main/lesson14/src/main.cpp&quot;&gt;TODO&lt;/a&gt; в исходниках, в частности:&lt;/p&gt;

&lt;p&gt;1) Проверьте работает ли у вас вебкамера через OpenCV, и выставите &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;useWebcam&lt;/code&gt; соответственно&lt;/p&gt;

&lt;p&gt;2) Детектируйте ключевые точки и постройте у них дескрипторы (на обеих фотографиях - ту что ищем и ту где ищем эту картинку)&lt;/p&gt;

&lt;p&gt;3) Сопоставьте ключевые точки&lt;/p&gt;

&lt;p&gt;4) Пофильтруйте найденные сопоставления (как минимум через K-ratio test, но лучше на ваш выбор)&lt;/p&gt;

&lt;p&gt;5) Добавьте вывод в лог - сколько ключевых точек было изначально, и сколько осталось сопоставлений после фильтрации&lt;/p&gt;

&lt;p&gt;6) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;findHomography(...)&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cv::warpPerspective&lt;/code&gt; = нашли матрицу перехода и натянули одну картинку поверх другой&lt;/p&gt;

&lt;p&gt;7) В конце кода поправьте управление - если пользователь нажал кнопку &lt;strong&gt;X&lt;/strong&gt;, то &lt;strong&gt;Y&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;8) Добавьте короткую текстовую справку про кнопки управления (текстом поверх окна)&lt;/p&gt;

&lt;p&gt;9) Добавьте разбивку сколько времени занимает детектирование, сколько матчинг, сколько фильтрация (текстом поверх окна)&lt;/p&gt;

&lt;p&gt;10) &lt;strong&gt;SIFT&lt;/strong&gt; работает довольно медленно, попробуйте использовать &lt;strong&gt;ORB&lt;/strong&gt; + не забудьте что тогда вам нужен другой &lt;strong&gt;DescriptorMatcher&lt;/strong&gt;, насколько вырос &lt;strong&gt;FPS&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;13) Изучите документацию и попробуйте улучшить качество/скорость - &lt;a href=&quot;https://docs.opencv.org/4.5.1/d7/d60/classcv_1_1SIFT.html#ad337517bfdc068ae0ba0924ff1661131&quot;&gt;SIFT::create()&lt;/a&gt; и &lt;a href=&quot;https://docs.opencv.org/4.5.1/db/d95/classcv_1_1ORB.html#aeff0cbe668659b7ca14bb85ff1c4073b&quot;&gt;ORB::create()&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;14) Бонусное задание - можно попробовать сделать эффект дополненной реальности более полным - ведь сейчас если мы вкорячиваем несквик поверх коробок - то он рисуется поверх в т.ч. коробки справа, которую мы заменять не планировали. Можно попробовать это улучшить подобно тому как мы делали зеленый экран - достаточно сверить похож ли на заменяемую картинку пиксель который мы хотим заменить и если да - то только тогда заменять его на пиксель с новой картинки (на несквик).&lt;/p&gt;
</description>
        <pubDate>Tue, 01 Feb 2022 21:02:00 +0000</pubDate>
        <link>http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/02/01/lesson17-webcamera-ar-sift-orb.html</link>
        <guid isPermaLink="true">http://polarnick.com/blogs/239/2021/school239_11_2021_2022/2022/02/01/lesson17-webcamera-ar-sift-orb.html</guid>
        
        
        <category>blogs</category>
        
        <category>239</category>
        
        <category>2021</category>
        
        <category>school239_11_2021_2022</category>
        
      </item>
    
    
  </channel>
</rss>
