Алгоритм расчёта периметра растровой фигуры более сложен. Ниже приведён код соответствующей функции.
Следует отметить, что предлагаемый алгоритм расчёта периметра является оригинальной разработкой, учитывающей особенности формы раковины черноморской мидии. По классификации алгоритмов выделения контуров [10] настоящий алгоритм следует отнести к сканирующим алгоритмам. С учётом типа исследуемых фигур использование алгоритмов этого типа представляется более оправданным, чем отслеживающих алгоритмов (или алгоритмов обхода контура, алгоритмов "жука").
Алгоритм включает два последовательно выполняемых блока. Вначале определяются крайние точки внешнего контура фигуры: левая, верхняя, правая и нижняя. Затем начинается проверка каждого пиксела на предмет его принадлежности фигуре: вначале от левой до верхней точки контура, затем от верхней до правой и так до замыкания контура.
Кратко о решении проблем, которые возникли при разработке этого алгоритма:
снижение вычислительной сложности;
учёт возможной невыпуклости фигуры;
учёт ступенчатости контура растровой фигуры
Порядок вычислительной сложности алгоритма (под сложностью будем понимать время, затрачиваемое на вычисления) удалось снизить не менее чем до величины ~O(N2/3) за счёт того, что просматривались лишь пиксели вокруг внешнего контура фигуры и не затрагивались точки внутри контура. Поскольку фигуры исследуемых объектов занимают обычно не менее трети холста (для эллипса - π/4), то в результате имеем указанную выше оценку вычислительной сложности. Под N можно понимать средние геометрические размеры фигуры. (Анастасии - о понятии "порядок вычислительной сложности алгоритма" хорошо сказано здесь)
Выпуклой называется такая фигура, которой принадлежат все точки отрезка, соединяющего любые ее две точки. Исследуемые фигуры раковин моллюсков не всегда являются выпуклыми. По этой причине просмотр (или сканирование) пикселей, например только лишь по горизонтали, может привести к неверным результататам. Для раковины мидии невыпуклость может появляться (а может и не появляться) примерно в одних и тех же местах или секторах при используемом нами методе поиска точек контура. Поэтому сектор с возможной невыпуклостью контура разбивается на два сектора и сканирование пикселей в этих "полусекторах" выполняется в разных направлениях. Многочисленные случаи проверки алгоритма на снимках реальных раковин не выявил ошибок при наличии невыпуклости.
Поскольку растровые фигуры имеют ступенчатый контур, то простой подсчёт пикселей контура не будет равен его длине, то есть периметру исходной фигуры. Определение длины вертикальных или горизонтальных линий в растре наиболее просто: проводится подсчёт числа пикселей, через которые проходит контур. Аналогично - для горизонтали. Но если линия ориентирована не точно по горизонтали или вертикали, такой метод не будет точным. Например, если линия проходит точно по диагоналям ячеек, то ее длина будет очевидно больше примерно в 1.414 раз, чем произведение разрешения на число ячеек. В настоящем алгоритме ступенчатый контур аппроксимируется ломанной линией. Конечно, это даёт возможность более точно определить длину контура отображаемой фигуры, но также с некоторой ошибкой, которую мы в последующем попытаемся определить.
Приведённый выше код функции по расчёту периметра относится к фигуре, нарисованной на первом этапе. Для определения принадлежности контуру фигуры, также как при расчёте площади, используется встроенный метод cntxt.isPointInPath(i,j), предусмотренный в API HTML5 Canvas для проверки, находится ли точка (пиксел) с координатами (i,j) внутри контура.
Завершая описание алгоритмов первого этапа напомним, что этот этап предназначен для экспериментов по определению оптимальных размеров растрового изображения для различных дисплеев и браузеров.
Полученные при этом результаты представлены в разделе 3 настоящего доклада.
На втором и последующих этапах нашего примера изображение вначале копируется с первого холста на другой холст, а затем контур фигуры выделяется фильтром на изображении. Таким образом, мы как-бы моделируем фотоснимки раковины, проверяя возможные результаты на тестовых фигурах.
Ниже приведена функция, предназначенная для копирования рисунка, получаемого на первом этапе.
Для копирования используется новая возможность HTML5 - поддержка интернет-адресов (или путей к файлу) типа dataURL (URL с данными-закодированными изображениями). То есть, такие адреса содержат не только пути (ссылки) к изображению, но сами изображения в закодированном виде (обычно Base64). Поэтому мы имеем возможность превратить содержимое холста в такой адрес с изображением (метод toDataURL("MIME-тип_изображения")), загрузить его в объект-изображение, не отображая это изображение, и затем загрузить и отобразить на холсте (метод drawImage(адрес_изображения, 0, 0)). Такая последовательность действий осуществляется с помощью операторов:
var srcImg = cnvs_in.toDataURL("image/png"); // Создаём dataURL, взяв изображение из первого холста
var img_ellips = new Image(); // Создаём элемент Image
img_ellips.src = srcImg; // и передаём ему путь к dataURL
img_ellips.onload = function() {
cntxt_out.drawImage(img_ellips, 0, 0); // Загружаем объект-изображение на нужный холст
}
Для выделения используется упрощённый вариант фильтра Собела [10,11]. Фильтр Собела по существу состоит в вычислении и анализе градиента перепада цветов. Упрощение заключается в том, что мы вычисляем лишь модуль градиента, без учёта направления. Такое упрощение оправдано тем, что мы имеем дело с предварительно обработанными изображениями (фон одного цвета), а само изображение обладает "гладким" и достаточно резко выделяющимся контуром. Тем не менее, без использования подобного фильтра обойтись не удаётся, поскольку на изображении могут быть участки, которые необходимо отфильтровать (кроме возможности обнаружения перепадов цвета, фильтр обладает сглаживающими свойствами).
Ниже приводится код функции по вычислению массива модулей градиента для выделения контура фигуры по Собелу.
Рассчитывается модуль градиента только лищь для одного, первого, цвета. В используемой цветовой rgba-модели - это красный цвет. Как показали эксперименты, фильтрации по одному цвету для нашей задачи оказалось достаточно. Функция возвращает двумерный массив модулей градиента, а для визуального контроля пиксели с модулями градиента, превышающими заданный порог, окрашиваются в зелёный цвет. Значение порога подобрано экспериментально.
Отличие в алгоритмах расчёта площади и периметра на третьем и четвёртом этапах состоит в необходимости учёта разрешения изображения фигуры в миллиметрах (количества миллиметров в пикселе) по горизонтали и по вертикали.
Первый этап можно использовать для определения того, как будут меняться ошибки определения площади и периметра в зависимости от типа используемых дисплея [12÷16] и браузера при изменении размера тестовой фигуры в пикселах. В качестве тестовой фигуры возьмём эллипс с соотношением сторон 2:1. При изменении размеров фигуры меняется разрешение изображения, от которого зависит точность определения площади и периметра. Последовательно увеличивая размер фигуры можно проследить изменение точности.
Для выполнения этой задачи студенты группы МК-1 собрали необходимую статистику, запуская наш пример и выполняя с его помощью расчёты первого этапа на имеющихся дисплеях в браузерах: Opera, Firefox, Safari и Chrome.
Некоторые результаты, типичные для всех использованных студентами десктопных дисплеев и ноутбуков, представлены на размещённых ниже графиках. По оси абсцисс отложены средние размеры фигуры (среднее геометрическое, в пикселах), по оси ординат - относительная ошибка расчёта площади или периметра (в процентах).
Ошибка определения площади и периметра для всех браузеров на различных типах мониторов [12÷16] снижается при увеличении размера изображения, но уже при размере более 170 пикселей ошибка уменьшается незначительно. На основании подобных экспериментов можно сделать вывод, что не имеет смысла делать снимки более 170 px, поскольку точность определения площади и периметра для снимков большего разрешения существенно не улучшится. В то же время небольшие размеры изображений позволят существенно уменьшить размер файлов базы данных для их хранения.
Следует отметить, что динамика и знак ошибок в Google Chrome заметно отличается от остальных браузеров. В этом браузере расчётные значения площади и периметра превышают истинные (ошибаемся в сторону увеличения площади и периметра) и при увеличении разрешения снимка остаются на достаточно большом уровне (около 0.3% для площади и 0.2% для периметра), тогда как для остальных браузеров ошибка приближается к нулю. Наилучшими показателями для нашей задачи обладает Safari.
Проводились также одиночные эксперименты со смартфонами и планшетами, работающие с браузерами Opera и Chrome. Предварительно можно сказать, что получаются аналогичные результаты, но величина ошибок колеблется для различных масштабов изображений, так как приходится использовать zoom.
Второй этап можно использовать для определения того, как будут меняться ошибки определения площади и периметра в зависимости от типа используемых дисплея и браузера и от размера изображения тестовой фигуры. В отличие от первого этапа эта фигура выделяется посредством фильтра и, конечно, каким-то образом искажается. Поэтому сделанные выше выводы для идеальной фигуры, неискажённой фильтрацией, требуют уточнения.
Результаты, экспериментов, аналогичных тем, которые проводились при использовании первого этапа, демонстрируются на рис.4.1 и 4.2.
На основании этих экспериментов можно сделать вывод, что размеры изображения для измерения площади долны быть около 270 пикселей. Изображения больших размеров не следует делать в целях экономии места базы данных, предназначенной для хранения изображений.
При этом уровень ошибок, показанных на рис.4.1 и 4.2 соответствуют 100%-ному масштабу изображения. При изменении масштаба (zoom, зуммировании) уровень ошибок колеблется, но ход ошибок аналогичен показанному на рисунках. Причина таких колебаний понятна - из-за изменений размера виртуального пиксела при изменении масштаба изображения [17,18]. Более точные результаты получаются при совпадении физического и виртуального пикселей, то есть, когда совпадает разрешение монитора и разрешение изображения. Менее точные, - когда на виртуальный пиксел приходится не целое число физических пикселей. Поэтому на втором и третьем этапах лучше проводить расчёты площади и периметра без масштабирования (зуммирования) изображения.
Последнее правило легко соблюдать для десктопов. Как показали эксперименты, для планшетов и мобильных устройств с маленькими экранами использование нашего примера также возможно, но требуется доработка по автоматизации изменения масштаба изображения в зависимости от характеристик дисплея [18]).
Третий этап нашего примера можно использовать для исследования влияния ошибок измерения ширины и высоты эллипса. Изображение заданных размеров, также, как и на втором этапе, копируется из первого холста. Кроме этого, задаются размеры фигуры в миллиметрах и ошибка измерения. Вычисляются три типа ошибок вычисления площади и периметра: 1) за счёт ошибок измерения линейных размеров; 2) за счёт ошибок представления фигуры в виде растрового изображения; 3) суммарные ошибки.
Учитывая, что при измерении размеров раковин имеется возможность использовать штангенциркули, имеющие различные точности измерений (0.1, 0.05 и 0.02 мм), можно рассчитать относительную точность расчёта площади и периметра. На рис.5.1 и рис.5.2 представлены результаты таких расчётов.
В предыдущем разделе было показано, что изображения со средними размерами в 270 пикселей являются оптимальными для нашей задачи. Для изображений такого размера относительные ошибки определения площади равны примерно 0.3% для площади и 0.2% для периметра. Поэтому можно потребовать, чтобы ошибки площади и периметра за счёт ошибок измерения не превышали соответственно 0.3% и 0.2%. На рис.5.1 и 5.2 видно, что эти условия выполняются при следующих соотношениях размеров фигур и точности измерений:
при размерах до 30 мм, точность измерений - 0.02 мм;
при размерах от 30 до 60-ти мм, точность измерений - 0.05 мм;
при размерах от 60 мм, точность измерений - 0.1 мм;
Эти условия можно рекомендовать при проведении измерений размеров раковины.
При выполнении измерений с рекомендуемой точностью для изображений с размерами 270 и более пикселей суммарные относительные ошибки расчёта площади и периметра не должны превышать соответственно 0.6% и 0.4%. При желании, проверку выполнения полученных нами результатов можно сделать с помощью этапа 3 веб-инструмента.
Проверку выводов предыдущих разделов доклада можно выполнить, используя четвёртый этап нашего примера на снимках и по результатам измерений раковин мидий из базы данных, наполняемой в настоящее время студентами группы МК-5 в рамках некоторых курсовых работ.
В дополнение к линейным размерам обмерялись также периметры контуров изображений, которые использовались в качестве тестовых значений. Расхождения между вычисляемыми и тестовыми значениями периметров не превышают точности обмеров периметра.
Расчёт ошибок можно осуществить, используя четвёртый, первый и третий этапы в предлагаемой ниже последовательности действий на примере одного тестового снимка раковины.
- Используя кнопку "Загрузка" формы четвёртого этапа, выбираем необходимый снимок и загружаем его, размещая на холсте. Результат показан на рис.6.1.
Рис.6.1 Пример изображения раковины, помещённого на холст с помощью кнопки "Загрузка".
Одновременно с изображением загружаются значения измерений ширины и высоты, полученные при обмерах раковины штангенциркулем, а также инструментальная погрешность используемого штангенциркуля.
Нажимая на кнопку "Расчёты" этой же формы получаем результаты расчётов площади и периметра, показанные на рис.6.2.
Рис.6.2 Пример изображения раковины с выделенным контуром и предварительных результатов, получаемых с помощью кнопки "Расчёты".
Как видно из рисунка, определяются пока что только лишь погрешности (косвенные погрешности) определения площади и периметра за счёт инструментальных погрешностей определения ширины и высоты (прямые погрешности). Погрешности за счёт разрешения снимка, приближения растром, и, соответственно, суммарные ошибки, ещё не определяются.
Но, поскольку теперь нам известны размеры изображения в пикселах и миллиметрах, мы можем определить незвестные погрешности с помощью первого и третьего этапов нашего примера. При этом предполагается, что погрешности для эллипса с шириной и высотой раковины, будут равны погрешностям для самой раковины. При этом исходим из того факта, что форма эллипса, её площадь и периметр примерно равны аналогичным характеристикам раковины и алгоритмы их выделения и определения одни и те же.
- Возвращаемся к форме первого этапа и, введя ширину и высоту раковины (379 и 228 px), рисуем эллипс, с линейными размерами, эквивалентными размерам нашей раковины (рис.6.3).
Рис.6.3 Пример эллипса, получаемого с помощью кнопки "Рисуем", с разрешением, эквивалентным разрешению снимка раковины.
Для нас сейчас задача этого этапа - только лишь рисование эллипса, а расчёты площади, периметра и погрешностей выполним далее, используя третий этап.
- Переходим к форме третьего этапа, с помощью кнопки "Изображение" переносим на холст этого этапа полученное выше изображение эллипса, затем вводим размеры нашей раковины в миллиметрах и погрешность штангенциркуля в соответствующие поля, нажимаем на кнопку "Площадь и периметр" и получаем результаты, показанные на рис.6.4.
Рис.6.4 Пример изображения эллипса с выделенным контуром и результатов, получаемых с помощью кнопки "Площадь и периметр".
Следует учитывать, что погрешность (ошибка) на этом этапе задаётся числом 0.02, как это обычно пишется в паспорте штангенциркуля, а на этапе 4 мы видим эту погрешность как ±0.01 (в таком виде она вводится в базу данных при обмерах раковины.
- Возвращаемся к форме четвёртого этапа, повторяем загрузку всё того же снимка раковины и получаем результаты с помощью кнопки "Расчёты" (показаны на рис.6.5).
Рис.6.5 Пример изображения раковины с выделенным контуром и окончательных результатов, полученных с помощью кнопки "Расчёты".
В результате проделанной работы:
разработан и предоставлен для свободного использования пример, явлющийся HTML-документом с веб-инструментами, реализованными в виде скриптов на языке Javascript, позволяющими проводить эксперименты по исследованию погрешностей определения площадей и периметров тестовых изображений эллипса и раковин черноморской мидии, в различных браузерах и мониторах;
проведены эксперименты, результаты которых демонстрирут возможность использования средств HTML5 Canvas API для создания полезных инструментов, предназначенных для определения морфометрических характеристик раковины (пока что на примере инструментов для определения площади и периметра);
на основании экспериментов даются рекомендации по измерению линейных размеров и получению цифровых снимков, позволяющих определять площади и периметров сечений раковин с необходимой точностью. При этом, небольшой объём фотографий позволяет использовать базу данных небольших размеров, размещаемую на бесплатных серверах интернет-провайдеров.
В заключение необходимо сказать, что выполненная работа является одним из этапов недавно задуманного с нашим участием проекта "Мидия", предназначенного для накопления данных о черноморской мидии и последующих морфометрических измерений с помощью средств манипуляции 2D- и 3D-изображениями, предусмотренных стандартами консорциума W3C и в рамках подхода WEB2.0
Про HTML5 Canvas
- Canvas. Краткий обзор: http://codestorage.ru/vvedenie-v-canvas-2d-api/
- Canvas. Обзор с примерами: http://www.pixelcom.crimea.ua/rukovodstvo-po-html5-canvas.html
- Canvas. Сводка методов и свойств: http://webonrails.ru/articles/html5/189/
- Лабберс П., Олберс Б., Салим Ф. HTML5 для профессионалов: мощные инструменты для разработки современных веб-приложений. : Пер. с англ. - М. ООО "И.Д. Вильямс". 2011. - 272 с. (Элемент Canvas: c. 41-78).
- Canvas. использование data URL (метод canvas.toDataURL): http://www.thevista.ru/page.php?id=13971
- Выводим текст на HTML5 Canvas: http://habrahabr.ru/post/140235/
- Canvas. Анимации спрайтов в canvas с EaselJS: http://www.pixelcom.crimea.ua/html5-igry-canvas.html
Ссылки по WebGL API для Canvas HTML
- Введение в WebGL (англ.): http://dev.opera.com/articles/view/an-introduction-to-webgl/
- Постобработка изображений с WebGL с использованием texImage2D в Canvas HTML: http://css-live.ru/articles/postobrabotka-izobrazhenij-i-video-s-webgl.html
Ссылки по выделению контура и расчёту периметра растрового изображения
- Выделение и описание контуров: http://wiki.technicalvision.ru/index.php/Выделение_и_описание_контуров
- Алгоритмы выделения контуров изображений: http://habrahabr.ru/post/114452/#habracut
Ссылки по работе с изображениями на экранах различного разрешения
- Типы мониторов с различным разрешением: http://ru.wikipedia.org/wiki/Разрешение_(компьютерная_графика)
- Расчёт разрешения в ppi (англ. pixels per inch): http://ru.wikipedia.org/wiki/Ppi
- Диагональ и разрешение различных мониторов: http://www.kreker.org/my/16
- Калькулятор разрешения вашего монитора: http://force-net.com/2009/10/blog-post.html
- Калькулятор разрешений различных мониторов: http://axofiber.org.ru/inside/pixel.size.htm
- Проблемы десктопных браузеров: http://www.beskrovnyy.com/verstka/skaz-o-dvux-vyuportax-chast-pervaya/
- Проблемы мобильных браузеров: http://www.beskrovnyy.com/verstka/skaz-o-dvux-vyuportax-chast-vtoraya/
Canvas (русс. - холст) встраивается в HTML-документ с помощью тэга <canvas>...</canvas>
К этому тэгу, как и к другим тэгам, можно обращаться из скриптов, как к объекту доменной модели или по имени. Все свойства и методы API содержатся в так называемых контекстах - объектах, посвящённых какому-то типу графики. Мы будем использовать контекст для работы с 2D-изображениями. Этого достаточно, хотя эти и другие, более развитые методы, имеются в контексте для работы с 3D.
Итак, встраиваем элемент Canvas в HTML-документ и получаем его в скрипте, как объект. Затем получаем 2D-контекст для этого объекта:
Координаты холста отсчитываются от точки x=0, y=0, находящейся в левом верхнем углу (начало координат или начало отсчёта). Координаты увеличиваются в горизонтальном направлении вправо и в вертикальном - вниз. Каждая координата обозначает местонахождение соответствующего пиксела.
Обо всех свойствах и методах API HTML5 Canvas и примерах их применения можно узнать в многочисленных источниках, например, здесь [1,2,3]. Ниже кратко рассмотриваются лишь свойства и методы API HTML5 Canvas, используемые в наастоящем примере.
Стили пера и кисти
- cntx.strokeStyle - свойство, определяет стиль пера;
- cntx.fillStyle - свойство, определяет стиль кисти.
- cntx.lineWidth - свойство, определяет ширину линии (пера).
В качестве значений этих свойств можно использовать:
- RGB-цвет в шестнадцатеричном виде. Например, "#ff0000";
- RGB-функцию вида "rgb(красный, зеленый, синий)". Например, "rgb(255,0,0)" и "rgb(100%, 0%, 0%)";
- RGBА-функцию вида "rgbа(красный, зеленый, синий, альфа-канал)". Например "rgbа(255,0,0,0.5)";
- а также методы, создающие различные градиенты (мы не будем здесь использовать);
- ширину линии будем задавать в пикселах.
Вот примеры задания этих свойств:
- cntx.strokeStyle = "#ff0";
- cntx.fillStyle = "rgbа(255, 0, 0, 0.5)";
- cntx.lineWidth = 4.
Методы для рисования прямоугольников
- cntx.rect(x, y, width, height) - выводит прямоугольник;
- cntx.strokeRect(x,y,width,height) - выполняет визуализацию контура прямоугольника;
- cntx.fillRect(x,y,width,height) - выполняет визуализацию закраски прямоугольника;
- cntx.clearRect(x,y,width,height) - очищает прямоугольную область от любого содержимого и восстанавливает исходный прозрачный цвет.
Эти методы принимают в качестве аргументов координаты левого верхнего угла прямоугольника, а также его ширину и высоту.
Методы для рисования сложного контура
- cntx.beginPath() - начинает сложный контур;
- cntx.moveTo(x, y) - перемещает текущие координаты пера в (x, y);
- cntx.lineTo(x, y) - рисует линию от текущих координат до указанных в (x, y);
- cntx.closePath() - завершает сложный контур;
- cntx.arc(x, y, radius) - выводит окружность;
- cntx.stroke() - выполняет визуализацию построенного контура на холсте;
- cntx.fill() - выполняет визуализацию закраски кистью;
- cntx.isPointInPath(x,y) - определяет, находится ли точка внутри контура, возвращая true или false.
Использование первых четырех методов позволяет построить контур в виде многоугольника. В примере мы их не используем, а здесь они приводятся для того, чтобы было понятно, что методы API HTML5 Canvas похожи на функции, используемые в других языках программирования. Рисование окружности и круга выполнить ещё более просто для этого надо вызвать методы: cntx.beginPath(); cntx.arc(x, y, radius); cntx.closePath(); и затем отобразить построение с помощью cntx.stroke() - для окружности и cntx.fill() - для круга.
Методы преобразований
- scale(x, y) - масштабирование;
- cntx.translate(x, y) - линейный сдвиг координат;
- cntx.save() - сохранить состояние;
- cntx.restore() - востановить состояние.
Перечисленные методы достаточно просты и их не трудно понять, усвоив материал из раздела 3. Имеются также более сложные методы преобразований (и анимации), с помощью которых можно получить поразительные 2D (и даже 3D) эффекты, но их следует использовать только лишь, если вы хорошо понимаете математические алгоритмы, лежащие в основе 2Dграфики.
Для построения эллипса используется метод преобразования окружности, который должен быть известен вам из школьной геометрии.
Использование данных типа dataURL
Для копирования изображения из одного холста на другой можно использовать новую возможность HTML5 - поддержка интернет-адресов (или путей к файлу) типа dataURL (URL с данными-закодированными изображениями). То есть, такие адреса содержат не только пути (ссылки) к изображению, но сами изображения в закодированном виде (обычно используется Base64). Поэтому мы имеем возможность превратить содержимое холста в такой адрес с изображением (метод toDataURL("MIME-тип_изображения")), не сохраняя его на внешний носитель, загрузить его в объект-изображение, не отображая это изображение, и затем загрузить и отобразить на холсте (метод drawImage(адрес_изображения, 0, 0)). Такую последовательность действий можно осуществить с помощью операторов:
var srcImg = cnvs_in.toDataURL("image/png"); // Создаём dataURL, взяв изображение из первого холста
var img_ellips = new Image(); // Создаём элемент Image
img_ellips.src = srcImg; // и передаём ему путь к dataURL
img_ellips.onload = function() {
cntxt_out.drawImage(img_ellips, 0, 0); // Загружаем объект-изображение на нужный холст
}