Русский
Русский
English
Статистика
Реклама

Лайфхаки

Как создать и у всех на виду хранить пароли, очень стойкие и очень длинные, не запоминая их

22.12.2020 20:16:02 | Автор: admin

Приветствую читателей!

Меня зовут Андреас, давно веду видеоблог (SunAndreas) на темы гражданской информационной безопасности, а сегодня решил в текстовом варианте поделиться со всеми полезными идеями, которые могут быть полезными даже для далёких от ИТ людей. Расскажите об этом старшему поколению, таки они смогут хранить пароли довольно безопасно, в том числе на работе, не используя дополнительные программы, менеджеры паролей.

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

Способ 1

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

Для удобства набор символов, таблица, желательно чтобы была равносторонней, то есть или 10х10, или 25х25, или 100х100, или иной вариант который вы пожелаете, но не обязательно, и желательно чтобы в итоге ваш пароль в итоге был не менее 20 символов. Чем больше различных букв, цифр, символов в таблице, тем безопаснее. Хорошо не забывать и про использование как строчных, так и заглавных букв в своём наборе, что усложнит попытки подбора пароля злоумышленникам. Таблицы можно легко удобно создавать в LibreOffice Calc или если вы сидите на мелко-мягкой проприетарщине, то MS Excel.

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

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

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

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

Но уже с такой таблицей может хватить и даже всего лишь одного слова. И согласитесь, одно слово уж можно запомнить и держать в своей голове. Более того, для всех ваших аккаунтов можно запомнить всего лишь одно слово! Это же превосходно! Для этого нужно лишь создать таблицы с разным набором символов и букв. В этом случае наборы символов, таблицы, будут меняться, а воображаемое слово или фигура, или предложение будут одни и те же. Сделайте только отметки на бумажных таблицах с зади или назовите соответствующим образом файлы-картинки на компьютере, что бы не забыть какая таблица для какого аккаунта предназначена.

Так мы убиваем сразу нескольких зайцев. Не надо особо прятать пароль от посторонних глаз, можно создавать сверх сложные пароли и не надо их постоянно помнить. Если таблицы вы храните на компьютере, то можете менять расширения файлов, например, с jpg на txt. Иногда это может быть дополнительной защитой от посторонних глаз.

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

Способ 2

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

И это одно слово можно использовать для множества ваших аккаунтов, но рассчитывая для этого сохранённого файла разные хеш-суммы, на ваш выбор. И это не только md5 и sha256, но и множество иных.

А если вы опасаетесь, что вашему неприятелю, или мне, известен такой метод и мы можем похитить ваш файл с парольным словом, рассчитать хэш и получу доступ к вашим аккаунтам, то вы можете к полученным хеш-суммам добавлять в любое место хеш-строки ещё любой один символ или, если хотите, хоть целое слово. Таким образом вы усложните задачу в разы вашим недоброжелателям, даже если они знают мой метод хранения паролей.

При использовании хэш-сумм, вам только нужно помнить какая хэш-строка для какого аккаунта, ведь мы помним, что один и тот же пароль не следует использовать для всех аккаунтов.

А для расчёта хеш-сумм можно использовать разные утилиты. Для GNU/Linux систем можете установить GTKHash.

Для которой существуют удобные плагины для обычных файловых менеджеров. После установки плагинов вы сможете просто: 1. правой клавишей мыши нажать на файл, 2. выбрать свойcтва, 3. и на вкладке Дайджесты выбрать для рассчёта нужные вам хеш-суммы.

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

Заключение

Озвученные мной идеи, можно развивать, это своего рода opensource. Я описал лишь фундамент. Надеюсь, что это вам пригодиться, а если не пригодиться, то это хотя бы немного разовьёт вашу смекалку. Успехов.

Подробнее..

Больше, больше фронтенда доклады c ЮMoneyDay

15.12.2020 16:09:41 | Автор: admin
Готовы потереть за фронтенд? Мы да. Поэтому публикуем новую партию полезного видеоматериала от экспертов крупного платежного сервиса. Под катом найдете 2 нескучных доклада про:

  • лайфхаки в CSS, HTML и JS,
  • архитектуру фронтенда ЮMoney.

Устраивайтесь поудобнее: возможно, такого вы раньше не слышали.



Лайфхаки фронтендера


Михаил Степанов, старший разработчик интерфейсов
10 лайфхаков в CSS, HTML и JS (о которых вы, возможно, даже не догадывались)

2:13 Предыстория, или причем здесь тигры
3:26 Лайфхак #1. Linking: новый способ подключения стилей
6:55 Лайфхак #2. Carousel: спецификация Scroll Snap
9:25 Лайфхак #3. Использование атрибута Base tag
11:14 Лайфхак #4. Ping
13:25 Лайфхак #5. Datails: нативные аккордеоны
14:54 Лайфхак #6. Select в несколько строк
17:22 Лайфхак #7. Inputs outside form
19:37 Лайфхак #8. Disable inputs
20:57 Лайфхак #9. Popup
22:28 Лайфхак #10. Scroll
24:12 Сколько новых тигров вы открыли для себя?





Обзор архитектуры фронтенда ЮMoney


Илья Кашлаков, руководитель отдела разработки интерфейсов
В своем докладе Илья расскажет, как развивалась архитектура фронтенда ЮMoney. Обсудим влияние легаси на развитие архитектуры. И то, как мы боремся с легаси. Расскажу про новые подходы как к серверной архитектуре фронтенда, так и к клиентской на чем они основаны и чего мы стремимся достичь в разработке.

1:39 Справка о спикере
2:38 Коротко про фронтенд ЮMoney
2:57 Почему нужна платформенная команда
4:30 Server Side: направления развития
5:53 Инфраструктурное направление: про переход от монолита к микросервисам, возникшие проблемы
9:27 Что такое заготовка и зачем она? Проблемы заготовки
13:28 Заготовка должна быть лучше и лучше
19:00 Архитектурное направление: обзор модели MVC
20:17 Чистой функции хватит всем!
21:47 Что было не так с чистой функцией?
24:06 New age: как архитектура выглядит сейчас
25:42 Что нам дает выбор Nest
28:25 Client Side: от классического фронтенда до современного





Все доклады с большой ИТ-конференции ЮMoneyDay. На подходе материалы про SQL, DevOps, PM, тестирование и мобильную разработку.

Подробнее..

Перевод Обнаружение эмоций на лице в браузере с помощью глубокого обучения и TensorFlow.js. Часть 2

02.03.2021 20:22:33 | Автор: admin

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

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

Соединив наш код отслеживания лица с набором данных об эмоциях на лице FER, мы обучим вторую нейросетевую модель определять эмоции человека по нескольким трехмерным ключевым точкам.


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

Настройка по данным об эмоциях на лице FER2013

Мы используем код для отслеживания лиц из предыдущей статьи, чтобы создать две веб-страницы. Одна страница будет использоваться для обучения модели ИИ на точках отслеженных лиц в наборе данных FER, а другая будет загружать обученную модель и применять её к тестовому набору данных.

Давайте изменим окончательный код из проекта отслеживания лиц, чтобы обучить нейросетевую модель и применить её к данным о лицах. Набор данных FER2013 состоит более чем из 28 тысяч помеченных изображений лиц; он доступен на веб-сайте Kaggle. Мы загрузили эту версию, в которой набор данных уже преобразован в файлы изображений, и поместили её в папку web/fer2013. Затем мы обновили код сервера NodeJS в index.js, чтобы он возвращал список ссылок на изображения по адресу http://localhost:8080/data/. Поэтому вы можете получить полный объект JSON, если запустите сервер локально.

Чтобы упростить задачу, мы сохранили этот объект JSON в файле web/fer2013.js, чтобы вы могли использовать его напрямую, не запуская сервер локально. Вы можете включить его в другие файлы скриптов в верхней части страницы:

<script src="web/fer2013.js"></script>

Мы собираемся работать с изображениями, а не с видео с веб-камеры (не беспокойтесь, мы вернёмся к видео в следующей статье). Поэтому нам нужно заменить элемент<video> элементом <img>и переименовать его ID в image. Мы также можем удалить функцию setupWebcam, так как для этого проекта она не нужна.

<img id="image" style="    visibility: hidden;    width: auto;    height: auto;    "/>

Далее добавим служебную функцию, чтобы задать изображение для элемента, и ещё одну, чтобы перетасовать массив данных. Так как исходные изображения имеют размер всего 48x48 пикселей, давайте для большего выходного размера зададим 500 пикселей, чтобы получить более детальное отслеживание лиц и возможность видеть результат в более крупном элементе canvas. Также обновим служебные функции для линий и многоугольников, чтобы масштабировать в соответствии с выходными данными.

async function setImage( url ) {    return new Promise( res => {        let image = document.getElementById( "image" );        image.src = url;        image.onload = () => {            res();        };    });}function shuffleArray( array ) {    for( let i = array.length - 1; i > 0; i-- ) {        const j = Math.floor( Math.random() * ( i + 1 ) );        [ array[ i ], array[ j ] ] = [ array[ j ], array[ i ] ];    }}const OUTPUT_SIZE = 500;

Нам понадобятся некоторые глобальные переменные: для списка категорий эмоций, списка агрегированных массивов данных FER и индекса массива:

const emotions = [ "angry", "disgust", "fear", "happy", "neutral", "sad", "surprise" ];let ferData = [];let setIndex = 0;

Внутри блока async мы можем подготовить и перетасовать данные FER и изменить размер элемента canvas до 500x500 пикселей:

const minSamples = Math.min( ...Object.keys( fer2013 ).map( em => fer2013[ em ].length ) );Object.keys( fer2013 ).forEach( em => {    shuffleArray( fer2013[ em ] );    for( let i = 0; i < minSamples; i++ ) {        ferData.push({            emotion: em,            file: fer2013[ em ][ i ]        });    }});shuffleArray( ferData );let canvas = document.getElementById( "output" );canvas.width = OUTPUT_SIZE;canvas.height = OUTPUT_SIZE;

Нам нужно в последний раз обновить шаблон кода перед обучением модели ИИ на одной странице и применением обученной модели на второй странице. Необходимо обновить функцию trackFace, чтобы она работала с элементом image, а не video. Также требуется масштабировать ограничивающий прямоугольник и выходные данные сетки для лица в соответствии с размером элемента canvas. Мы зададим приращение setIndex в конце функции для перехода к следующему изображению.

async function trackFace() {    // Set to the next training image    await setImage( ferData[ setIndex ].file );    const image = document.getElementById( "image" );    const faces = await model.estimateFaces( {        input: image,        returnTensors: false,        flipHorizontal: false,    });    output.drawImage(        image,        0, 0, image.width, image.height,        0, 0, OUTPUT_SIZE, OUTPUT_SIZE    );    const scale = OUTPUT_SIZE / image.width;    faces.forEach( face => {        // Draw the bounding box        const x1 = face.boundingBox.topLeft[ 0 ];        const y1 = face.boundingBox.topLeft[ 1 ];        const x2 = face.boundingBox.bottomRight[ 0 ];        const y2 = face.boundingBox.bottomRight[ 1 ];        const bWidth = x2 - x1;        const bHeight = y2 - y1;        drawLine( output, x1, y1, x2, y1, scale );        drawLine( output, x2, y1, x2, y2, scale );        drawLine( output, x1, y2, x2, y2, scale );        drawLine( output, x1, y1, x1, y2, scale );        // Draw the face mesh        const keypoints = face.scaledMesh;        for( let i = 0; i < FaceTriangles.length / 3; i++ ) {            let pointA = keypoints[ FaceTriangles[ i * 3 ] ];            let pointB = keypoints[ FaceTriangles[ i * 3 + 1 ] ];            let pointC = keypoints[ FaceTriangles[ i * 3 + 2 ] ];            drawTriangle( output, pointA[ 0 ], pointA[ 1 ], pointB[ 0 ], pointB[ 1 ], pointC[ 0 ], pointC[ 1 ], scale );        }    });    setText( `${setIndex + 1}. Face Tracking Confidence: ${face.faceInViewConfidence.toFixed( 3 )} - ${ferData[ setIndex ].emotion}` );    setIndex++;    requestAnimationFrame( trackFace );}

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

1. Глубокое изучение эмоций на лице

В этом первом файле веб-страницы мы собираемся задать обучающие данные, создать нейросетевую модель, а затем обучить её и сохранить веса в файл. В код включена предварительно обученная модель (см. папку web/model), поэтому при желании можно пропустить эту часть и перейти к части2.

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

let trainingData = [];function emotionToArray( emotion ) {    let array = [];    for( let i = 0; i < emotions.length; i++ ) {        array.push( emotion === emotions[ i ] ? 1 : 0 );    }    return array;}

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

// Add just the nose, cheeks, eyes, eyebrows & mouthconst features = [    "noseTip",    "leftCheek",    "rightCheek",    "leftEyeLower1", "leftEyeUpper1",    "rightEyeLower1", "rightEyeUpper1",    "leftEyebrowLower", //"leftEyebrowUpper",    "rightEyebrowLower", //"rightEyebrowUpper",    "lipsLowerInner", //"lipsLowerOuter",    "lipsUpperInner", //"lipsUpperOuter",];let points = [];features.forEach( feature => {    face.annotations[ feature ].forEach( x => {        points.push( ( x[ 0 ] - x1 ) / bWidth );        points.push( ( x[ 1 ] - y1 ) / bHeight );    });});// Only grab the faces that are confidentif( face.faceInViewConfidence > 0.9 ) {    trainingData.push({        input: points,        output: ferData[ setIndex ].emotion,    });}

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

async function trackFace() {    // Fast train on just 200 of the images    if( setIndex >= 200 ) {        setText( "Finished!" );        trainNet();        return;    }    ...}

Наконец, мы пришли к той части, которую так долго ждали: давайте создадим функцию trainNet и обучим нашу модель ИИ!

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

async function trainNet() {    let inputs = trainingData.map( x => x.input );    let outputs = trainingData.map( x => emotionToArray( x.output ) );    // Define our model with several hidden layers    const model = tf.sequential();    model.add(tf.layers.dense( { units: 100, activation: "relu", inputShape: [ inputs[ 0 ].length ] } ) );    model.add(tf.layers.dense( { units: 100, activation: "relu" } ) );    model.add(tf.layers.dense( { units: 100, activation: "relu" } ) );    model.add(tf.layers.dense( {        units: emotions.length,        kernelInitializer: 'varianceScaling',        useBias: false,        activation: "softmax"    } ) );    model.compile({        optimizer: "adam",        loss: "categoricalCrossentropy",        metrics: "acc"    });    const xs = tf.stack( inputs.map( x => tf.tensor1d( x ) ) );    const ys = tf.stack( outputs.map( x => tf.tensor1d( x ) ) );    await model.fit( xs, ys, {        epochs: 1000,        shuffle: true,        callbacks: {            onEpochEnd: ( epoch, logs ) => {                setText( `Training... Epoch #${epoch} (${logs.acc.toFixed( 3 )})` );                console.log( "Epoch #", epoch, logs );            }        }    } );    // Download the trained model    const saveResult = await model.save( "downloads://facemo" );}

На этом всё! На этой веб-странице модель ИИ будет обучена распознавать выражения лиц в различных категориях, и вы получите модель для загрузки и применения. Это мы и сделаем далее.

1. Финишная прямая

Вот полный код обучения модели на наборе данных FER:
<html>    <head>        <title>Training - Recognizing Facial Expressions in the Browser with Deep Learning using TensorFlow.js</title>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.4.0/dist/tf.min.js"></script>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow-models/face-landmarks-detection@0.0.1/dist/face-landmarks-detection.js"></script>        <script src="web/triangles.js"></script>        <script src="web/fer2013.js"></script>    </head>    <body>        <canvas id="output"></canvas>        <img id="image" style="            visibility: hidden;            width: auto;            height: auto;            "/>        <h1 id="status">Loading...</h1>        <script>        function setText( text ) {            document.getElementById( "status" ).innerText = text;        }        async function setImage( url ) {            return new Promise( res => {                let image = document.getElementById( "image" );                image.src = url;                image.onload = () => {                    res();                };            });        }        function shuffleArray( array ) {            for( let i = array.length - 1; i > 0; i-- ) {                const j = Math.floor( Math.random() * ( i + 1 ) );                [ array[ i ], array[ j ] ] = [ array[ j ], array[ i ] ];            }        }        function drawLine( ctx, x1, y1, x2, y2, scale = 1 ) {            ctx.beginPath();            ctx.moveTo( x1 * scale, y1 * scale );            ctx.lineTo( x2 * scale, y2 * scale );            ctx.stroke();        }        function drawTriangle( ctx, x1, y1, x2, y2, x3, y3, scale = 1 ) {            ctx.beginPath();            ctx.moveTo( x1 * scale, y1 * scale );            ctx.lineTo( x2 * scale, y2 * scale );            ctx.lineTo( x3 * scale, y3 * scale );            ctx.lineTo( x1 * scale, y1 * scale );            ctx.stroke();        }        const OUTPUT_SIZE = 500;        const emotions = [ "angry", "disgust", "fear", "happy", "neutral", "sad", "surprise" ];        let ferData = [];        let setIndex = 0;        let trainingData = [];        let output = null;        let model = null;        function emotionToArray( emotion ) {            let array = [];            for( let i = 0; i < emotions.length; i++ ) {                array.push( emotion === emotions[ i ] ? 1 : 0 );            }            return array;        }        async function trainNet() {            let inputs = trainingData.map( x => x.input );            let outputs = trainingData.map( x => emotionToArray( x.output ) );            // Define our model with several hidden layers            const model = tf.sequential();            model.add(tf.layers.dense( { units: 100, activation: "relu", inputShape: [ inputs[ 0 ].length ] } ) );            model.add(tf.layers.dense( { units: 100, activation: "relu" } ) );            model.add(tf.layers.dense( { units: 100, activation: "relu" } ) );            model.add(tf.layers.dense( {                units: emotions.length,                kernelInitializer: 'varianceScaling',                useBias: false,                activation: "softmax"            } ) );            model.compile({                optimizer: "adam",                loss: "categoricalCrossentropy",                metrics: "acc"            });            const xs = tf.stack( inputs.map( x => tf.tensor1d( x ) ) );            const ys = tf.stack( outputs.map( x => tf.tensor1d( x ) ) );            await model.fit( xs, ys, {                epochs: 1000,                shuffle: true,                callbacks: {                    onEpochEnd: ( epoch, logs ) => {                        setText( `Training... Epoch #${epoch} (${logs.acc.toFixed( 3 )})` );                        console.log( "Epoch #", epoch, logs );                    }                }            } );            // Download the trained model            const saveResult = await model.save( "downloads://facemo" );        }        async function trackFace() {            // Fast train on just 200 of the images            if( setIndex >= 200 ) {//ferData.length ) {                setText( "Finished!" );                trainNet();                return;            }            // Set to the next training image            await setImage( ferData[ setIndex ].file );            const image = document.getElementById( "image" );            const faces = await model.estimateFaces( {                input: image,                returnTensors: false,                flipHorizontal: false,            });            output.drawImage(                image,                0, 0, image.width, image.height,                0, 0, OUTPUT_SIZE, OUTPUT_SIZE            );            const scale = OUTPUT_SIZE / image.width;            faces.forEach( face => {                // Draw the bounding box                const x1 = face.boundingBox.topLeft[ 0 ];                const y1 = face.boundingBox.topLeft[ 1 ];                const x2 = face.boundingBox.bottomRight[ 0 ];                const y2 = face.boundingBox.bottomRight[ 1 ];                const bWidth = x2 - x1;                const bHeight = y2 - y1;                drawLine( output, x1, y1, x2, y1, scale );                drawLine( output, x2, y1, x2, y2, scale );                drawLine( output, x1, y2, x2, y2, scale );                drawLine( output, x1, y1, x1, y2, scale );                // Draw the face mesh                const keypoints = face.scaledMesh;                for( let i = 0; i < FaceTriangles.length / 3; i++ ) {                    let pointA = keypoints[ FaceTriangles[ i * 3 ] ];                    let pointB = keypoints[ FaceTriangles[ i * 3 + 1 ] ];                    let pointC = keypoints[ FaceTriangles[ i * 3 + 2 ] ];                    drawTriangle( output, pointA[ 0 ], pointA[ 1 ], pointB[ 0 ], pointB[ 1 ], pointC[ 0 ], pointC[ 1 ], scale );                }                // Add just the nose, cheeks, eyes, eyebrows & mouth                const features = [                    "noseTip",                    "leftCheek",                    "rightCheek",                    "leftEyeLower1", "leftEyeUpper1",                    "rightEyeLower1", "rightEyeUpper1",                    "leftEyebrowLower", //"leftEyebrowUpper",                    "rightEyebrowLower", //"rightEyebrowUpper",                    "lipsLowerInner", //"lipsLowerOuter",                    "lipsUpperInner", //"lipsUpperOuter",                ];                let points = [];                features.forEach( feature => {                    face.annotations[ feature ].forEach( x => {                        points.push( ( x[ 0 ] - x1 ) / bWidth );                        points.push( ( x[ 1 ] - y1 ) / bHeight );                    });                });                // Only grab the faces that are confident                if( face.faceInViewConfidence > 0.9 ) {                    trainingData.push({                        input: points,                        output: ferData[ setIndex ].emotion,                    });                }            });            setText( `${setIndex + 1}. Face Tracking Confidence: ${face.faceInViewConfidence.toFixed( 3 )} - ${ferData[ setIndex ].emotion}` );            setIndex++;            requestAnimationFrame( trackFace );        }        (async () => {            // Get FER-2013 data from the local web server            // https://www.kaggle.com/msambare/fer2013            // The data can be downloaded from Kaggle and placed inside the "web/fer2013" folder            // Get the lowest number of samples out of all emotion categories            const minSamples = Math.min( ...Object.keys( fer2013 ).map( em => fer2013[ em ].length ) );            Object.keys( fer2013 ).forEach( em => {                shuffleArray( fer2013[ em ] );                for( let i = 0; i < minSamples; i++ ) {                    ferData.push({                        emotion: em,                        file: fer2013[ em ][ i ]                    });                }            });            shuffleArray( ferData );            let canvas = document.getElementById( "output" );            canvas.width = OUTPUT_SIZE;            canvas.height = OUTPUT_SIZE;            output = canvas.getContext( "2d" );            output.translate( canvas.width, 0 );            output.scale( -1, 1 ); // Mirror cam            output.fillStyle = "#fdffb6";            output.strokeStyle = "#fdffb6";            output.lineWidth = 2;            // Load Face Landmarks Detection            model = await faceLandmarksDetection.load(                faceLandmarksDetection.SupportedPackages.mediapipeFacemesh            );            setText( "Loaded!" );            trackFace();        })();        </script>    </body></html>

2. Обнаружение эмоций на лице

Мы почти достигли своей цели. Применение модели обнаружения эмоций проще, чем её обучение. На этой веб-странице мы собираемся загрузить обученную модель TensorFlow и протестировать её на случайных лицах из набора данных FER.

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

let emotionModel = null;(async () => {    ...    // Load Face Landmarks Detection    model = await faceLandmarksDetection.load(        faceLandmarksDetection.SupportedPackages.mediapipeFacemesh    );    // Load Emotion Detection    emotionModel = await tf.loadLayersModel( 'web/model/facemo.json' );    ...})();

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

async function predictEmotion( points ) {    let result = tf.tidy( () => {        const xs = tf.stack( [ tf.tensor1d( points ) ] );        return emotionModel.predict( xs );    });    let prediction = await result.data();    result.dispose();    // Get the index of the maximum value    let id = prediction.indexOf( Math.max( ...prediction ) );    return emotions[ id ];}

Чтобы между тестовыми изображениями можно было делать паузу в несколько секунд, давайте создадим служебную функцию wait:

function wait( ms ) {    return new Promise( res => setTimeout( res, ms ) );}

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

async function trackFace() {    ...    let points = null;    faces.forEach( face => {        ...        // Add just the nose, cheeks, eyes, eyebrows & mouth        const features = [            "noseTip",            "leftCheek",            "rightCheek",            "leftEyeLower1", "leftEyeUpper1",            "rightEyeLower1", "rightEyeUpper1",            "leftEyebrowLower", //"leftEyebrowUpper",            "rightEyebrowLower", //"rightEyebrowUpper",            "lipsLowerInner", //"lipsLowerOuter",            "lipsUpperInner", //"lipsUpperOuter",        ];        points = [];        features.forEach( feature => {            face.annotations[ feature ].forEach( x => {                points.push( ( x[ 0 ] - x1 ) / bWidth );                points.push( ( x[ 1 ] - y1 ) / bHeight );            });        });    });    if( points ) {        let emotion = await predictEmotion( points );        setText( `${setIndex + 1}. Expected: ${ferData[ setIndex ].emotion} vs. ${emotion}` );    }    else {        setText( "No Face" );    }    setIndex++;    await wait( 2000 );    requestAnimationFrame( trackFace );}

Готово! Наш код должен начать определять эмоции на изображениях FER в соответствии с ожидаемой эмоцией. Попробуйте, и увидите, как он работает.

2. Финишная прямая

Взгляните на полный код применения обученной модели к изображениям из набора данных FER:
<html>    <head>        <title>Running - Recognizing Facial Expressions in the Browser with Deep Learning using TensorFlow.js</title>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.4.0/dist/tf.min.js"></script>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow-models/face-landmarks-detection@0.0.1/dist/face-landmarks-detection.js"></script>        <script src="web/fer2013.js"></script>    </head>    <body>        <canvas id="output"></canvas>        <img id="image" style="            visibility: hidden;            width: auto;            height: auto;            "/>        <h1 id="status">Loading...</h1>        <script>        function setText( text ) {            document.getElementById( "status" ).innerText = text;        }        async function setImage( url ) {            return new Promise( res => {                let image = document.getElementById( "image" );                image.src = url;                image.onload = () => {                    res();                };            });        }        function shuffleArray( array ) {            for( let i = array.length - 1; i > 0; i-- ) {                const j = Math.floor( Math.random() * ( i + 1 ) );                [ array[ i ], array[ j ] ] = [ array[ j ], array[ i ] ];            }        }        function drawLine( ctx, x1, y1, x2, y2, scale = 1 ) {            ctx.beginPath();            ctx.moveTo( x1 * scale, y1 * scale );            ctx.lineTo( x2 * scale, y2 * scale );            ctx.stroke();        }        function drawTriangle( ctx, x1, y1, x2, y2, x3, y3, scale = 1 ) {            ctx.beginPath();            ctx.moveTo( x1 * scale, y1 * scale );            ctx.lineTo( x2 * scale, y2 * scale );            ctx.lineTo( x3 * scale, y3 * scale );            ctx.lineTo( x1 * scale, y1 * scale );            ctx.stroke();        }        function wait( ms ) {            return new Promise( res => setTimeout( res, ms ) );        }        const OUTPUT_SIZE = 500;        const emotions = [ "angry", "disgust", "fear", "happy", "neutral", "sad", "surprise" ];        let ferData = [];        let setIndex = 0;        let emotionModel = null;        let output = null;        let model = null;        async function predictEmotion( points ) {            let result = tf.tidy( () => {                const xs = tf.stack( [ tf.tensor1d( points ) ] );                return emotionModel.predict( xs );            });            let prediction = await result.data();            result.dispose();            // Get the index of the maximum value            let id = prediction.indexOf( Math.max( ...prediction ) );            return emotions[ id ];        }        async function trackFace() {            // Set to the next training image            await setImage( ferData[ setIndex ].file );            const image = document.getElementById( "image" );            const faces = await model.estimateFaces( {                input: image,                returnTensors: false,                flipHorizontal: false,            });            output.drawImage(                image,                0, 0, image.width, image.height,                0, 0, OUTPUT_SIZE, OUTPUT_SIZE            );            const scale = OUTPUT_SIZE / image.width;            let points = null;            faces.forEach( face => {                // Draw the bounding box                const x1 = face.boundingBox.topLeft[ 0 ];                const y1 = face.boundingBox.topLeft[ 1 ];                const x2 = face.boundingBox.bottomRight[ 0 ];                const y2 = face.boundingBox.bottomRight[ 1 ];                const bWidth = x2 - x1;                const bHeight = y2 - y1;                drawLine( output, x1, y1, x2, y1, scale );                drawLine( output, x2, y1, x2, y2, scale );                drawLine( output, x1, y2, x2, y2, scale );                drawLine( output, x1, y1, x1, y2, scale );                // Add just the nose, cheeks, eyes, eyebrows & mouth                const features = [                    "noseTip",                    "leftCheek",                    "rightCheek",                    "leftEyeLower1", "leftEyeUpper1",                    "rightEyeLower1", "rightEyeUpper1",                    "leftEyebrowLower", //"leftEyebrowUpper",                    "rightEyebrowLower", //"rightEyebrowUpper",                    "lipsLowerInner", //"lipsLowerOuter",                    "lipsUpperInner", //"lipsUpperOuter",                ];                points = [];                features.forEach( feature => {                    face.annotations[ feature ].forEach( x => {                        points.push( ( x[ 0 ] - x1 ) / bWidth );                        points.push( ( x[ 1 ] - y1 ) / bHeight );                    });                });            });            if( points ) {                let emotion = await predictEmotion( points );                setText( `${setIndex + 1}. Expected: ${ferData[ setIndex ].emotion} vs. ${emotion}` );            }            else {                setText( "No Face" );            }            setIndex++;            await wait( 2000 );            requestAnimationFrame( trackFace );        }        (async () => {            // Get FER-2013 data from the local web server            // https://www.kaggle.com/msambare/fer2013            // The data can be downloaded from Kaggle and placed inside the "web/fer2013" folder            // Get the lowest number of samples out of all emotion categories            const minSamples = Math.min( ...Object.keys( fer2013 ).map( em => fer2013[ em ].length ) );            Object.keys( fer2013 ).forEach( em => {                shuffleArray( fer2013[ em ] );                for( let i = 0; i < minSamples; i++ ) {                    ferData.push({                        emotion: em,                        file: fer2013[ em ][ i ]                    });                }            });            shuffleArray( ferData );            let canvas = document.getElementById( "output" );            canvas.width = OUTPUT_SIZE;            canvas.height = OUTPUT_SIZE;            output = canvas.getContext( "2d" );            output.translate( canvas.width, 0 );            output.scale( -1, 1 ); // Mirror cam            output.fillStyle = "#fdffb6";            output.strokeStyle = "#fdffb6";            output.lineWidth = 2;            // Load Face Landmarks Detection            model = await faceLandmarksDetection.load(                faceLandmarksDetection.SupportedPackages.mediapipeFacemesh            );            // Load Emotion Detection            emotionModel = await tf.loadLayersModel( 'web/model/facemo.json' );            setText( "Loaded!" );            trackFace();        })();        </script>    </body></html>

Что дальше? Позволит ли это определять наши эмоции на лице?

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

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

Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодомHABR, который даст еще +10% скидки на обучение.

Подробнее..

Перевод Обнаружение эмоций на лице в реальном времени с помощью веб-камеры в браузере с использованием TensorFlow.js. Часть 3

03.03.2021 22:15:39 | Автор: admin

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


Вы можете загрузить демоверсию этого проекта. Для обеспечения необходимой производительности может потребоваться включить в веб-браузере поддержку интерфейса WebGL. Вы также можете загрузить код и файлы для этой серии. Предполагается, что вы знакомы с JavaScript и HTML и имеете хотя бы базовое представление о нейронных сетях.

Добавление обнаружения эмоций на лице

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

Давайте загрузим и применим нашу предварительно обученную модель выражений на лице. Начала мы определим некоторые глобальные переменные для обнаружения эмоций, как мы делали раньше:

const emotions = [ "angry", "disgust", "fear", "happy", "neutral", "sad", "surprise" ];let emotionModel = null;

Затем мы можем загрузить модель обнаружения эмоций внутри блока async:

(async () => {    ...    // Load Face Landmarks Detection    model = await faceLandmarksDetection.load(        faceLandmarksDetection.SupportedPackages.mediapipeFacemesh    );    // Load Emotion Detection    emotionModel = await tf.loadLayersModel( 'web/model/facemo.json' );    ...})();

А для модельного прогнозирования по ключевым точкам лица мы можем добавить служебную функцию:

async function predictEmotion( points ) {    let result = tf.tidy( () => {        const xs = tf.stack( [ tf.tensor1d( points ) ] );        return emotionModel.predict( xs );    });    let prediction = await result.data();    result.dispose();    // Get the index of the maximum value    let id = prediction.indexOf( Math.max( ...prediction ) );    return emotions[ id ];}

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

async function trackFace() {    ...    let points = null;    faces.forEach( face => {        ...        // Add just the nose, cheeks, eyes, eyebrows & mouth        const features = [            "noseTip",            "leftCheek",            "rightCheek",            "leftEyeLower1", "leftEyeUpper1",            "rightEyeLower1", "rightEyeUpper1",            "leftEyebrowLower", //"leftEyebrowUpper",            "rightEyebrowLower", //"rightEyebrowUpper",            "lipsLowerInner", //"lipsLowerOuter",            "lipsUpperInner", //"lipsUpperOuter",        ];        points = [];        features.forEach( feature => {            face.annotations[ feature ].forEach( x => {                points.push( ( x[ 0 ] - x1 ) / bWidth );                points.push( ( x[ 1 ] - y1 ) / bHeight );            });        });    });    if( points ) {        let emotion = await predictEmotion( points );        setText( `Detected: ${emotion}` );    }    else {        setText( "No Face" );    }    requestAnimationFrame( trackFace );}

Это всё, что нужно для достижения нужной цели. Теперь, когда вы открываете веб-страницу, она должна обнаружить ваше лицо и распознать эмоции. Экспериментируйте и получайте удовольствие!

Вот полный код, нужный для завершения этого проекта
<html>    <head>        <title>Real-Time Facial Emotion Detection</title>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.4.0/dist/tf.min.js"></script>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow-models/face-landmarks-detection@0.0.1/dist/face-landmarks-detection.js"></script>    </head>    <body>        <canvas id="output"></canvas>        <video id="webcam" playsinline style="            visibility: hidden;            width: auto;            height: auto;            ">        </video>        <h1 id="status">Loading...</h1>        <script>        function setText( text ) {            document.getElementById( "status" ).innerText = text;        }        function drawLine( ctx, x1, y1, x2, y2 ) {            ctx.beginPath();            ctx.moveTo( x1, y1 );            ctx.lineTo( x2, y2 );            ctx.stroke();        }        async function setupWebcam() {            return new Promise( ( resolve, reject ) => {                const webcamElement = document.getElementById( "webcam" );                const navigatorAny = navigator;                navigator.getUserMedia = navigator.getUserMedia ||                navigatorAny.webkitGetUserMedia || navigatorAny.mozGetUserMedia ||                navigatorAny.msGetUserMedia;                if( navigator.getUserMedia ) {                    navigator.getUserMedia( { video: true },                        stream => {                            webcamElement.srcObject = stream;                            webcamElement.addEventListener( "loadeddata", resolve, false );                        },                    error => reject());                }                else {                    reject();                }            });        }        const emotions = [ "angry", "disgust", "fear", "happy", "neutral", "sad", "surprise" ];        let emotionModel = null;        let output = null;        let model = null;        async function predictEmotion( points ) {            let result = tf.tidy( () => {                const xs = tf.stack( [ tf.tensor1d( points ) ] );                return emotionModel.predict( xs );            });            let prediction = await result.data();            result.dispose();            // Get the index of the maximum value            let id = prediction.indexOf( Math.max( ...prediction ) );            return emotions[ id ];        }        async function trackFace() {            const video = document.querySelector( "video" );            const faces = await model.estimateFaces( {                input: video,                returnTensors: false,                flipHorizontal: false,            });            output.drawImage(                video,                0, 0, video.width, video.height,                0, 0, video.width, video.height            );            let points = null;            faces.forEach( face => {                // Draw the bounding box                const x1 = face.boundingBox.topLeft[ 0 ];                const y1 = face.boundingBox.topLeft[ 1 ];                const x2 = face.boundingBox.bottomRight[ 0 ];                const y2 = face.boundingBox.bottomRight[ 1 ];                const bWidth = x2 - x1;                const bHeight = y2 - y1;                drawLine( output, x1, y1, x2, y1 );                drawLine( output, x2, y1, x2, y2 );                drawLine( output, x1, y2, x2, y2 );                drawLine( output, x1, y1, x1, y2 );                // Add just the nose, cheeks, eyes, eyebrows & mouth                const features = [                    "noseTip",                    "leftCheek",                    "rightCheek",                    "leftEyeLower1", "leftEyeUpper1",                    "rightEyeLower1", "rightEyeUpper1",                    "leftEyebrowLower", //"leftEyebrowUpper",                    "rightEyebrowLower", //"rightEyebrowUpper",                    "lipsLowerInner", //"lipsLowerOuter",                    "lipsUpperInner", //"lipsUpperOuter",                ];                points = [];                features.forEach( feature => {                    face.annotations[ feature ].forEach( x => {                        points.push( ( x[ 0 ] - x1 ) / bWidth );                        points.push( ( x[ 1 ] - y1 ) / bHeight );                    });                });            });            if( points ) {                let emotion = await predictEmotion( points );                setText( `Detected: ${emotion}` );            }            else {                setText( "No Face" );            }            requestAnimationFrame( trackFace );        }        (async () => {            await setupWebcam();            const video = document.getElementById( "webcam" );            video.play();            let videoWidth = video.videoWidth;            let videoHeight = video.videoHeight;            video.width = videoWidth;            video.height = videoHeight;            let canvas = document.getElementById( "output" );            canvas.width = video.width;            canvas.height = video.height;            output = canvas.getContext( "2d" );            output.translate( canvas.width, 0 );            output.scale( -1, 1 ); // Mirror cam            output.fillStyle = "#fdffb6";            output.strokeStyle = "#fdffb6";            output.lineWidth = 2;            // Load Face Landmarks Detection            model = await faceLandmarksDetection.load(                faceLandmarksDetection.SupportedPackages.mediapipeFacemesh            );            // Load Emotion Detection            emotionModel = await tf.loadLayersModel( 'web/model/facemo.json' );            setText( "Loaded!" );            trackFace();        })();        </script>    </body></html>

Что дальше? Когда мы сможем носить виртуальные очки?

Взяв код из первых двух статей этой серии, мы смогли создать детектор эмоций на лице в реальном времени, используя лишь немного кода на JavaScript. Только представьте, что ещё можно сделать с помощью библиотеки TensorFlow.js! В следующей статье мы вернёмся к нашей цели создать фильтр для лица в стиле Snapchat, используя то, что мы уже узнали об отслеживании лиц и добавлении 3D-визуализации посредством ThreeJS. Оставайтесь с нами! До встречи завтра, в это же время!

Отслеживание лиц в реальном времени в браузере с использованием TensorFlow.js. Часть 2

Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодомHABR, который даст еще +10% скидки на обучение.

Подробнее..

Перевод Отслеживание лиц в реальном времени в браузере с использованием TensorFlow.js. Часть 4

04.03.2021 20:15:20 | Автор: admin

В 4 части (вы же прочли первую, вторую и третью, да?) мы возвращаемся к нашей цели создание фильтра для лица в стиле Snapchat, используя то, что мы уже узнали об отслеживании лиц и добавлении 3D-визуализации посредством ThreeJS. В этой статье мы собираемся использовать ключевые точки лица для виртуальной визуализации 3D-модели поверх видео с веб-камеры, чтобы немного развлечься с дополненной реальностью.


Вы можете загрузить демоверсию этого проекта. Для обеспечения необходимой производительности может потребоваться включить в веб-браузере поддержку интерфейса WebGL. Вы также можете загрузить код и файлы для этой серии. Предполагается, что вы знакомы с JavaScript и HTML и имеете хотя бы базовое представление о нейронных сетях.

Добавление 3D-графики с помощью ThreeJS

Этот проект будет основан на коде проекта отслеживания лиц, который мы создали в начале этой серии. Мы добавим наложение 3D-сцены на исходное полотно.

ThreeJS позволяет относительно легко работать с 3D-графикой, поэтому мы собираемся с помощью этой библиотеки визуализировать виртуальные очки поверх наших лиц.

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

<script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/three@0.123.0/build/three.min.js"></script><script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/three@0.123.0/examples/js/loaders/GLTFLoader.js"></script>

Чтобы упростить задачу и не беспокоиться о том, как поместить текстуру веб-камеры на сцену, мы можем наложить дополнительное прозрачное полотно (canvas) и нарисовать виртуальные очки на нём. Мы используем CSS-код, приведённый ниже над тегом body, поместив выходное полотно (output) в контейнер и добавив полотно наложения (overlay).

<style>    .canvas-container {        position: relative;        width: auto;        height: auto;    }    .canvas-container canvas {        position: absolute;        left: 0;        width: auto;        height: auto;    }</style><body>    <div class="canvas-container">        <canvas id="output"></canvas>        <canvas id="overlay"></canvas>    </div>    ...</body>

Для 3D-сцены требуется несколько переменных, и мы можем добавить служебную функцию загрузки 3D-модели для файлов GLTF:

<style>    .canvas-container {        position: relative;        width: auto;        height: auto;    }    .canvas-container canvas {        position: absolute;        left: 0;        width: auto;        height: auto;    }</style><body>    <div class="canvas-container">        <canvas id="output"></canvas>        <canvas id="overlay"></canvas>    </div>    ...</body>

Теперь мы можем инициализировать все компоненты нашего блока async, начиная с размера полотна наложения, как это было сделано с выходным полотном:

(async () => {    ...    let canvas = document.getElementById( "output" );    canvas.width = video.width;    canvas.height = video.height;    let overlay = document.getElementById( "overlay" );    overlay.width = video.width;    overlay.height = video.height;    ...})();

Также необходимо задать переменные renderer, scene и camera. Даже если вы не знакомы с трёхмерной перспективой и математикой камеры, вам не надо волноваться. Этот код просто располагает камеру сцены так, чтобы ширина и высота видео веб-камеры соответствовали координатам трёхмерного пространства:

(async () => {    ...    // Load Face Landmarks Detection    model = await faceLandmarksDetection.load(        faceLandmarksDetection.SupportedPackages.mediapipeFacemesh    );    renderer = new THREE.WebGLRenderer({        canvas: document.getElementById( "overlay" ),        alpha: true    });    camera = new THREE.PerspectiveCamera( 45, 1, 0.1, 2000 );    camera.position.x = videoWidth / 2;    camera.position.y = -videoHeight / 2;    camera.position.z = -( videoHeight / 2 ) / Math.tan( 45 / 2 ); // distance to z should be tan( fov / 2 )    scene = new THREE.Scene();    scene.add( new THREE.AmbientLight( 0xcccccc, 0.4 ) );    camera.add( new THREE.PointLight( 0xffffff, 0.8 ) );    scene.add( camera );    camera.lookAt( { x: videoWidth / 2, y: -videoHeight / 2, z: 0, isVector3: true } );    ...})();

Нам нужно добавить в функцию trackFace всего лишь одну строку кода для визуализации сцены поверх выходных данных отслеживания лица:

async function trackFace() {    const video = document.querySelector( "video" );    output.drawImage(        video,        0, 0, video.width, video.height,        0, 0, video.width, video.height    );    renderer.render( scene, camera );    const faces = await model.estimateFaces( {        input: video,        returnTensors: false,        flipHorizontal: false,    });    ...}

Последний этап этого ребуса перед отображением виртуальных объектов на нашем лице загрузка 3D-модели виртуальных очков. Мы нашли пару очков в форме сердца от Maximkuzlin на SketchFab. При желании вы можете загрузить и использовать другой объект.

Здесь показано, как загрузить объект и добавить его в сцену до вызова функции trackFace:

Размещение виртуальных очков на отслеживаемом лице

Теперь начинается самое интересное наденем наши виртуальные очки.

Помеченные аннотации, предоставляемые моделью отслеживания лиц TensorFlow, включают массив координат MidwayBetweenEyes, в котором координаты X и Y соответствуют экрану, а координата Z добавляет экрану глубины. Это делает размещение очков на наших глазах довольно простой задачей.

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

glasses.position.x = face.annotations.midwayBetweenEyes[ 0 ][ 0 ];glasses.position.y = -face.annotations.midwayBetweenEyes[ 0 ][ 1 ];glasses.position.z = -camera.position.z + face.annotations.midwayBetweenEyes[ 0 ][ 2 ];

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

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

glasses.up.x = face.annotations.midwayBetweenEyes[ 0 ][ 0 ] - face.annotations.noseBottom[ 0 ][ 0 ];glasses.up.y = -( face.annotations.midwayBetweenEyes[ 0 ][ 1 ] - face.annotations.noseBottom[ 0 ][ 1 ] );glasses.up.z = face.annotations.midwayBetweenEyes[ 0 ][ 2 ] - face.annotations.noseBottom[ 0 ][ 2 ];const length = Math.sqrt( glasses.up.x ** 2 + glasses.up.y ** 2 + glasses.up.z ** 2 );glasses.up.x /= length;glasses.up.y /= length;glasses.up.z /= length;

Чтобы получить относительный размер головы, можно вычислить расстояние между глазами:

const eyeDist = Math.sqrt(    ( face.annotations.leftEyeUpper1[ 3 ][ 0 ] - face.annotations.rightEyeUpper1[ 3 ][ 0 ] ) ** 2 +    ( face.annotations.leftEyeUpper1[ 3 ][ 1 ] - face.annotations.rightEyeUpper1[ 3 ][ 1 ] ) ** 2 +    ( face.annotations.leftEyeUpper1[ 3 ][ 2 ] - face.annotations.rightEyeUpper1[ 3 ][ 2 ] ) ** 2);

Наконец, мы масштабируем очки на основе значения eyeDist и ориентируем очки по оси Z, используя угол между вектором вверх и осью Y. И вуаля!

Выполните свой код и проверьте результат.

Прежде чем перейти к следующей части этой серии, давайте посмотрим на полный код, собранный вместе:

Простыня с кодом
<html>    <head>        <title>Creating a Snapchat-Style Virtual Glasses Face Filter</title>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.4.0/dist/tf.min.js"></script>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow-models/face-landmarks-detection@0.0.1/dist/face-landmarks-detection.js"></script>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/three@0.123.0/build/three.min.js"></script>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/three@0.123.0/examples/js/loaders/GLTFLoader.js"></script>    </head>    <style>        .canvas-container {            position: relative;            width: auto;            height: auto;        }        .canvas-container canvas {            position: absolute;            left: 0;            width: auto;            height: auto;        }    </style>    <body>        <div class="canvas-container">            <canvas id="output"></canvas>            <canvas id="overlay"></canvas>        </div>        <video id="webcam" playsinline style="            visibility: hidden;            width: auto;            height: auto;            ">        </video>        <h1 id="status">Loading...</h1>        <script>        function setText( text ) {            document.getElementById( "status" ).innerText = text;        }        function drawLine( ctx, x1, y1, x2, y2 ) {            ctx.beginPath();            ctx.moveTo( x1, y1 );            ctx.lineTo( x2, y2 );            ctx.stroke();        }        async function setupWebcam() {            return new Promise( ( resolve, reject ) => {                const webcamElement = document.getElementById( "webcam" );                const navigatorAny = navigator;                navigator.getUserMedia = navigator.getUserMedia ||                navigatorAny.webkitGetUserMedia || navigatorAny.mozGetUserMedia ||                navigatorAny.msGetUserMedia;                if( navigator.getUserMedia ) {                    navigator.getUserMedia( { video: true },                        stream => {                            webcamElement.srcObject = stream;                            webcamElement.addEventListener( "loadeddata", resolve, false );                        },                    error => reject());                }                else {                    reject();                }            });        }        let output = null;        let model = null;        let renderer = null;        let scene = null;        let camera = null;        let glasses = null;        function loadModel( file ) {            return new Promise( ( res, rej ) => {                const loader = new THREE.GLTFLoader();                loader.load( file, function ( gltf ) {                    res( gltf.scene );                }, undefined, function ( error ) {                    rej( error );                } );            });        }        async function trackFace() {            const video = document.querySelector( "video" );            output.drawImage(                video,                0, 0, video.width, video.height,                0, 0, video.width, video.height            );            renderer.render( scene, camera );            const faces = await model.estimateFaces( {                input: video,                returnTensors: false,                flipHorizontal: false,            });            faces.forEach( face => {                // Draw the bounding box                const x1 = face.boundingBox.topLeft[ 0 ];                const y1 = face.boundingBox.topLeft[ 1 ];                const x2 = face.boundingBox.bottomRight[ 0 ];                const y2 = face.boundingBox.bottomRight[ 1 ];                const bWidth = x2 - x1;                const bHeight = y2 - y1;                drawLine( output, x1, y1, x2, y1 );                drawLine( output, x2, y1, x2, y2 );                drawLine( output, x1, y2, x2, y2 );                drawLine( output, x1, y1, x1, y2 );                glasses.position.x = face.annotations.midwayBetweenEyes[ 0 ][ 0 ];                glasses.position.y = -face.annotations.midwayBetweenEyes[ 0 ][ 1 ];                glasses.position.z = -camera.position.z + face.annotations.midwayBetweenEyes[ 0 ][ 2 ];                // Calculate an Up-Vector using the eyes position and the bottom of the nose                glasses.up.x = face.annotations.midwayBetweenEyes[ 0 ][ 0 ] - face.annotations.noseBottom[ 0 ][ 0 ];                glasses.up.y = -( face.annotations.midwayBetweenEyes[ 0 ][ 1 ] - face.annotations.noseBottom[ 0 ][ 1 ] );                glasses.up.z = face.annotations.midwayBetweenEyes[ 0 ][ 2 ] - face.annotations.noseBottom[ 0 ][ 2 ];                const length = Math.sqrt( glasses.up.x ** 2 + glasses.up.y ** 2 + glasses.up.z ** 2 );                glasses.up.x /= length;                glasses.up.y /= length;                glasses.up.z /= length;                // Scale to the size of the head                const eyeDist = Math.sqrt(                    ( face.annotations.leftEyeUpper1[ 3 ][ 0 ] - face.annotations.rightEyeUpper1[ 3 ][ 0 ] ) ** 2 +                    ( face.annotations.leftEyeUpper1[ 3 ][ 1 ] - face.annotations.rightEyeUpper1[ 3 ][ 1 ] ) ** 2 +                    ( face.annotations.leftEyeUpper1[ 3 ][ 2 ] - face.annotations.rightEyeUpper1[ 3 ][ 2 ] ) ** 2                );                glasses.scale.x = eyeDist / 6;                glasses.scale.y = eyeDist / 6;                glasses.scale.z = eyeDist / 6;                glasses.rotation.y = Math.PI;                glasses.rotation.z = Math.PI / 2 - Math.acos( glasses.up.x );            });            requestAnimationFrame( trackFace );        }        (async () => {            await setupWebcam();            const video = document.getElementById( "webcam" );            video.play();            let videoWidth = video.videoWidth;            let videoHeight = video.videoHeight;            video.width = videoWidth;            video.height = videoHeight;            let canvas = document.getElementById( "output" );            canvas.width = video.width;            canvas.height = video.height;            let overlay = document.getElementById( "overlay" );            overlay.width = video.width;            overlay.height = video.height;            output = canvas.getContext( "2d" );            output.translate( canvas.width, 0 );            output.scale( -1, 1 ); // Mirror cam            output.fillStyle = "#fdffb6";            output.strokeStyle = "#fdffb6";            output.lineWidth = 2;            // Load Face Landmarks Detection            model = await faceLandmarksDetection.load(                faceLandmarksDetection.SupportedPackages.mediapipeFacemesh            );            renderer = new THREE.WebGLRenderer({                canvas: document.getElementById( "overlay" ),                alpha: true            });            camera = new THREE.PerspectiveCamera( 45, 1, 0.1, 2000 );            camera.position.x = videoWidth / 2;            camera.position.y = -videoHeight / 2;            camera.position.z = -( videoHeight / 2 ) / Math.tan( 45 / 2 ); // distance to z should be tan( fov / 2 )            scene = new THREE.Scene();            scene.add( new THREE.AmbientLight( 0xcccccc, 0.4 ) );            camera.add( new THREE.PointLight( 0xffffff, 0.8 ) );            scene.add( camera );            camera.lookAt( { x: videoWidth / 2, y: -videoHeight / 2, z: 0, isVector3: true } );            // Glasses from https://sketchfab.com/3d-models/heart-glasses-ef812c7e7dc14f6b8783ccb516b3495c            glasses = await loadModel( "web/3d/heart_glasses.gltf" );            scene.add( glasses );            setText( "Loaded!" );            trackFace();        })();        </script>    </body></html>

Что дальше? Что если также добавить обнаружение эмоций на лице?

Поверите ли, что всё это возможно на одной веб-странице? Добавив 3D-объекты к функции отслеживания лиц в реальном времени, мы сотворили волшебство с помощью камеры прямо в веб-браузере. Вы можете подумать: Но очки в форме сердца существуют в реальной жизни И это правда! А что, если мы создадим что-то действительно волшебное, например шляпу которая знает, что мы чувствуем?

Давайте в следующей статье создадим волшебную шляпу (как в Хогвартсе!) для обнаружения эмоций и посмотрим, сможем ли мы сделать невозможное возможным, ещё больше используя библиотеку TensorFlow.js! До встречи завтра, в это же время.

Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодомHABR, который даст еще +10% скидки на обучение.

Другие профессии и курсы
Подробнее..

Перевод Отслеживание лиц в реальном времени в браузере с использованием TensorFlow.js. Часть 5

06.03.2021 20:20:06 | Автор: admin

Носить виртуальные аксессуары это весело, но до их ношения в реальной жизни всего один шаг. Мы могли бы легко создать приложение, которое позволяет виртуально примерять шляпы именно такое приложение вы могли бы захотеть создать для веб-сайта электронной коммерции. Но, если мы собираемся это сделать, почему бы при этом не получить немного больше удовольствия? Программное обеспечение замечательно тем, что мы можем воплотить в жизнь своё воображение.

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


Вы можете загрузить демоверсию этого проекта. Для обеспечения необходимой производительности может потребоваться включить в веб-браузере поддержку интерфейса WebGL. Вы также можете загрузить код и файлы для этой серии. Предполагается, что вы знакомы с JavaScript и HTML и имеете хотя бы базовое представление о нейронных сетях.

Создание волшебной шляпы

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

Чтобы создать нашу виртуальную шляпу, мы собираемся добавить графические ресурсы на веб-страницу как скрытые элементы img:

<img id="hat-angry" src="web/hats/angry.png" style="visibility: hidden;" /><img id="hat-disgust" src="web/hats/disgust.png" style="visibility: hidden;" /><img id="hat-fear" src="web/hats/fear.png" style="visibility: hidden;" /><img id="hat-happy" src="web/hats/happy.png" style="visibility: hidden;" /><img id="hat-neutral" src="web/hats/neutral.png" style="visibility: hidden;" /><img id="hat-sad" src="web/hats/sad.png" style="visibility: hidden;" /><img id="hat-surprise" src="web/hats/surprise.png" style="visibility: hidden;" />

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

let currentEmotion = "neutral";let hat = { scale: { x: 0, y: 0 }, position: { x: 0, y: 0 } };

Рисовать шляпу этого размера и в этом положении мы будем с помощью 2D-преобразования полотна в каждом кадре.

async function trackFace() {    ...    output.drawImage(        video,        0, 0, video.width, video.height,        0, 0, video.width, video.height    );    let hatImage = document.getElementById( `hat-${currentEmotion}` );    output.save();    output.translate( -hatImage.width / 2, -hatImage.height / 2 );    output.translate( hat.position.x, hat.position.y );    output.drawImage(        hatImage,        0, 0, hatImage.width, hatImage.height,        0, 0, hatImage.width * hat.scale, hatImage.height * hat.scale    );    output.restore();    ...}

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

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

const eyeDist = Math.sqrt(    ( face.annotations.leftEyeUpper1[ 3 ][ 0 ] - face.annotations.rightEyeUpper1[ 3 ][ 0 ] ) ** 2 +    ( face.annotations.leftEyeUpper1[ 3 ][ 1 ] - face.annotations.rightEyeUpper1[ 3 ][ 1 ] ) ** 2 +    ( face.annotations.leftEyeUpper1[ 3 ][ 2 ] - face.annotations.rightEyeUpper1[ 3 ][ 2 ] ) ** 2);const faceScale = eyeDist / 80;let upX = face.annotations.midwayBetweenEyes[ 0 ][ 0 ] - face.annotations.noseBottom[ 0 ][ 0 ];let upY = face.annotations.midwayBetweenEyes[ 0 ][ 1 ] - face.annotations.noseBottom[ 0 ][ 1 ];const length = Math.sqrt( upX ** 2 + upY ** 2 );upX /= length;upY /= length;hat = {    scale: faceScale,    position: {        x: face.annotations.midwayBetweenEyes[ 0 ][ 0 ] + upX * 100 * faceScale,        y: face.annotations.midwayBetweenEyes[ 0 ][ 1 ] + upY * 100 * faceScale,    }};

После сохранения названия спрогнозированной эмоции в currentEmotion отображается соответствующее изображение шляпы, и мы готовы её примерить!

if( points ) {    let emotion = await predictEmotion( points );    setText( `Detected: ${emotion}` );    currentEmotion = emotion;}else {    setText( "No Face" );}
Вот полный код этого проекта
<html>    <head>        <title>Building a Magical Emotion Detection Hat</title>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.4.0/dist/tf.min.js"></script>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow-models/face-landmarks-detection@0.0.1/dist/face-landmarks-detection.js"></script>    </head>    <body>        <canvas id="output"></canvas>        <video id="webcam" playsinline style="            visibility: hidden;            width: auto;            height: auto;            ">        </video>        <h1 id="status">Loading...</h1>        <img id="hat-angry" src="web/hats/angry.png" style="visibility: hidden;" />        <img id="hat-disgust" src="web/hats/disgust.png" style="visibility: hidden;" />        <img id="hat-fear" src="web/hats/fear.png" style="visibility: hidden;" />        <img id="hat-happy" src="web/hats/happy.png" style="visibility: hidden;" />        <img id="hat-neutral" src="web/hats/neutral.png" style="visibility: hidden;" />        <img id="hat-sad" src="web/hats/sad.png" style="visibility: hidden;" />        <img id="hat-surprise" src="web/hats/surprise.png" style="visibility: hidden;" />        <script>        function setText( text ) {            document.getElementById( "status" ).innerText = text;        }        function drawLine( ctx, x1, y1, x2, y2 ) {            ctx.beginPath();            ctx.moveTo( x1, y1 );            ctx.lineTo( x2, y2 );            ctx.stroke();        }        async function setupWebcam() {            return new Promise( ( resolve, reject ) => {                const webcamElement = document.getElementById( "webcam" );                const navigatorAny = navigator;                navigator.getUserMedia = navigator.getUserMedia ||                navigatorAny.webkitGetUserMedia || navigatorAny.mozGetUserMedia ||                navigatorAny.msGetUserMedia;                if( navigator.getUserMedia ) {                    navigator.getUserMedia( { video: true },                        stream => {                            webcamElement.srcObject = stream;                            webcamElement.addEventListener( "loadeddata", resolve, false );                        },                    error => reject());                }                else {                    reject();                }            });        }        const emotions = [ "angry", "disgust", "fear", "happy", "neutral", "sad", "surprise" ];        let emotionModel = null;        let output = null;        let model = null;        let currentEmotion = "neutral";        let hat = { scale: { x: 0, y: 0 }, position: { x: 0, y: 0 } };        async function predictEmotion( points ) {            let result = tf.tidy( () => {                const xs = tf.stack( [ tf.tensor1d( points ) ] );                return emotionModel.predict( xs );            });            let prediction = await result.data();            result.dispose();            // Get the index of the maximum value            let id = prediction.indexOf( Math.max( ...prediction ) );            return emotions[ id ];        }        async function trackFace() {            const video = document.querySelector( "video" );            const faces = await model.estimateFaces( {                input: video,                returnTensors: false,                flipHorizontal: false,            });            output.drawImage(                video,                0, 0, video.width, video.height,                0, 0, video.width, video.height            );            let hatImage = document.getElementById( `hat-${currentEmotion}` );            output.save();            output.translate( -hatImage.width / 2, -hatImage.height / 2 );            output.translate( hat.position.x, hat.position.y );            output.drawImage(                hatImage,                0, 0, hatImage.width, hatImage.height,                0, 0, hatImage.width * hat.scale, hatImage.height * hat.scale            );            output.restore();            let points = null;            faces.forEach( face => {                const x1 = face.boundingBox.topLeft[ 0 ];                const y1 = face.boundingBox.topLeft[ 1 ];                const x2 = face.boundingBox.bottomRight[ 0 ];                const y2 = face.boundingBox.bottomRight[ 1 ];                const bWidth = x2 - x1;                const bHeight = y2 - y1;                // Add just the nose, cheeks, eyes, eyebrows & mouth                const features = [                    "noseTip",                    "leftCheek",                    "rightCheek",                    "leftEyeLower1", "leftEyeUpper1",                    "rightEyeLower1", "rightEyeUpper1",                    "leftEyebrowLower", //"leftEyebrowUpper",                    "rightEyebrowLower", //"rightEyebrowUpper",                    "lipsLowerInner", //"lipsLowerOuter",                    "lipsUpperInner", //"lipsUpperOuter",                ];                points = [];                features.forEach( feature => {                    face.annotations[ feature ].forEach( x => {                        points.push( ( x[ 0 ] - x1 ) / bWidth );                        points.push( ( x[ 1 ] - y1 ) / bHeight );                    });                });                const eyeDist = Math.sqrt(                    ( face.annotations.leftEyeUpper1[ 3 ][ 0 ] - face.annotations.rightEyeUpper1[ 3 ][ 0 ] ) ** 2 +                    ( face.annotations.leftEyeUpper1[ 3 ][ 1 ] - face.annotations.rightEyeUpper1[ 3 ][ 1 ] ) ** 2 +                    ( face.annotations.leftEyeUpper1[ 3 ][ 2 ] - face.annotations.rightEyeUpper1[ 3 ][ 2 ] ) ** 2                );                const faceScale = eyeDist / 80;                let upX = face.annotations.midwayBetweenEyes[ 0 ][ 0 ] - face.annotations.noseBottom[ 0 ][ 0 ];                let upY = face.annotations.midwayBetweenEyes[ 0 ][ 1 ] - face.annotations.noseBottom[ 0 ][ 1 ];                const length = Math.sqrt( upX ** 2 + upY ** 2 );                upX /= length;                upY /= length;                hat = {                    scale: faceScale,                    position: {                        x: face.annotations.midwayBetweenEyes[ 0 ][ 0 ] + upX * 100 * faceScale,                        y: face.annotations.midwayBetweenEyes[ 0 ][ 1 ] + upY * 100 * faceScale,                    }                };            });            if( points ) {                let emotion = await predictEmotion( points );                setText( `Detected: ${emotion}` );                currentEmotion = emotion;            }            else {                setText( "No Face" );            }                        requestAnimationFrame( trackFace );        }        (async () => {            await setupWebcam();            const video = document.getElementById( "webcam" );            video.play();            let videoWidth = video.videoWidth;            let videoHeight = video.videoHeight;            video.width = videoWidth;            video.height = videoHeight;            let canvas = document.getElementById( "output" );            canvas.width = video.width;            canvas.height = video.height;            output = canvas.getContext( "2d" );            output.translate( canvas.width, 0 );            output.scale( -1, 1 ); // Mirror cam            output.fillStyle = "#fdffb6";            output.strokeStyle = "#fdffb6";            output.lineWidth = 2;            // Load Face Landmarks Detection            model = await faceLandmarksDetection.load(                faceLandmarksDetection.SupportedPackages.mediapipeFacemesh            );            // Load Emotion Detection            emotionModel = await tf.loadLayersModel( 'web/model/facemo.json' );            setText( "Loaded!" );            trackFace();        })();        </script>    </body></html>

Что дальше? Возможен ли контроль по состоянию глаз и рта?

В этом проекте собраны воедино все куски, созданные ранее в этой серии статей в целях развлечения с визуальными образами. А что, если бы можно было реализовать в нём взаимодействие с лицом?

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

Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодомHABR, который даст еще +10% скидки на обучение.

Другие профессии и курсы
Подробнее..

Перевод Отслеживание лиц в реальном времени в браузере с использованием TensorFlow.js. Часть 6

07.03.2021 22:07:07 | Автор: admin
Активация экранной магии вашим лицом в браузереАктивация экранной магии вашим лицом в браузере

Вот и финал этой серии статей (ссылки на предыдущие части в конце этого материала), в которой мы создавали в браузере фильтры в стиле Snapchat, обучая модель ИИ понимать выражения лиц и добились ещё большего, используя библиотеку Tensorflow.js и отслеживание лиц.

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


Вы можете загрузить демоверсию этого проекта. Для обеспечения необходимой производительности может потребоваться включить в веб-браузере поддержку интерфейса WebGL. Вы также можете загрузить код и файлы для этой серии. Предполагается, что вы знакомы с JavaScript и HTML и имеете хотя бы базовое представление о нейронных сетях.

Обнаружение моргания глаз и открывания рта

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

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

Для этого мы можем обратиться к удобному расстоянию между глазами, чтобы аппроксимировать относительную шкалу в функции trackFace:

async function trackFace() {    ...    faces.forEach( face => {        const eyeDist = Math.sqrt(            ( face.annotations.leftEyeUpper1[ 3 ][ 0 ] - face.annotations.rightEyeUpper1[ 3 ][ 0 ] ) ** 2 +            ( face.annotations.leftEyeUpper1[ 3 ][ 1 ] - face.annotations.rightEyeUpper1[ 3 ][ 1 ] ) ** 2 +            ( face.annotations.leftEyeUpper1[ 3 ][ 2 ] - face.annotations.rightEyeUpper1[ 3 ][ 2 ] ) ** 2        );        const faceScale = eyeDist / 80;    });    requestAnimationFrame( trackFace );}

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

Взгляните:

async function trackFace() {    ...    let areEyesClosed = false, isMouthOpen = false;    faces.forEach( face => {        ...        // Check for eyes closed        const leftEyesDist = Math.sqrt(            ( face.annotations.leftEyeLower1[ 4 ][ 0 ] - face.annotations.leftEyeUpper1[ 4 ][ 0 ] ) ** 2 +            ( face.annotations.leftEyeLower1[ 4 ][ 1 ] - face.annotations.leftEyeUpper1[ 4 ][ 1 ] ) ** 2 +            ( face.annotations.leftEyeLower1[ 4 ][ 2 ] - face.annotations.leftEyeUpper1[ 4 ][ 2 ] ) ** 2        );        const rightEyesDist = Math.sqrt(            ( face.annotations.rightEyeLower1[ 4 ][ 0 ] - face.annotations.rightEyeUpper1[ 4 ][ 0 ] ) ** 2 +            ( face.annotations.rightEyeLower1[ 4 ][ 1 ] - face.annotations.rightEyeUpper1[ 4 ][ 1 ] ) ** 2 +            ( face.annotations.rightEyeLower1[ 4 ][ 2 ] - face.annotations.rightEyeUpper1[ 4 ][ 2 ] ) ** 2        );        if( leftEyesDist / faceScale < 23.5 ) {            areEyesClosed = true;        }        if( rightEyesDist / faceScale < 23.5 ) {            areEyesClosed = true;        }        // Check for mouth open        const lipsDist = Math.sqrt(            ( face.annotations.lipsLowerInner[ 5 ][ 0 ] - face.annotations.lipsUpperInner[ 5 ][ 0 ] ) ** 2 +            ( face.annotations.lipsLowerInner[ 5 ][ 1 ] - face.annotations.lipsUpperInner[ 5 ][ 1 ] ) ** 2 +            ( face.annotations.lipsLowerInner[ 5 ][ 2 ] - face.annotations.lipsUpperInner[ 5 ][ 2 ] ) ** 2        );        // Scale to the relative face size        if( lipsDist / faceScale > 20 ) {            isMouthOpen = true;        }    });    setText( `Eyes: ${areEyesClosed} Mouth: ${isMouthOpen}` );    requestAnimationFrame( trackFace );}

Теперь мы готовы к обнаружению некоторых движений на лицах.

Время вечеринки с конфетти

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

Для этого мы будем использовать библиотеку JavaScript с открытым исходным кодом, которая называется Party-JS. Включите её в верхней части своей страницы следующим образом:

<script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/party-js@1.0.0/party.min.js"></script>

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

let didParty = false;

И последнее, но не менее важное: мы можем включать анимацию вечеринки, когда мы моргаем или открываем рот.

async function trackFace() {    ...    if( !didParty && ( areEyesClosed || isMouthOpen ) ) {        party.screen();    }    didParty = areEyesClosed || isMouthOpen;    requestAnimationFrame( trackFace );}

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

Этот проект не закончен без полного кода, на который вы могли бы взглянуть. Поэтому вот он:

Простыня с кодом
<html>    <head>        <title>Tracking Faces in the Browser with TensorFlow.js</title>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.4.0/dist/tf.min.js"></script>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/@tensorflow-models/face-landmarks-detection@0.0.1/dist/face-landmarks-detection.js"></script>        <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/party-js@1.0.0/party.min.js"></script>    </head>    <body>        <canvas id="output"></canvas>        <video id="webcam" playsinline style="            visibility: hidden;            width: auto;            height: auto;            ">        </video>        <h1 id="status">Loading...</h1>        <script>        function setText( text ) {            document.getElementById( "status" ).innerText = text;        }        async function setupWebcam() {            return new Promise( ( resolve, reject ) => {                const webcamElement = document.getElementById( "webcam" );                const navigatorAny = navigator;                navigator.getUserMedia = navigator.getUserMedia ||                navigatorAny.webkitGetUserMedia || navigatorAny.mozGetUserMedia ||                navigatorAny.msGetUserMedia;                if( navigator.getUserMedia ) {                    navigator.getUserMedia( { video: true },                        stream => {                            webcamElement.srcObject = stream;                            webcamElement.addEventListener( "loadeddata", resolve, false );                        },                    error => reject());                }                else {                    reject();                }            });        }        let output = null;        let model = null;        let didParty = false;        async function trackFace() {            const video = document.getElementById( "webcam" );            const faces = await model.estimateFaces( {                input: video,                returnTensors: false,                flipHorizontal: false,            });            output.drawImage(                video,                0, 0, video.width, video.height,                0, 0, video.width, video.height            );            let areEyesClosed = false, isMouthOpen = false;            faces.forEach( face => {                const eyeDist = Math.sqrt(                    ( face.annotations.leftEyeUpper1[ 3 ][ 0 ] - face.annotations.rightEyeUpper1[ 3 ][ 0 ] ) ** 2 +                    ( face.annotations.leftEyeUpper1[ 3 ][ 1 ] - face.annotations.rightEyeUpper1[ 3 ][ 1 ] ) ** 2 +                    ( face.annotations.leftEyeUpper1[ 3 ][ 2 ] - face.annotations.rightEyeUpper1[ 3 ][ 2 ] ) ** 2                );                const faceScale = eyeDist / 80;                // Check for eyes closed                const leftEyesDist = Math.sqrt(                    ( face.annotations.leftEyeLower1[ 4 ][ 0 ] - face.annotations.leftEyeUpper1[ 4 ][ 0 ] ) ** 2 +                    ( face.annotations.leftEyeLower1[ 4 ][ 1 ] - face.annotations.leftEyeUpper1[ 4 ][ 1 ] ) ** 2 +                    ( face.annotations.leftEyeLower1[ 4 ][ 2 ] - face.annotations.leftEyeUpper1[ 4 ][ 2 ] ) ** 2                );                const rightEyesDist = Math.sqrt(                    ( face.annotations.rightEyeLower1[ 4 ][ 0 ] - face.annotations.rightEyeUpper1[ 4 ][ 0 ] ) ** 2 +                    ( face.annotations.rightEyeLower1[ 4 ][ 1 ] - face.annotations.rightEyeUpper1[ 4 ][ 1 ] ) ** 2 +                    ( face.annotations.rightEyeLower1[ 4 ][ 2 ] - face.annotations.rightEyeUpper1[ 4 ][ 2 ] ) ** 2                );                if( leftEyesDist / faceScale < 23.5 ) {                    areEyesClosed = true;                }                if( rightEyesDist / faceScale < 23.5 ) {                    areEyesClosed = true;                }                // Check for mouth open                const lipsDist = Math.sqrt(                    ( face.annotations.lipsLowerInner[ 5 ][ 0 ] - face.annotations.lipsUpperInner[ 5 ][ 0 ] ) ** 2 +                    ( face.annotations.lipsLowerInner[ 5 ][ 1 ] - face.annotations.lipsUpperInner[ 5 ][ 1 ] ) ** 2 +                    ( face.annotations.lipsLowerInner[ 5 ][ 2 ] - face.annotations.lipsUpperInner[ 5 ][ 2 ] ) ** 2                );                // Scale to the relative face size                if( lipsDist / faceScale > 20 ) {                    isMouthOpen = true;                }            });            if( !didParty && ( areEyesClosed || isMouthOpen ) ) {                party.screen();            }            didParty = areEyesClosed || isMouthOpen;            setText( `Eyes: ${areEyesClosed} Mouth: ${isMouthOpen}` );            requestAnimationFrame( trackFace );        }        (async () => {            await setupWebcam();            const video = document.getElementById( "webcam" );            video.play();            let videoWidth = video.videoWidth;            let videoHeight = video.videoHeight;            video.width = videoWidth;            video.height = videoHeight;            let canvas = document.getElementById( "output" );            canvas.width = video.width;            canvas.height = video.height;            output = canvas.getContext( "2d" );            output.translate( canvas.width, 0 );            output.scale( -1, 1 ); // Mirror cam            output.fillStyle = "#fdffb6";            output.strokeStyle = "#fdffb6";            output.lineWidth = 2;            // Load Face Landmarks Detection            model = await faceLandmarksDetection.load(                faceLandmarksDetection.SupportedPackages.mediapipeFacemesh            );            setText( "Loaded!" );            trackFace();        })();        </script>    </body></html>

Что дальше?

Собственно, на этом пока всё. В этой серии статей мы научились применять ИИ к лицам, чтобы отслеживать их в режиме реального времени, а также определять эмоции на лице и движения рта и глаз. Мы даже создали с нуля собственную игру с дополненной реальностью и виртуальными очками, и всё это работает в веб-браузере.

Хотя мы выбрали для применения забавные примеры, для этой технологии также существует множество приложений в бизнесе. Представьте продавца очков, который хочет позволить посетителям своего веб-сайта выбирать очки, примеряя их. Нетрудно представить, как вы будете использовать знания, приобретённые в этой серии статей, для создания нужных функциональных возможностей. Надеюсь, теперь у вас есть инструменты для создания более полезных решений с использованием ИИ и TensorFlow.js.

Попробуйте реализовать конфетти в проекте виртуальных очков. Проверьте, сможете ли вы применить обнаружение эмоций к фотоальбому.

И если эти серии статей вдохновят вас на создание ещё более крутых проектов, поделитесь ими в комментариях! Мы будем рады узнать о ваших проектах.

Удачи и удовольствия от программирования!

Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодомHABR, который даст еще +10% скидки на обучение.

Другие профессии и курсы
Подробнее..

Перевод Удвойте скорость написания кода на React с помощью этих простых трюков

12.03.2021 20:06:18 | Автор: admin

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

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

С повторно используемыми компонентами и хуками легко создать что-то потрясающее. Это похоже на сборку частей Lego.

Cory House. Проектирование повторно используемых компонентов React

Рефакторинг, рефакторинг и рефакторинг это нормально; вам придётся менять свой код много-много раз; это естественный процесс обучения.

Tomas Eglinskas. Самые важные уроки, которые я извлёк за год работы с React

  • В этой статье будет много кода на React. Не бойтесь кода. Не торопитесь, прочтите и поймите его.

  • Это большая статья. Содержание всеобъемлющее. Не стесняйтесь сохранить эту статью в своих вкладках и прочитать её несколько раз.

  • Даже несмотря на то, что код будет на React Native, вы можете легко использовать эту статью, чтобы улучшить свои навыки React.

Исходный компонент, который мы будем рефакторить

Простой компонент, написанный таким же разработчиком, как вы. Что он делает:

  • Получает список браузеров с сервера.

  • Показывает состояние загрузки на экране.

  • Показывает загруженный список в виде списка карточек.

  • Когда пользователь нажимает на карточку, появляется модальное окно с подробным описанием браузера.

Этот код похож на ваш?

Чем хорош этот компонент:

  • Он корректно работает: список будет загружен и показан пользователю со всеми описанным выше функционалом.

  • Он использует React Hooks.

Что плохого в этом компоненте

Этот компонент является монолитом, потому что он был спроектирован и написан для использования в одном месте в одном приложении только один раз.

Запомните эту мантру:

Искусство быстрого разработчика это искусство писать повторно используемые строительные блоки.

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

  • Вы тратите меньше времени на написание кода. Повторно используемые компоненты/функции могут скрывать тонны кода внутри себя и могут использоваться сотни раз. А реализовать их нужно будет только один раз. Представьте, что каждый раз, когда вам нужно обратиться к API, вы используете fetch(URL), а это замедляет вашу работу.

  • Вы тратите еще меньше времени на написание кода. Гораздо быстрее добавлять новые функции в одно место, чем добавлять их в 10 разных мест. Если вы повторно используете свои строительные блоки, у вас будет только одно место для добавления ваших функций. Реализуйте функцию в одном месте, а результаты получайте везде.

  • Вы тратите меньше времени на поиск ошибок. Вы будете использовать уже проверенные строительные блоки. Новый код нужно тестировать, но старому можно доверять.

  • Вы тратите меньше времени на чтение кода. Если вы повторно используете строительный блок, это сэкономит ваше время на понимание кода. Легче создавать программы из знакомых частей.

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

Разработчик обычно тратит больше времени на выяснение того, что делает код, а не на его написание. Alexis Mangin. Почему разработчики React должны разбивать свои приложения на модули?

  • Вы пишете меньше тестов для повторно используемых строительных блоков. Тесты делают вашу жизнь сладкой, как мёд, но с монолитом вы обнаружите, что снова и снова проверяете одну и ту же логику. Больше повторно используемых строительных блоков = меньше тестов.

Монолит = медленное написание кода.

Многоразовые строительные блоки = быстрое написание кода.

Как сделать эти 100 строк кода более пригодными для повторного использования

Давайте сначала попробуем что-нибудь действительно базовое и простое, а затем перейдём к более сложным вещам. Как мы можем распределить эти 100 строк в разные места?

Перенести константы в пропсы вашего компонента

Мы переместили константу URL-адреса в пропсы и уже можем повторно использовать компонент Browser с другим URL-адресом. Несомненно, небольшое влияние на повторное использование, но это очень легко реализовать.

Разогрелись? Тогда давай попробуем что-нибудь действительно крутое.

Разделите бизнес-логику и интерфейс

Жизнь проще, когда компоненты пользовательского интерфейса не знают о сети, бизнес-логике или состоянии приложения. Передавая одни и те же пропсы, всегда отображайте одни и те же данные. Eric Elliott, Недостающее введение в React

Это самая важная часть статьи. Если вы можете выучить только одну вещь, пусть ею будет она:

Искусство быть быстрым React-разработчиком это искусство отделения бизнес-логики от интерфейса.

Ух ты, какой всплеск сложности! Не бойтесь, я научу вас.

Бизнес-логика: логика, которая принимает решения и сохраняет состояние, например: всё, что находится в <Browsers /> выше ключевого слова return.

Интерфейс: всё, что отображает состояние на экране и считывает ввод пользователя: всё, что находится внутри return()

Давайте сделаем небольшой шаг вперёд и разделим наш компонент на 2 части, а затем посмотрим, как это сделает наш код более пригодным для повторного использования.

Что мы будем делать:

  • Создадим кастомный хук useBrowsers() для нашей бизнес-логики.

  • Создадим компонент <BrowsersList /> с множеством пропсов для нашего интерфейса.

Хорошо, наш код далёк от совершенства, но стал более пригодным для повторного использования:

  • Мы можем использовать <BrowsersList /> с другим источником данных. Раньше мы были обязаны использовать данные только с одного места, но теперь мы можем получить их из памяти, с диска или любого другого места.

  • Мы можем использовать useBrowsers() с другим экраном или даже без экрана. Например, мы можем использовать useBrowsers() в другом приложении с другим дизайном.

Но наш разделённый код далёк от совершенства! Мы всё ещё можем сделать этот код более пригодным для повторного использования.

Повторно используемые строительные блоки = быстрое написание кода.

Разделите свой код на множество небольших файлов

Перво-наперво. Разделение монолита на 2 части открыло новые возможности для рефакторинга. Мы можем сделать нашу программу более читаемой (а вас быстрее), если разделим наш код на разные файлы/модули. Это позволит нам думать о каждом модуле изолированно,а также сделать каждый модуль более читабельным и многоразовым.

Типичная структура проекта на ReactТипичная структура проекта на React
  • index.js экспортирует <Browsers /> с Browsers.jsx.

  • В папках components и hooks хранятся строительные блоки, относящиеся к компоненту <Browsers />.

  • BrowsersList.jsx также может превратиться в папку со связанными хуками, компонентами и индексом.

Файловая структура проекта React представляет собой рекурсивное дерево.

Возможная рекурсивная структура проектаВозможная рекурсивная структура проекта

Подробнее о структуре React-приложений читайте в статье автора David Gilbertson На 100 % правильный способ структурировать React-приложение (или почему такого способа нет).

Давайте посмотрим на наш BrowsersList.jsx.

Стало гораздо меньше кода. Он стал гораздо менее загромождённым. Теперь мы можем сосредоточиться на рефакторинге этой части кода.

Меньше беспорядка на экране = быстрое написание кода

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

Большая проблема с множеством небольших повторно используемых файлов

Посмотрите на наш компонент <BrowsersList />:

  • если мы переименуем некоторые пропсы, например changeDescription в setSelectedBrowser или description в browser,

  • или мы удалим некоторые пропсы,

  • или добавим некоторые пропсы,

В каждом месте, где используется <BrowsersList />, будет ошибка!

Каждый раз, когда вы меняете входящие данные компонента, вы ломаете каждое место своего кода, в котором используется этот компонент. И вы будете часто менять входные данные, так как сложно всё продумать в первый же день, например: дизайн часто возникает из того, как вы пишете код. А поскольку мы сосредоточены на создании множества компонентов, которые можно использовать повторно, во многих местах будут возникать ошибки.

Ваша IDE не может переименовывать пропсы с помощью автоматического рефакторинга. Вам нужно будет сделать это вручную. Вручную проверить каждое место использования. Это замедлит вашу работу.

И хуже всего то, что мы получим ошибку и не узнаем об этом. Единственный способ поймать её это:

  • запустить приложение;

  • вручную перейти к сломанному компоненту и

  • получить ошибку;

  • прочитать текст ошибки;

  • исправить ошибку и попробовать ещё раз.

Это медленная и утомительная задача. Вот так неприятные баги попадают в продакшн. А такое бывает очень часто.

Проверьте, работает ли наш Browsers.jsx?

Кто знает! Для уверенности нам нужно открыть BrowsersList.jsx и useBrowsers.js и вручную сравнить входные данные.

Этот код не работает?

Да. У descripton в useBrowsers.js отсутствует i, это опечатка.

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

Перестаньте медленно писать код на JavaScript, ускорьте себя с помощью TypeScript!

Наступил 2021 год, и каждый разработчик React/React Native должен использовать TypeScript. Фактически нет причин не использовать его.

Он может выглядеть страшно.

Преимущества TypeScript значительно перевешивают недостатки, которые действительно существуют в JavaScript. Наиболее значимыми для нас являются дополнительные расходы на обучение.

Наиболее очевидное решение этой проблемы просто включите TypeScript, не меняя код, добавьте несколько простых объявлений типов. В конце концов, наш опыт показывает, что при простейшем использовании TypeScript можно получить много преимуществ. Felix Rieseberg, TypeScript в Slack.

Классные фичи TypeScript

Меньше ошибок.

Меньше ошибок = более быстрое написание кода.

38% ошибок в Airbnb можно было предотвратить с помощью TypeScript, согласно анализу.

TypeScript избавляет от ошибок.

Мы бы сразу нашли опечатку с буквой i:

TypeScript выделяет ошибкиTypeScript выделяет ошибки

Ваша IDE получит функцию автозаполнения для пропсов в React. Это особенно экономит ваше время, когда вы используете сторонние компоненты с неизвестными пропсами:

TypeScript автоматически предлагает вариантыTypeScript автоматически предлагает варианты

Ваша IDE получит функцию рефакторинга. Это сэкономит вам время на переименование пропсов. Все использования пропса будут автоматически переименованы одним щелчком мыши.

Вы никогда больше не забудете добавить проверку на null/undefined.

? означает, что параметр может быть неопределённым.? означает, что параметр может быть неопределённым.

И, конечно же, мы получим сообщение об ошибке, если попытаемся поместить недопустимые данные в наш пропс:

TypeScript проверяет правильность пропсов.TypeScript проверяет правильность пропсов.

TypeScript легко сэкономит вам массу часов и избавит от стресса, связанного с отладкой кода.

JavaScript = медленное написание кода.

TypeScript = быстрое написание кода.

Первый шаг к быстрому проектированию системы: определите свои типы.

Это неполное руководство по TypeScript. Если вы чувствуете, что застряли, перейдите на typescriptlang.org, а затем вернитесь к статье.

Вернёмся к нашему компоненту <BrowsersList /> и определим некоторые типы для его пропсов (не забудьте переименовать файл с .jsx в .tsx).

И обновите сигнатуру хука useBrowsers():

Теперь TypeScript проверит, что useBrowsers() и BrowsersList совместимы. Если мы когда-нибудь изменим входные данные для BrowsersList, мы получим ошибку. Один этот факт гарантирует гораздо меньше ошибок в продакшне.

Меньше ошибок = более быстрое написание кода.

Быстрая системная архитектура

BrowsersListProps в настоящее время выглядит беспорядочно:

  • Компонент должен показывать состояние загрузки. Используется 1 строка в определении типа.

  • Должен отобразиться список Browser[]. Используется 1 строка в определении типа.

  • Должно появиться модальное окно с подробным описанием браузера. Используются 4 строки в определении типа. Эти строки ограничивают нас отображением поля description в нашем модальном окне. И кажется, что есть вероятность, что нам понадобится отобразить больше полей браузера в этом модальном окне. Помните, очень дорого менять сигнатуру компонента.

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

Это снизит сложность пропсов:

TypeScript проверяет правильность сигнатуры.TypeScript проверяет правильность сигнатуры.

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

Типы писать очень легко. Их быстро описывать (4 строчки типов против 60 строк фактического компонента), а также они содержат много информации о вашей системе. Таким образом, вы можете написать несколько типов и разработать свою программу, фактически не написав никакого кода. Это сэкономит вам много-много времени на архитектуру.

Первый шаг в проектировании любой системы: определение ваших типов.

Архитектура и планирование с типами TypeScript = быстрое написание кода.

После того как вы определили свои типы, вы можете начать писать логику компонента

Давайте исправим <BrowsersList />, так как он будет обрабатывать новую сигнатуру BrowsersListProps. Мы можем провести рефакторинг <BrowserItem />, так чтобы потребовалось бы только 2 пропса вместо множества. Это сделает код более читаемым, а нас быстрее.

Если у вашего компонента много пропсов, это хороший намёк на возможный рефакторинг.

Этот компонент уже выглядит более читабельным и менее пугающим.

Меньше беспорядка на экране = быстрое написание кода.

Извлечение повторно используемоей логики <UIFriendlyList/> от <BrowsersList/>

Просто наблюдая за компонентом, разработчик испытывает множество разных ощущений. Например, я чувствую, что отображать состояние загрузки для списка в <BrowsersList /> это очень крутая функция. Настолько крутая, что, скорее всего, она будет использоваться в различных компонентах.

Это потенциально очень полезная и многоразовая функция нашего приложения. Но в настоящее время она связана с компонентом <BrowsersList />, и мы не можем повторно использовать её в другом месте. Давай исправим это.

Мы хотим создать новый компонент <UIFriendlyList /> и использовать его вместо <FlatList />. Этот <UIFriendlyList /> сможет отображать состояние загрузки.

Как всегда, мы начинаем с определения некоторых типов:

  • T это аргумент типа. Типы с аргументами называются Дженерик. T для UIFriendlyList <T> то же самое, что arg для функции foo (arg). Если вы хотите построить свой тип из другого типа, используйте Generic. Для получения дополнительной информации ознакомьтесь с этой статьёй Ross Bulat.

Объяснение дженериков в TypeScript

Посмотрите на это крутое решение:

  • Мы определяем типы для наших пропсов UIFriendlyListProps.

  • Мы определяем универсальный тип: список может содержать элементы любого типа.

  • UIFriendlyListProps расширяет FlatListProps из библиотеки React Native с помощью нашей функции индикатора загрузки.

  • Итак, мы определяем тип UIFriendlyListProps как пересечение типов из FlatListProps и {loading ?: boolean}

Насколькоэто круто?

Я доволен этим дизайном, давайте напишем тело этого компонента и переместим его в другой файл UIFriendlyList.jsx

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

<UIFriendlyList /> это компонент, который можно использовать повторно, и он наверняка сэкономит нам время в будущем. С этим компонентом мы стали более быстрым React-разработчиком.

Теперь давайте проверим наш <BrowsersList />:

Вот о чём я говорил. Этот компонент намного проще понять по сравнению с исходным BrowsersList . И у нас есть переиспользуемый компонент <UIFriendlyList />, который наверняка сэкономит нам время. Мы можем пойти еще глубже и предположить, что мы хотим повторно использовать логику ModalWindow + List, но мы на этом остановимся.

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

Мы, люди, склонны писать код как форму прокрастинации, откладывая решение сложных проблем, которые у нас есть сейчас, путём решения гипотетических проблем будущего. Justin Travis Waith-Mair. Прекратите писать повторно используемые компоненты React.

Мы закончили с интерфейсом, пора проверить бизнес-логику в хуке useBrowsers().

Рефакторинг бизнес-логики в хуке useBrowsers()

Как и компоненты, мы можем создавать настраиваемые повторно используемые хуки. Это делает код более читаемым, потому что кодовая база разделена на более мелкие, многоразовые блоки. Bikash Paneru. Как лучше писать функциональные компоненты в React.

Давайте проведем рефакторинг useBrowsers(), чтобы вернуть валидный объект BrowsersListProps. Я также отрефакторил loading: теперь пропс будет установлен в true перед запросом и на false после.

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

Как всегда, давайте начнём с определения некоторых типов. Мы хотим сделать хук useFetch(), который сможет хранить полученные данные в стейте, а также включать индикатор загрузки. Мы также хотим определить форму данных, которые мы получаем от API, как FetchBrowsersResults:

Неплохо выглядит, теперь давайте определим тело useFetch() и переместим его в специальный файл useFetch.ts:

Я также добавил нотификации на случай ошибки запроса. Таким образом, пользователь увидит описание ошибки.

useFetch() это многоразовая функция, которая наверняка сэкономит нам время в будущем. С помощью этой функции мы стали более быстрым React-разработчиком.

Теперь займёмся рефакторингом хука useBrowsers():

Сравните с изначальным useBrowsers(), он намного меньше по размеру и прост для понимания.

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

4 простых совета, как стать более быстрым React-разработчиком.

С основной частью мы закончили, давайте немного расслабимся. Я дам вам 4 простых совета.

1. Никогда не форматируйте код вручную.

Ваша IDE должна предоставлять вам функцию автоматического рефакторинга кода. Ваш проект на React должен содержать файлы .eslintrc.js и .prettierrc.js. Они настраивают линтинг и стиль кода. Вы должны иметь возможность применять этот стиль, нажав горячую клавишу:

Функция автоматического рефакторингаФункция автоматического рефакторинга

2. Никогда не импортируйте модули вручную.

Ваша IDE должна предоставлять вам функцию автоматического импорта. Никогда не вводите ../../../ вручную и не тратьте время на ввод/удаление импорта вручную.

Попробуйте функцию автоматического импорта в действии:

Добавление всеъ отсутствующих импортов.Добавление всеъ отсутствующих импортов.

3. Перемещайтесь по проекту как профессионал

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

Посмотрите на эту огромную файловую структуру крошечного мобильного приложения:

Огромная файловая структура приложенияОгромная файловая структура приложения

Знаете ли вы, как мучительно пытаться найти нужный файл во вкладках?

Открыто 10 вкладок. Где файл, который я ищу?Открыто 10 вкладок. Где файл, который я ищу?

Ваша IDE должна предоставлять вам следующие полезные инструменты:

  • перейти к файлу с помощью строки поиска;

  • перейти к компоненту с помощью строки поиска;

  • перейти к ранее открытому файлу;

  • перейти к определению компонента с помощью курсора;

  • перейти к использованию компонента.

Изучите горячие клавиши своей IDE. Это сделает вашу работу гладкой, как масло.

4. Используйте линтинг ESLint

Каждый React-разработчик знает о боли неправильной зависимости хуков useEffect/Memo/Callback. В них всегда сложно найти ошибки:

Найти эти ошибки без линтинга очень сложноНайти эти ошибки без линтинга очень сложно

ESLint позволяет без труда кешировать эти ошибки.

Инструменты разработки, такие как eslint и typescript, помогают поддерживать кодовую базу в большой команде. Хороший разработчик умеет программировать. Хороший разработчик умеет работать в команде. Roman Nguyen. Создание архитектуры вашего React приложения. Перспективы разработки и бизнеса, о чём следует знать.

Все эти функции доступны в IDE Webstorm. Я рекомендую вам использовать Webstorm для разработки на TypeScript с React.

Заключение: искусство разработки повторно используемых строительных блоков

Искусство быстрого разработчика это искусство писать повторно используемые строительные блоки.

Чтобы быть быстрым, вам необходимо:

  • Отделить бизнес-логику от интерфейса.

  • Использовать TypeScript, чтобы получать меньше ошибок.

  • Использовать TypeScript для включения мощных функций IDE: линтинга, рефакторинга имен, автозаполнения.

  • Определять типы перед написанием кода для быстрого проектирования архитектуры.

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

  • С помощью IDE форматировать код и импортировать модули.

  • Переходить по файлам с помощью горячих клавиш.

  • Практика рефакторинга.

Мы успешно отрефакторили компонент <Browsers />. Посмотрите на исходный компонент: это огромный, сложный для понимания монолит. Такой код кажется тяжёлым и замедляет нас.

Взгляните на эту лёгкую, как пёрышко, красоту:

Мы сделали простой для понимания компонент + извлекли 2 очень удобные многоразовые детали:

  • useFetch();

  • <UIFriendlyList />.

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

Поэкспериментируйте с кодом из статьи

Посмотрите итоговый результат в Codesandbox, чтобы поэкспериментировать и узнать больше. Этот код написан на React, он немного отличается от кода React Native в статье.

Что дальше?

  • Много читать.

  • Много практиковаться.

  • Задавать вопросы. Особенно под этой статьёй в разделе комментариев.

И последнее, но самое важное:

  • Автоматически тестируйте компоненты React.

  • Практика TDD.

Начать тестировать ваш код сложно, но это сделает вашу жизнь сладкой, как леденец. Посмотрите статью Ian Wilson "Как создавать надёжные приложения React с TDD и библиотекой тестирования React", это хорошая точка для начала изучения тестирования. Ежедневно тренируйте свое ремесло и со временем вы станете мастером. А также не забывайте осваивать новое (например с нашей помощью) только так можно сохранять свою актуальность в стремительно изменяющемся мире технологий. А наши менторы и чуткая поддержка в этом помогут.

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Думай как инженер 4 способа находить нестандартные решения

16.10.2020 16:14:58 | Автор: admin

В работе и быту нам часто приходится сталкиваться с проблемами, которые заводят в тупик, хотя кажется, что их решение лежит где-то на поверхности. Эх, мне бы сейчас чужие мозги, думаете вы. К счастью, операция необязательна, достаточно использовать МФО, РВС, детский язык и маленьких человечков! Не переживайте, это научно подкрепленные методы, которые заставляют мозг работать иначе. Вместе с ведущимителеграм-канала Креативность 101разбираемся с феноменом инерции мышления и преодолеваем психологические барьеры.

Эта и другие статьи раньше всего выходят в блоге на нашем сайте. Приятного чтения.


Предисловие


Попробуйте быстро ответить на вопросы: Чему равно два в квадрате? А четыре в квадрате? А пять в квадрате? А угол в квадрате?

Удалось ли вам так же быстро ответить на последний вопрос, как и на предыдущие три? Вероятно, вы не сразу переключились с возведения чисел во вторую степень на градусы углов в равностороннем прямоугольнике. А ведь если бы последний вопрос стоял отдельно (Чему равен угол в квадрате?), то ответ на него последовал бы куда быстрее. Это пример инерции мышления когда мы мыслим по накатанной и нам сложно переключиться на новую информацию.

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

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

Инерция мышления с точки зрения работы мозга


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

Нарушение привычного образа действия приводит к сильному стрессу. Есть даже таблицы стрессовых событий и относительных баллов, которыми они измеряются. К ним относятся, например: смена досуга (34 балла), и развлекательная поездка (33), и изменение условий труда (43) или смена профессии (50). Поэтому сложно просто так взять и начать что-то делать по-другому мозг избегает лишнего стресса.

Так что же можно сделать, чтобы ослабить инерцию мышления? Генрих Альтшуллер (советский писатель-фантаст, изобретатель, автортеории решения изобретательских задач (ТРИЗ)и теории развития творческой личностиеще в середине прошлого века заметил, что инженеры, увлеченные научной фантастикой, изобретают смелее, не боятся дерзких и необычных идей при решении задач с помощью ТРИЗ. То есть преодолеть инерцию мышления им помогают фантазия и воображение. Позже были выделены специальные операторы гашения инерции мышления, которые, среди прочего, помогают развивать творческое воображение. Рассмотрим некоторые из них.



1. Метод фокальных объектов (МФО)


Один из инструментов это метод фокальных объектов (МФО). Представьте себя с волшебной палочкой в руках, которая позволяет переносить свойства одного предмета на другой. Используйте свойства или признаки случайно выбранных объектов для совершенствования своего объекта путем переноса на него выбранных характеристик.

Как использовать такой метод:


  1. Ставим цель (для чего?).
  2. Выбираем объект, который будем совершенствовать.
  3. Выбираем любые другие слова (можно взять словарь, ткнуть пальцем в любое слово это будет идеальным словом, и лучше таких слов выбрать несколько).
  4. Определяем и выбираем их свойства.
  5. Применяем свойства к фокальному объекту.

Пример: нам нужно изменить стул, придумать с ним что-то новое. Выбираем несколько произвольных слов (у меня это холодильник, окно, игрушка). Определяем свойства выбранных слов: холодильник (большой, холодит, высокий, серый, на нем магнитики, двухкамерный), окно (прозрачное, пластиковое, открывается в разных вариациях, с москитной сеткой), игрушка (мягкая, приятная на ощупь, имеет части тела, легкая). Теперь применяем выписанные свойства к нашему изначальному объекту. Большой стул, стул с охлаждением, стул с высокими ножками, стул с высокой спинкой, металлический стул, магнитный стул, двухместный стул, прозрачный стул, пластиковый стул, стул-трансформер, стул с вентиляцией, стул с куполом из москитной сетки, мягкий стул, стул, покрытый материалом, который приятно трогать, стул, который можно обнять, стул из суперлегкого материала. Есть из чего выбрать, правда?

Что можно сделать при помощи МФО?


  • Преодолеть инерцию мышления;
  • Развить творческое мышление, фантазию и воображение (соединяйте несоединимое);
  • Получить в результате применения метода направление совершенствования (оригинальные модификации, неожиданные потенциальные свойства объекта например, книги стали водонепроницаемыми, чтобы дети смогли с ними купаться);
  • Выявить новые сферы применения объекта (например, винную бутылку сплющили, и она стала тарелкой в ресторане);
  • Создать необычные заголовки статей, сюжеты произведений, цепляющую и эффективную рекламу;
  • Развить речь, воображение у детей дошкольного возраста (А давай представим, что ложка была бы не прочная, а как тарелка хрупкая? Такие ложки есть? А если она была бы прозрачной, как стакан? А если бы она была большой или маленькой? Или пушистой?).



2. Детский язык или Попробуй объяснить ребенку


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

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

Как использовать детский язык?


  1. Кратко и четко, заменив термины простыми детскими, функциональными понятиями, изложить содержание трудной, длительно неразрешаемой, актуальной управленческой или технической проблемы.
  2. Кратко описать общую обстановку, акцентировать внимание на нежелательном эффекте.
  3. Нарисовать условную схему.

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

Если данную проблему пытаться объяснить ребёнку, выйдет следующее: висит пустой котелок, его нужно подогревать, но часть тепла уходит вверх, не коснувшись самого котелка, да и сам котелок быстро остывает, потому что сверху большое отверстие, через которое это тепло уходит. И если тепло уходит вверх, то что можно сделать, чтобы тепло сохранялись в пустом котелке? Перевернуть! Теперь тепло не уйдёт дальше, чем дно котелка. Задача решена.



3. Метод маленьких человечков (моделирование маленькими человечками)


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

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

Как использовать?


  1. Определяем то, что надо изменить (улучшить, исправить, устранить ошибку, доделать).
  2. Пишем, что будет Идеальным конечным результатом (ИКР).
  3. Рисуем зону и маленьких человечков, которые выполняют задачу.
  4. Видим, что они делают, и решаем проблему без них.

Человечки умеют выполнять всё (бегать, прыгать, держаться друг за друга, отпускать руки, вообще всё), и не надо задумываться, как они это сделают. Важно выяснить только то, что они должны делать, остальное несущественно.

Пример: решение задачи скопившегося снега в водосточных трубах. Многократно оттаивая и замерзая, снег превращается в ледяную пробку, а потом при потеплении падает вниз, ломая трубу. Получается, что ледяная пробка не должна падать вниз. ИКР пробка сама не падает вниз, пока не растает.

Рисуем человечков в трубе (много человечков), которые держат пробку. На что похоже? На цепь! Пока пробка висит на цепи, она не упадёт бомбой вниз и не сломает трубу, а будет стекать постепенно.

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

Например, как представить ортодонтическое лечение методом маленьких человечков? Вот зубы, они немного неровные. На каждый зуб прямо по центру крепится маленький человечек, который будет тянуть зуб к дуге, чтобы все человечки выстроились вдоль этой дуги. Но человечки могут тянуться не только к дуге, они могут тянуться вообще куда захотят, надо только придумать, куда. Если нужно быстрее совместить верхний и нижний зубной ряд, то свободные человечки потянутся от одних к другим (натянули эластики). Если нам нужно внедрить в кость какие-то зубы или наоборот немного вытащить, то представляем, что бы делали человечки в таком случае. Нашли бы опору, относительно чего это сделать. Мини-винт, вкрученный в кость, подойдет. Теперь человечки могут продолжать свою работу.

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



4. Оператор РВС (размер, время, стоимость)


И последний на сегодня способ приглушить инерцию мышления это стать оператором размера, времени и стоимости и представить себя человеком, меняющим реальность. Оператор РВС не решит вашу задачу, но с его помощью можно получить несколько необычных направлений решения. Суть его заключается в том, чтобы мысленно:

  • Увеличивать размеры объекта до бесконечности, а затем уменьшать до минимума;
  • Увеличивать время действия на объект до бесконечности, а затем сокращать до минимума;
  • Увеличивать стоимость объекта до бесконечности, а потом уменьшать до минимума.

Пример: В длинную узкую трубу нужно проложить провод. Если мысленно увеличить трубу до больших размеров, то представляется тоннель, и способ прокладки становится более понятным (надо просто взять провод и пронести, осталось придумать, кто это сделает).

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

Со стоимостью так же: представляем, что денег бесконечно много, и тогда можно запустить нанороботов, которые протащат провод или даже сконструируют его прямо там. Ну, а если денег почти нет, будем пользоваться подручными средствами.

А вот как работает оператор РВС в более бытовом ключе, например, в организации работы дома:

Размер. Что, если бы ваше рабочее место было огромным? Как бы вы организовали его? Например, отодвинули всё мешающее и отвлекающее как можно дальше. А если вы представите ваше рабочее место настолько маленьким, что вам бы пришлось работать стоя?

Время. Увеличиваем время того, что вам нужно сделать в течение недели до года. Как теперь организовать работу? А если есть только секунда? Что нужно сделать прямо сейчас?

Стоимость. У вас есть миллион, чтобы справиться с рядовой задачей. Как бы вы его потратили? Возможно, что-то делегировали? А если денег нет совсем? Может, самое время что-то освоить и сделать самому?

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

А вот еще один способ, на этот раз как сэкономить промокод HABR дает дополнительную скидку 10%, которая суммируется со скидкой на баннере.

image




Рекомендуемые статьи


Подробнее..

Перевод Алюминиевый профиль как универсальный ресурс для сборки чего угодно. Часть 1

06.06.2021 16:09:40 | Автор: admin

Еще недавно профиль типа Т-слот (T-slot) был не самым популярным, но после того, как его стали применять в конструкции многих моделей 3D-принтеров, он появился везде и всюду. Теперь он используется для сборки тех же 3D-принтеров, лазерных резаков, станков с ЧПУ.

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

Почему профиль удобен?



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

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

Типы профилей и их особенности


Говоря о профиле мы подразумеваем одновременно несколько типов изделий из алюминия с разной геометрией. Чаще всего встречается профиль в форме квадрата или прямоугольника. Наиболее распространенная разновидность профиль с квадратным сечением, продольным отверстием в центре и Т-образными пазами для подключения самых разных объектов.


Кстати, есть профили, изготовляемые по метрической системе, есть по имперской. Называют профиль (в данном случае квадратный) по его размерности. Например, квадратный профиль с длиной стороны 20мм будет называться профиль 20Х20. Официально такой профиль называется алюминиевый станочный профиль 20Х20.


Конечно, есть и другие формы профиля, некоторые из них весьма экзотические. Есть профили с полукруглым корпусом, есть с треугольным. У каждой формы собственное предназначение, все зависит от цели мастера и проекта. Пример такого проекта ниже. Авторы его, семейная пара, собрали шикарный стол из профиля и ДСП.


Крепление к профилю


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


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


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

Соединение профилей между собой


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


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

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

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

Аксессуары


Их огромное количество, продаются они там же, где и профили. Есть накладки, ножки, ролики, пружины, ручки и петли, равно, как и другие элементы. На любом сайте 3D-печати есть файлы как этих, так и любых других элементов для крепежей. Это могут быть держатели катушек, держатели для инструментов, лампы и т.п.

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

Где достать профиль?


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

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

Немного о 3D-печати креплений


Кронштейны и крепления можно печатать на принтере, о чем говорилось выше. Если есть достаточное количество пластика, можно распечатать и сам профиль. Т-гайки, о которых говорилось выше, тоже можно печатать.


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

Еще один нюанс профили от разных производителей могут чуть отличаться, поэтому и крепления для них будут другими. Крепление от производителя Х могут не подходить к профилям производителя Y.

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

Подробнее..

Перевод Как построить диаграмму на Python

16.10.2020 16:14:58 | Автор: admin

Каждый, кому хоть раз приходилось строить диаграммы в draw.io или Google Diagrams, помнит всю утомительность и медлительность этого процесса. Сегодня делимся с вами материалом, в котором шаг за шагом показывается, как можно строить красивые архитектурные диаграммы с помощью Python. Главное удобство встроенные узлы для обозначения сервисов и языков программирования. Только код и никакой мыши.



Предисловие


На этой неделе я наткнулся на ценную библиотеку Python. Эта библиотека называется Diagrams. С помощью этой библиотеки, достаточно быстро создаются красивые диаграммы, какие я мог бы сделать неуклюже вставляя изображения в draw.io или Google Diagrams. Я тратил бы часы, чтобы все правильно выровнять. В дополнение к этому изнурительному труду, когда мне позже нужно было обновить диаграммы, я поднимал и перемещал более половины компонентов ради нескольких изменений в архитектуре. После дальнейшего изучения библиотеки я смог увидеть, что она в состоянии облегчить задачу.

Начало работы


Нам нужен Python 3.6 или выше. Также нужна GraphViz: она визуализирует диаграммы. В репозитории Github есть довольно приличный раздел Начало работы. Мне для начала работы нужно было выполнить такую команду:

pip install diagrams


Код находится здесь.

Типы компонентов


Библиотека диаграмм предоставляет компоненты от различных поставщиков. Вероятно, что-то из этого списка соответствует вашим задачам.

  • AWS/GCP/Azure предоставляют официальные ресурсы облачных сервисов для диаграммы. Моя команда в основном работает в GCP, и я потратил бы часы на создание этих диаграмм вручную, прежде чем наткнуться на эту библиотеку, поэтому я был немного взволнован, когда обнаружил, что узлы у меня под рукой.
  • Универсальные и локальные эти узлы, скорее всего, будут использоваться вместе в случае, если вы хотите проиллюстрировать технологии в облаке независимо от него. Например, предоставление архитектуре компонента Beam поверх отображения Google DataFlow.
  • Фреймворки эти компоненты полезны, когда вы хотите показать язык программирования.
  • SaaS коллекция узлов SaaS, которые можно использовать, когда вы хотите показать, что ваша архитектуре есть уведомления вроде Slack.

Элементы диаграмм


  • Узлы это абстрактное понятие, представляющее собой единый компонент системы.
  • Кластеры позволяют организовать узлы в группы (или кластеры).
  • Ребра отображают связь между узлами.


Первая диаграмма


Теперь, когда вы знаете основные понятия, давайте построим очень простую диаграмму с кодом в том порядке, в котором мы узнали эти понятия. Диаграмма будет описывать простой веб-сайт с балансировкой нагрузки на AWS с базой данных PostgreSQL и кэшем Redis, чтобы показать разнообразие сервисов.

1. Рабочая область диаграммы


Опишем пустую диаграмму с меткой:

from diagrams import Diagramwith Diagram("Simple Website Diagram") as diag:    passdiag # This will illustrate the diagram if you are using a Google Colab or Jypiter notebook.


Код находится здесь.

2. Добавление узлов


Теперь, когда у нас есть рабочее пространство, пришло время добавить узлы веб-сайта. Добавляемые узлы имеют разных поставщиков, это AWS и OnPrem. Скорее всего, если бы вы делали это по-настоящему, вы бы придерживались AWS, поскольку у них есть такие предложения, как RDS и ElastiCache, которые, вероятно, есть в ваших проектах.

from diagrams import Diagram, Clusterfrom diagrams.aws.compute import EC2from diagrams.aws.network import ELBfrom diagrams.aws.network import Route53from diagrams.onprem.database import PostgreSQL # Would typically use RDS from aws.databasefrom diagrams.onprem.inmemory import Redis # Would typically use ElastiCache from aws.databasewith Diagram("Simple Website Diagram") as diag:    dns = Route53("dns")    load_balancer = ELB("Load Balancer")    database = PostgreSQL("User Database")    cache = Redis("Cache")    svc_group = [EC2("Webserver 1"),                 EC2("Webserver 2"),                 EC2("Webserver 3")]diag # This will illustrate the diagram if you are using a Google Colab or Jypiter notebook.

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


Код находится здесь.

3. Группировка узлов (необязательно)


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

from diagrams import Diagram, Clusterfrom diagrams.aws.compute import EC2from diagrams.aws.network import ELBfrom diagrams.aws.network import Route53from diagrams.onprem.database import PostgreSQL # Would typically use RDS from aws.databasefrom diagrams.onprem.inmemory import Redis # Would typically use ElastiCache from aws.databasewith Diagram("Simple Website Diagram") as diag:    dns = Route53("dns")    load_balancer = ELB("Load Balancer")    database = PostgreSQL("User Database")    cache = Redis("Cache")    with Cluster("Webserver Cluster"):        svc_group = [EC2("Webserver 1"),                    EC2("Webserver 2"),                    EC2("Webserver 3")]diag # This will illustrate the diagram if you are using a Google Colab or Jypiter notebook.

На рисунке видно, что диаграмма это по-прежнему просто список узлов, но теперь мы сгруппировали соответствующие узлы в логические группировки.


Код находится здесь.

4. Единое целое из компонентов


Здесь мы не будем связывать узлы, организованные для использования в архитектуре. Эта задача обычно занимала у меня больше всего времени, когда нужно было обновить или настроить архитектуру. Смотрите ниже. Мы всего лишь определяем поток к каждому узлу с помощью двойных стрелок, и все готово! Здесь мы связываем узлы без меток, но если вы посмотрите на документацию, то применение меток к вашим связям, то увидите, что это просто.

from diagrams import Diagram, Clusterfrom diagrams.aws.compute import EC2from diagrams.aws.network import ELBfrom diagrams.aws.network import Route53from diagrams.onprem.database import PostgreSQL # Would typically use RDS from aws.databasefrom diagrams.onprem.inmemory import Redis # Would typically use ElastiCache from aws.databasewith Diagram("Simple Website Diagram", direction='LR') as diag: # It's LR by default, but you have a few options with the orientation    dns = Route53("dns")    load_balancer = ELB("Load Balancer")    database = PostgreSQL("User Database")    cache = Redis("Cache")    with Cluster("Webserver Cluster"):        svc_group = [EC2("Webserver 1"),                    EC2("Webserver 2"),                    EC2("Webserver 3")]    dns >> load_balancer >> svc_group    svc_group >> cache    svc_group >> databasediag # This will illustrate the diagram if you are using a Google Colab or Jypiter notebook.

Итоговое изображение можно увидеть ниже, и теперь вы видите логическую связь между узлами диаграммы. Направление связи можно развернуть, изменив порядок узлов. В дополнение к настройке потока вы можете изменить и другие вещи: объект edge содержит три атрибута: метку, цвет и стиль. Я не буду описывать здесь, как это делается. Если вам интересно, ссылка на документацию есть в конце этой статьи, эти атрибуты отражают соответствующие атрибуты GraphViz, что облегчает работу, если вы работали с ребрами graphviz.


Код находится здесь.

Заключение


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


А вот еще один лайфхак, но уже не на Python промокод HABR дает дополнительную скидку 10%, которая суммируется со скидкой на баннере.

image




Читать еще


Подробнее..

Перевод Git, я хочу все отменить! Команды исправления допущенных ошибок

25.12.2020 14:07:02 | Автор: admin
image

Git удобная, но довольно сложная система. Сложность, прежде всего, в том, что по невнимательности можно допустить ошибку, которую затем сложно или вообще невозможно исправить. Документация Git предоставляет описание множества команд, которые дают возможность исправить ошибку.

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

Черт, я сделал что-то не то. Дайте мне волшебную машину времени!

git reflog
# you will see a list of every thing you've
# done in git, across all branches!
# each one has an index HEAD@{index}
# find the one before you broke everything
git reset HEAD@{index}
# magic time machine


image

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

Я сделал коммит, но сразу же заметил ошибку, ее нужно исправить!

# make your change
git add . # or add individual files
git commit --amend --no-edit
# now your last commit contains that change!
# WARNING: never amend public commits


Команда дает возможность поправить неприятные мелочи когда вы что-то закоммитили, а потом увидели проблему вроде отсутствующего пробела после знака "=". Да, есть возможность внести изменения новым коммитом, объединив два варианта при помощи rebase -i. Но это долгий путь.

NB! Никогда не изменяйте коммиты в публичной ветке. Применяйте команду лишь для коммитов в локальной ветке, иначе у вас будут проблемы.

Хочу изменить сообщение последнего коммита!

git commit --amend
# follow prompts to change the commit message


Это просто глупые требования к оформлению сообщений.

Я случайно закоммитил в мастер, хотя это должен был в новую ветку!

# create a new branch from the current state of master
git branch some-new-branch-name
# remove the last commit from the master branch
git reset HEAD~ --hard
git checkout some-new-branch-name
# your commit lives in this branch now :)


Если вы уже закоммитили в публичную ветку, команды не сработают. В этом случае поможет git reset HEAD@{укажите количество коммитов, на которое нужно вернуться} вместо HEAD~.

Ну вот, я ошибочно закоммитил не в ту ветку

# undo the last commit, but leave the changes available
git reset HEAD~ --soft
git stash
# move to the correct branch
git checkout name-of-the-correct-branch
git stash pop
git add . # or add individual files
git commit -m "your message here";
# now your changes are on the correct branch


Есть еще один способ, который использует большое количество разработчиков это cherry-pick.

git checkout name-of-the-correct-branch
# grab the last commit to master
git cherry-pick master
# delete it from master
git checkout master
git reset HEAD~ --hard


Мне нужно запустить diff, но ничего не получается

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

git diff --staged

В общем, это не баг, а фича, но она чертовски неочевидная \_()_/

Мне срочно нужно отменить коммит, который сделан 5 коммитов назад

# find the commit you need to undo
git log
# use the arrow keys to scroll up and down in history
# once you've found your commit, save the hash
git revert [saved hash]
# git will create a new commit that undoes that commit
# follow prompts to edit the commit message
# or just save and commit


К счастью, не нужно отказываться назад на 5 коммитов, занимаясь копипастом старых и новых файлов. Отменить все это можно при помощи revert.

Кроме того, откатить можно не только коммит, но и целый файл. Правда, это уже будут другие команды

Отменить изменения в файле

А вот и они, эти другие команды.

# find a hash for a commit before the file was changed
git log
# use the arrow keys to scroll up and down in history
# once you've found your commit, save the hash
git checkout [saved hash] -- path/to/file
# the old version of the file will be in your index
git commit -m "Wow, you don't have to copy-paste to undo"


Когда я впервые нашел эту возможность, это было КРУТО, КРУТО, К-Р-У-Т-О. Но если задуматься почему именно checkout лучший вариант для отмены изменений в файле? :shakes-fist-at-linus-torvalds:

Все, я сдаюсь

cd ..
sudo rm -r fucking-git-repo-dir
git clone https://some.github.url/fucking-git-repo-dir.git
cd fucking-git-repo-dir


Спасибо Eric V. За этот способ. И все жалобы по поводу использования sudo адресуйте ему.

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

# get the lastest state of origin
git fetch origin
git checkout master
git reset --hard origin/master
# delete untracked files and directories
git clean -d --force
# repeat checkout/reset/clean for each borked branch


Внимание! Эта статья не является исчерпывающим руководством. И да, есть и другие способы сделать то же самое, причем еще лучше. Но я пришел именно к этим вариантам методом проб и ошибок. Потом у меня появилась сумасшедшая идея поделиться находками. Берите это или уходите!

image

Комментарий эксперта

Даниил Пилипенко, директор центра подбора IT-специалистов SymbioWay и евангелист бэкенд-направления онлайн-университета Skillbox, дополнил перевод мнением о Git и его актуальности для разработчиков.

Git появился в 2005-ом году, и он далеко не сразу занял рынок. Помню, когда мы ещё в 2008-ом году в команде разработчиков внедряли SVN. И даже в 2012-ом одна близкая ко мне компания усиленно внедряла Mercurial. С годами для многих стало очевидным, что Git это лучшая система контроля версий, и её теперь используют практически все разработчики.

Если вы начинающий разработчик и собираетесь устраиваться на работу, обязательно изучите Git! Вы должны знать, что такое система контроля версий и зачем она нужна, что такое коммит, ветка, как клонировать репозиторий и отправлять сделанные изменения на сервер, как получать новые изменения с сервера, как делать merge, какие бывают виды reset. Поначалу эта тема вам может показаться непонятной и сложной, но вам нужно лишь привыкнуть пользоваться Git, и отвыкнуть вы уже не сможете.
Подробнее..

Перевод Подарок на Рождество от программиста Alexa, WebSocket и мобильное приложение

25.12.2020 16:21:43 | Автор: admin
Каждый год я дарю брату рождественские подарки необычным способом. Это началось как шутка на Рождество, но в конце концов дошло до того, что я превращаю подарок в настоящее испытание. В прошлом году я заставил его писать и звонить подаркам, чтобы узнать, готовы ли они к открытию. За год до этого мой брат должен был провести некоторые исследования пород собак Американского клуба собаководов и воспользоваться их результатами, чтобы понять, в каком порядке открывать свои подарки. Но в этом году всё по-другому.

Я решил воспользоваться своим опытом программиста, чтобы подарить брату неповторимое рождественское утро с эффектом погружения. Я сделал игру, в которую брат сможет играть на своём телефоне, чтобы понять, как открывать свои подарки. В чём прикол? Единственная возможность управления игрой это команды голосовому помощнику Alexa через Echo.




Игра


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

В игре можно выбрать два действия: move или explore. Explore исследование комнаты в поисках лакомства и возможности открыть подарок, move перемещение в соседнюю комнату. По мере перемещения карта начинает заполняться, с каждым движением макет видно всё лучше. Во всех комнатах Alexa рассказывает часть истории. Чем глубже мой брат в подземелье, тем более запутанной становится история. Здорово, да?


Карта

Мобильное приложение


Чтобы вызвать восторг от завтрашних приключений, каждый год в канун Рождества я даю брату ламинированное Руководство по получению подарков. В этом году в нём есть QR-код, в котором зашифрована ссылка для скачивания мобильного приложения. Брат установит приложение и будет готов к приключениям. Само приложение довольно простое. На самом деле это приложение только для того, чтобы смотреть на карту. Все команды идут через Alexa, так что нельзя делать ничего другого, кроме как смотреть на телефон и планировать следующий шаг.

У приложения два экрана: страница инвентаря и страница карты. На странице с инвентарем показываются найденные подарки и их изображения. На странице карты показаны исследованные комнаты и места, где нашлись предметы.


Страницы мобильного приложения

Каждый раз, когда мне нужно что-то быстро разработать, я захожу в OutSystems. Это интуитивно понятная платформа для разработки с минимумом кода, которая позволяет быстро создавать реактивные веб-страницы, веб-сервисы и мобильные приложения. Что в ней самое лучшее? Её можно бесплатно использовать для проверки концепции! Есть несколько подвижных частей, создающих впечатление волшебства:

  • API.
  • Мобильное приложение.
  • Навыки Alexa.
  • WebSocket.

API


Для выполнения логики и хранения состояния у приложения должен быть мозг. Мозг лучшего рождественского подарка это простой API, созданный в OutSystems. Он загружает положение на карте, проверяет возможность движения, перемещает вас, а затем рассказывает вам небольшую часть истории. Приложение целиком имеет только две конечные точки API: одну для перемещения, а другую для получения текущего состояния. Получить текущее состояние можно только при выходе из приложения и возврате в него (я знаю, что брат иногда будет делать перерывы). За кулисами, ниже, есть модель данных, которая строит карту, сюжет, подарок в инвентаре и переходы между комнатами.


Диаграмма отношений в игре.

С помощью OutSystems я создал модель данных, логику перемещения персонажа, а также REST API для управления всем этим. Пришло время создать фронтенд!

Мобильное приложение


И снова нам поможет OutSystems. У сервиса простой пользовательский интерфейс, позволяющий перетаскивать компоненты на экран, а затем собрать всё это в мобильное приложение для вас. Итак, я приступил к работе и нарисовал две страницы, чтобы отслеживать, какие подарки были найдены и какие области на карте исследованы. Для загрузки данных карты и их отображения на экране я воспользовался API. Как я уже упоминал, это довольно простое мобильное приложение, на него можно только смотреть. Я поиграл с CSS, добавил несколько рождественских изображений и решил, что этого достаточно.

Навык Alexa


Каждый день на работе я использую AWS, так что довольно хорошо знаком со многими сервисами платформы. Но я никогда раньше не разрабатывал навыки Alexa. Подойдя к приставке, я начал смотреть видео. Оказывается, команда Alexa в AWS действительно знает, что делает. Я увидел одно из лучших представлений простого решения сложной проблемы. В навыке есть две составляющие:

  • Модель взаимодействия.
  • Код на бэкенде.

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


Два интента для навыка Alexa.

Код на бэкенде писался так же легко. Можно написать код навыков в VS Code, с помощью расширения Alexa отправив его в облако. Это достаточно просто. Определите в коде, что будет делать каждый интент при вызове. В игре я только вызывал разработанное в OutSystems API, поэтому для каждого интента написал быстрый вызов с помощью axios к соответствующей конечной точке API, попросив Alexa повторить ответное сообщение. Готово!

WebSocket


Во время тестирования приложения я быстро понял, что кое-что забыл. Как обновить мобильное приложение, когда Alexa перемещает персонажа? Мне нужно было что-то, что передаёт данные в приложение всякий раз, когда происходит событие. И это был WebSocket. WebSocket, по сути, открывает двусторонний канал связи между браузером (или мобильным приложением) и сервером. Это позволяет получать сообщения сразу вместо того, чтобы постоянно опрашивать сервер на предмет обновлений.

Внутри WebSocket происходит много вещей. К счастью для меня, есть компании, сосредоточенные на упрощении всего этого для потребителей. Я создал бесплатную учётную запись на Pusher, сконфигурировал приложение несколькими щелчками мыши и был готов интегрировать Pusher в мой API и мобильное приложение. WebSocket можно представить себе как подход издатель/подписчик. Когда в системе что-то происходит, запускается событие, подписчик получает его и выполняет действие.

Я обновил API для публикации события Pusher всякий раз, когда персонаж перемещался или исследовал комнату. Кроме того, я включил в сообщение часть возвращённой API истории. В мобильное приложение я добавил простой фрагмент кода подписки на события на JavaScript. Подписка обновляет данные на экране и отображает новую часть истории. Быстрый тест через мою Echo показал, что персонаж перемещается по карте, как только я проговариваю слово. Круто, да?


Панель управления Pusher показывает график сообщений WebSocket.

Разоблачение


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

Верите или нет, но я не хотел, чтобы прошлогодний подарок расстроил моего брата. Я хочу, чтобы этот подарок был для него особенным, радостным случаем в жизни и возможностью развивать мои навыки разработчика и учиться для меня. Думаю, подарок в этом году хороший компромисс. Он тоже довольно сложный, но есть история, за которой нужно следить. Кроме того, мне приходится работать со многими современными, фантастическими технологиями.

И это бесплатно! Всё, что я сделал для этого подарка, возможно на уровне бесплатного использования, а это значит, что единственной затратой было моё время. Я потратил около 30 часов, чтобы придумать историю, нарисовать карту и собрать всё воедино. Это была инвестиция наверняка. На Рождество мы узнаем, стоила ли она того. Обязательно сообщу о результатах. С Рождеством!


Обучение со скидкой чем не подарок самому себе, в новом 2021 году? А промокод HABR сделает этот подарок еще приятнее, добавив 10% к скидке на баннере.
image


Подробнее..

Перевод Продвинутые функции гита, о которых вы, возможно, не знали

04.03.2021 18:06:42 | Автор: admin

Git очень мощный инструмент, который практически каждый разработчик должен использовать ежедневно, но для большинства из нас git сводится к нескольким командам: pull commit push. Однако, чтобы быть эффективным, продуктивным и обладать всей мощью git, необходимо знать ещё несколько команд и трюков. Итак, в этой статье мы исследуем функции git, которые просто запомнить, применять и настроить, но которые могут сделать ваше время с git гораздо более приятным.


Прокачиваем базовый рабочий процесс

Прежде чем мы воспользуемся даже самыми базовыми командами pull, commit и push, необходимо выяснить, что происходит с нашими ветками и изменёнными файлами. Для этого можно воспользоваться git log довольно известной командой, хотя не все знают, как сделать его вывод на самом деле читабельным и красивым:

Дерево git log.Дерево git log.

Такой граф даст хороший обзор, однако часто нужно копать немного глубже. Например, посмотреть историю (эволюцию) определённых файлов или даже отдельных функций; в этом поможет git log с флагом -L::).

git log для функции.git log для функции.

Теперь, когда мы немного представляем происходящее в репозитории, мы, возможно, захотим проверить различия между обновлёнными файлами и последним коммитом. Здесь можно воспользоваться git diff; опять же ничего нового здесь нет, но у diff есть кое-какие опции и флаги, о которых вы, возможно, не знаете. Например, можно сравнить две ветки: git diff branch -a branch -b, или даже конкретные файлы в разных ветках: `git diff <commit-a> <commit-b> -- <пути>`.

Иногда чтение git diff становится трудной задачей. Можно попробовать прописать игнорирующий все пробельные символы (white-space) флаг -w, и этим немного заспамить diff, или флаг --word-diff и работать вместо строк с раскрашенными словами.

Если простой статичный вывод в оболочке вас не устраивает, можно запустить difftool, вот так: git difftool=vimdiff, команда откроет файлы diff внутри vim в два окна слева и справа. Очевидно, что Vim не единственный вариант; можно запустить git difftool --tool-help, чтобы увидеть список всех инструментов, которые можно использовать вместе с diff.

Мы уже видели, как просматривать историю конкретных частей или строк в файла с помощью git log. Было бы удобно делать нечто подобное, например, стейджинг частей файлов, правда? И такое легко делается в в IDE, например, в IntelliJ; то же самое уже сложнее в git CLI, но, конечно же, по-прежнему возможно: в git add пропишите опцию --patch:

Команда открывает редактор, в котором отображается один "hunk" [кусок], представляющий собой кусок кода с несколькими отличающимися друг от друга строками в нём. Можно много чего сделать с этим куском, но самые важные опции это y принять изменения (делает стейджинг), n не принимать (не делать стейджинг) и e отредактировать кусок перед стейджингом (полный список опций здесь).

Когда закончите с интерактивным стейджингом, вы можете запустить git status, и увидите, что файл с частичным стейджингом находится в разделах "Changes to be committed:" и "Changes not staged for commit:". Кроме того, можно запустить git add -i (интерактивный стейджинг), а затем воспользоваться командой s (статус), которая покажет вам, какие строки находятся на стейджинге, а какие нет.

Исправление распространённых ошибок

Закончив со стейджингом, я (слишком) часто осознаю, что добавил то, чего добавлять не хотел. Однако на этот случай у git для файлов нет команды un-stage. Чтобы обойти ограничение, можно сбросить репозиторий командой git reset --soft HEAD somefile.txt. Вы также можете включить в git reset флаг -p, который покажет вам тот же UI, что и у git-add -p. Также не забудьте добавить туда флаг --soft, иначе вы сотрёте ваши локальные изменения!

Поменьше грубой силы

Теперь, когда мы закончили стейджинг, всё, что осталось, commit и push. Но что, если мы забыли что-то добавить или совершили ошибку и хотим исправить уже запушенные коммиты? Есть простое решение, использующее git commit -a и git push --force, но оно может быть довольно опасным, если мы работаем над общей веткой, например, master. Таким образом, чтобы избежать риска перезаписи чужой работы из-за того, что мы решили проблему грубой силой, мы можем воспользоваться флагом --force-with-lease. Этот флаг в отличие от --force запушит на изменения только в том случае, если за время работы никто не добавил никаких изменений в ветку. Если ветка была изменялась, код не будет отправлен, и этот факт сам по себе указывает на то, что перед отправкой кода мы должны выполнить git pull.

Правильное слияние веток

Если вы работаете над репозиторием, в котором участвует более одного разработчика, можно с уверенностью предположить, что вы работаете в отдельной ветке, а не в мастере. Это также означает, что рано или поздно вам придётся включить свой код в кодовую базу (главную ветку). Вполне вероятно, что, пока вы работали над своей веткой, кто-то другой уже добавил свой код в мастер, из-за чего ветка вашей функциональности отстаёт на несколько коммитов. Можно пойти напролом и выполнить слияние вашего кода в мастер с помощью git merge, но команда создаст дополнительный комммит слияния, а также, без необходимости на то, затруднит чтение истории и сделает её сложнее:

История с ветвлением.История с ветвлением.

Подход гораздо лучше (не стесняйтесь спорить со мной по этому поводу, образно говоря, это та высота, на которой я готов умереть) заключается в том, чтобы сделать rebase ветки функции в master, а затем выполнить так называемую быструю перемотку (git merge --ff). Подход сохраняет историю линейной, читать такую историю легче, упрощается и последующий поиск коммитов с новым функционалом и коммитов виновников ошибок.

Но как нам сделать такой rebase? Можно выполнить rebase в его базовой форме с помощью git rebase master feature_branch, чего часто бывает достаточно (за этим следует push --force). Однако, чтобы получить от git rebase максимальную отдачу, также следует включить флаг -i, чтобы rebase был интерактивным. Интерактивный rebase удобный инструмент, чтобы, например, переформулировать, сжать или вообще очистить ваши коммиты и всю ветку. В качестве небольшой демонстрации мы можем даже сделать rebase ветки на саму себя:

Приём выше позволяет нам повторно применять последние 4 коммита и изменить их, получив полезный результат, например сжать одни коммиты и переформулировать другие:

Выше показан пример сеанса rebase. В верхней части показывается ветка перед перезагрузкой. Вторая часть фрагмента это список коммитов, представленных после запуска git rebase каждый из них можно выбрать, чтобы включить в работу (pick). Мы можем изменить действие для каждого из них, а также полностью переупорядочить коммиты. Как показано в третьем разделе примера, некоторые допустимые действия переформулирование (оно говорит git открыть редактор сообщений о коммите), сжатие коммита (объединяет коммиты в предыдущий) и исправление коммита: (исправление работает как сжатие, но при этом сбрасывает сообщение о коммите). После того как мы применим эти изменения и переформулируем изменённые коммиты, мы получим историю, которая показана на скриншоте выше, в его нижней части.

Если во время rebase вы столкнулись с каким-либо конфликтом, чтобы разрешить его, вы можете запустить git mergetool --tool=vimdiff, а затем продолжить rebase с помощью git rebase --continue. git mergetool может быть вам не знаком, на первый взгляд он может показаться пугающим. В действительности же это то же самое, что IDE вроде IntelliJ, просто в стиле Vim. Если вы не знаете хотя бы несколько сочетаний клавиш Vim, то, как и в случае с любым другим использующим этот редактор инструментом, вам, может быть, трудно даже понять, на что на самом деле вы смотрите. Если вам нужна помощь, я рекомендую прочитать эту исчерпывающую статью.

Если всё это кажется слишком сложным или вы просто боитесь работать с rebase, в качестве альтернативы создайте пул реквест на GitHub и нажмите кнопку Rebase and merge, чтобы сделать, по крайней мере, простые и быстрые rebase и merge с быстрой перемоткой.

Главное эффективность

Я думаю, что примеры выше показали несколько изящных советов и хитростей, но всё это может быть довольно сложно запомнить, особенно когда дело касается команд вроде git log. К счастью, чтобы разрешить эти трудности, можно воспользоваться глобальной конфигурацией git. Она находится в ~/.gitconfig и обновляется каждый раз, когда вы запускаете git config --global. Даже если вы не настраивали этот файл, он, вероятно, содержит кое-какие базовые вещи, такие как раздел [user], но можно добавить много других разделов:

Выше приведён пример некоторых из доступных опций конфигурации. Примечательно, что длинная команда git log это только псевдоним git graph. Автокоррекция установлена 10: такое значение включает её и заставляет ждать 1 секунду, прежде чем выполнить правильную команду, в которой была опечатка, и, наконец, последний раздел подписывание коммита GPG (подробнее об этом читайте ниже).

Настройка .gitconfig с кучей алиасов требует отдельной статьи. Есть довольно много хороших ресурсов и примеров того, что можно прописать в .gitconfig, поэтому вместо полного списка всех опций и псевдонимов я оставлю ссылки:

Автозавершение команд это инструмент не менее продуктивный, чем псевдонимы, и он просто устанавливается:

Extras

Можно не только писать свои псевдонимы, но и взять на вооружение плагин git-extras, он вводит много полезных команд, которые могут немного упростить вам жизнь. Я не буду вдаваться в подробности обо всех возможностях этого плагина посмотрите список команд, а я просто покажу один краткий пример из этого списка прямо здесь:

  • git delta список файлов, которые в другой ветке отличаются.

  • git show-tree древовидное представление коммитов всех ветвей, похожее на показанный ранее git log.

  • git pull-request пул-реквест в командной строке.

  • git changelog генерирует журнал изменений (changelog) из тегов и сообщений в коммитах.

Конечно, это не единственный крутой плагин. Например, есть ещё один удобный инструмент, позволяющий открыть репозиторий в браузере прямо из командной строки. Кроме того, в приглашении терминала можно настроить статус репозитория, это делается с помощью zsh или bash-it.

Подписываем коммиты

Даже если вы никогда не вкладывались в какой-либо проект Open Source, вы, вероятно, прокручивали историю коммитов такого проекта. В ней вы, скорее всего, видели значок подтверждённого (sign-off знак о правах на ПО), проверенного или подписанного коммита. Что это такое и зачем?

Первый значок используется, когда автор подтверждает, что соответствующий код написал именно он, или же значком вы подтверждаете, что, насколько вам известно, он был создан на основе соответствующей лицензии Open Source. Это делается по юридическим причинам, которые связаны со статусом авторских прав на код. Обычно вам не нужно пользоваться этим значком, но, если вы в какой-то момент захотите внести вклад в проект, который требует подтверждения прав, знак подтверждения ставится так:

Сверху видно, что в git commit с опцией --sign-off в конце сообщения о коммите автоматически добавляется строка Signed-off-by: , которая формируется на основе вашего имени пользователя в конфигурации git.

Что касается значка signed/verified, который вы, вероятно, заметили в некоторых репозиториях, он существует, потому что на GitHub довольно легко выдавать себя за других пользователей. Всё, что вам нужно сделать, изменить имя сделавшего коммит человека и электронную почту в вашей конфигурации и отправить изменения. Чтобы предупредить ситуацию, вы можете подписывать коммиты с помощью ключей GPG, подтверждающих, что автор коммита и отправитель изменений на самом деле является тем, за кого он себя выдаёт. Подпись коммита более распространена, чем подтверждение прав, поскольку важно знать, кто на самом деле внёс код.

Если вы хотите начать пользоваться этой функцией или, возможно, хотите внедрить её в вашей команде, можно сделать следующее:

Сначала вы генерируете пару ключей GPG (если у вас её ещё нет), затем устанавливаете ключи при помощи git config и, наконец, добавляете опцию -S, когда делаете коммит. Затем, посмотрев на информацию о коммите на GitHub, вы увидите значок, как на картинке ниже.

Подписанный непроверенный коммит.Подписанный непроверенный коммит.

Однако, как видно на изображении, подпись не проверена, потому что GitHub не знает, что ключ GPG принадлежит вам. Чтобы это исправить, открытый ключ из нашей пары ключей нужно отправить на GitHub. Для этого экспортируем ключ командой gpg --export, как здесь:

Затем скопируйте этот ключ и вставите его в поле https://github.com/settings/gpg/new. Если вы проверите ранее подписанный коммит после добавления ключа, то увидите, что коммит теперь проверен (verified). Здесь предполагаем, что вы добавили на GitHub именно тот ключ, которым подписывали коммит:

Подписанный проверенный коммит.Подписанный проверенный коммит.

Заключение

Git очень мощный инструмент, у которого слишком много подкоманд и опций, чтобы в одной статье описать их все. Если вы хотите глубже погрузиться в некоторые связанные с Git темы, я бы порекомендовал прочитать Debugging with Git, чтобы узнать больше о blame, bisect или Getting solid at Git rebase vs. merge, чтобы глубже понять rebase и merge. Помимо множества полезных статей в Интернете часто при поиске информации о некоторых тонкостях git лучший выбор это мануал, который выводится опцией --help, или версия в сети.


Узнайте подробности, как получить Level Up по навыкам и зарплате или востребованную профессию с нуля, пройдя онлайн-курсы SkillFactory со скидкой 40% и промокодомHABR, который даст еще +10% скидки на обучение.

Подробнее..

Перевод Как считывать и удалять метаданные из ваших фотографий с помощью Python

12.04.2021 16:18:43 | Автор: admin

Те, кто знает Python, могут делать поистине удивительные вещи, например, создавать арт-объекты и игры и красивые карты, полнотекстовую поисковую машину и систему распознавания лиц. Применим Python и для вполне бытовых задач. Сегодня, специально к старту новых потоков курсов Python для веб-разработки и Fullstack-разработчик на Python поделимся с вами туториалом, как считывать, добавлять и удалять EXIF метаданные из фотографий с помощью Python.


Немного очевидного для тех кто не в теме метаданные в фото

Массовое распространение смартфонов (по сути, портативных, оснащённых датчиками, всегда подключённых к сети компьютеров) привело к двум важным последствиям влиянию на конфиденциальность и влиянию на безопасность, и это ещё не до конца изучено:

  1. У большого количества людей есть камера, которая обычно находится в пределах досягаемости.

  2. По умолчанию фотографии, сделанные на камеру, могут выдавать конфиденциальную информацию, и многие пользователи не подозревают об этом.

Помимо данных изображения фотографии, сделанные с помощью смартфонов и современных цифровых камер, содержат метаданные, которые являются дополнительной информацией о фотографии. Эти метаданные хранятся в формате EXIF (сокращение от EXchangeable Image File format), который является постоянно развивающимся стандартом для информации, добавляемой к цифровым изображениям и звукозаписям.

На фотографиях EXIF может включать такую информацию, как:

  • Размеры и плотность пикселей фото.

  • Марка и модель устройства, на котором был сделан снимок.

  • Масштабирование, диафрагма, вспышка и другие настройки камеры при съёмке фотографии.

  • Ориентация устройства при фотографировании.

  • Когда было сделано фото.

  • Где было сделано фото.

  • В каком направлении была обращена камера.

  • Высота, на которой был сделан снимок.

Эти метаданные полезны для сортировки, каталогизации и поиска фотографий, поэтому был определён стандарт EXIF. Однако при этом возникают проблемы с конфиденциальностью и безопасностью, которые многие пользователи не принимают во внимание.

Одним из первых предостережений об EXIF является инцидент с конфиденциальностью в 2012 году, связанный с основателем антивирусной компании Джоном Макафи. Это было захватывающее время в жизни Макафи, поскольку он уклонялся от действий правоохранительных органов, но при этом давал журналистам эксклюзивные, пользующиеся большим спросом интервью. Один журналист, которому посчастливилось взять интервью, решил похвастаться своей удачей, опубликовав фотографию Макафи, но предварительно не удалив EXIF-данные. Они и раскрыли местонахождение Макафи и привели к его аресту.

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

Я подозреваю, что вы как разработчики, заботящиеся о безопасности, вероятно, задаёте себе следующие вопросы:

  • Как я могу программно обнаружить и прочитать EXIF-метаданные с фотографии?

  • Как я могу программно изменять, добавлять или удалять EXIF-метаданные?

Если на вашем компьютере установлен Python 3.6 или более поздней версии, вы можете узнать это, выполнив приведённые ниже инструкции. Они охватывают пару пакетов Python, которые вы можете включить в свои приложения для извлечения, добавления, изменения или удаления EXIF-данных с фотографий. Попутно вы не только будете использовать свои навыки программирования, но и поработаете детективом!

Модуль exif

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

Чтобы установить exif, используйте pip, введя в командной строке следующее:

pip install exif

Если при вводе этой команды выдаётся сообщение об ошибке, попробуйте вместо этого использовать команду pip3 install exif. pip3 это версия pip, менеджера пакетов Python, специально предназначенная для Python 3.

Если вы не поддерживаете какой-либо код на Python 2, но вам нужно использовать pip3 для установки пакетов Python, то вам следует подумать об обновлении Python.

Загрузка фотографий и проверка их на наличие данных EXIF

Давайте проверим exif в работе. Рассмотрим эти две фотографии, palmtree1.jpg и palmtree2.jpg:

Предположим, вам задали следующие вопросы:

  1. Были ли эти фотографии сделаны на одно устройство или на разные?

  2. Какое фото было сделано первым?

  3. Где были сделаны эти фотографии?

Чтобы ответить на эти вопросы, мы загрузим данные из этих фотографий в объекты exif-Image, а затем будем использовать эти объекты для проверки EXIF-метаданных:

from exif import Imagewith open("./images/palm tree 1.jpg", "rb") as palm_1_file:    palm_1_image = Image(palm_1_file)    with open("./images/palm tree 2.jpg", "rb") as palm_2_file:    palm_2_image = Image(palm_2_file)    images = [palm_1_image, palm_2_image]

Код открывает каждый файл только для чтения. Он считывает данные файла в двоичном формате в объект файла, который затем используется для создания экземпляра объекта exif-Image. Наконец, он помещает объекты Image в массив, чтобы мы могли перебирать их, выполняя одни и те же операции с EXIF-метаданными каждой фотографии.

Давайте выполним нашу первую операцию: убедимся, что фото действительно содержат EXIF-данные. Мы сделаем это, проверив свойство has_exif каждого объекта Image. Для каждого изображения, содержащего EXIF-данные, мы будем использовать свойство изображения exif_version, чтобы отобразить версию EXIF:

for index, image in enumerate(images):    if image.has_exif:        status = f"contains EXIF (version {image.exif_version}) information."    else:        status = "does not contain any EXIF information."    print(f"Image {index} {status}")

При запуске этот код даёт следующие результаты:

Image 0 contains EXIF (version 0220) information.Image 1 contains EXIF (version 0232) information.

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

Какие метаданные EXIF доступны на каждой фотографии?

Думайте о EXIF как о структуре данных ключ-значение, подобной словарю Python. Пары ключ-значение EXIF называются тегами, и каждый тег может содержать строковое или числовое значение. В текущем стандарте EXIF (версия 2.32) есть десятки тегов, и каждый от производителей смартфонов и камер до фотографов может добавлять свои собственные теги.

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

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

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

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

Объекты Image, предоставляемые модулем exif, представляют теги EXIF как свойства этого объекта. Это означает, что вы можете использовать встроенную функцию Python dir() для объекта Image, чтобы увидеть, какие теги EXIF у него есть.

Следующий код отображает список тегов каждого объекта Image в нашем списке изображений:

image_members = []for image in images:    image_members.append(dir(image))for index, image_member_list in enumerate(image_members):    print(f"Image {index} contains {len(image_member_list)} members:")    print(f"{image_member_list}\n")

Вы увидите следующий результат:

Image 0 contains 53 members:['_exif_ifd_pointer', '_gps_ifd_pointer', '_segments', 'aperture_value', 'brightness_value', 'color_space', 'components_configuration', 'datetime', 'datetime_digitized', 'datetime_original', 'delete', 'delete_all', 'digital_zoom_ratio', 'exif_version', 'exposure_bias_value', 'exposure_mode', 'exposure_program', 'exposure_time', 'f_number', 'flash', 'flashpix_version', 'focal_length', 'focal_length_in_35mm_film', 'get', 'get_file', 'get_thumbnail', 'gps_altitude', 'gps_altitude_ref', 'gps_datestamp', 'gps_latitude', 'gps_latitude_ref', 'gps_longitude', 'gps_longitude_ref', 'has_exif', 'light_source', 'make', 'max_aperture_value', 'metering_mode', 'model', 'orientation', 'photographic_sensitivity', 'pixel_x_dimension', 'pixel_y_dimension', 'resolution_unit', 'scene_capture_type', 'sensing_method', 'shutter_speed_value', 'subsec_time', 'subsec_time_digitized', 'subsec_time_original', 'white_balance', 'x_resolution', 'y_resolution']Image 1 contains 68 members:['<unknown EXIF tag 316>', '<unknown EXIF tag 322>', '<unknown EXIF tag 323>', '<unknown EXIF tag 42080>', '_exif_ifd_pointer', '_gps_ifd_pointer', '_segments', 'aperture_value', 'brightness_value', 'components_configuration', 'datetime', 'datetime_digitized', 'datetime_original', 'delete', 'delete_all', 'exif_version', 'exposure_bias_value', 'exposure_mode', 'exposure_program', 'exposure_time', 'f_number', 'flash', 'flashpix_version', 'focal_length', 'focal_length_in_35mm_film', 'get', 'get_file', 'get_thumbnail', 'gps_altitude', 'gps_altitude_ref', 'gps_dest_bearing', 'gps_dest_bearing_ref', 'gps_horizontal_positioning_error', 'gps_img_direction', 'gps_img_direction_ref', 'gps_latitude', 'gps_latitude_ref', 'gps_longitude', 'gps_longitude_ref', 'gps_speed', 'gps_speed_ref', 'has_exif', 'lens_make', 'lens_model', 'lens_specification', 'make', 'maker_note', 'metering_mode', 'model', 'offset_time', 'offset_time_digitized', 'offset_time_original', 'orientation', 'photographic_sensitivity', 'pixel_x_dimension', 'pixel_y_dimension', 'resolution_unit', 'scene_capture_type', 'scene_type', 'sensing_method', 'shutter_speed_value', 'software', 'subject_area', 'subsec_time_digitized', 'subsec_time_original', 'white_balance', 'x_resolution', 'y_resolution']

Как вы можете видеть, в то время как оба объекта Image имеют много общих тегов, изображение 1 содержит немного больше, чем изображение 0. Это означает, что изображение 1 имеет несколько больше тегов EXIF, чем изображение 0. Это сильный индикатор того, что изображение 0 и изображение изображение 1 было снято на разные устройства.

Вы можете использовать стандартный метод set() для определения общих элементов изображения 0 и изображения 1:

common_members = set(image_members[0]).intersection(set(image_members[1]))common_members_sorted = sorted(list(common_members))print("Image 0 and Image 1 have these members in common:")print(f"{common_members_sorted}")

Запуск этого кода даёт следующий результат:

Image 0 and Image 1 have these members in common:['_exif_ifd_pointer', '_gps_ifd_pointer', '_segments', 'aperture_value', 'brightness_value', 'components_configuration', 'datetime', 'datetime_digitized', 'datetime_original', 'delete', 'delete_all', 'exif_version', 'exposure_bias_value', 'exposure_mode', 'exposure_program', 'exposure_time', 'f_number', 'flash', 'flashpix_version', 'focal_length', 'focal_length_in_35mm_film', 'get', 'get_file', 'get_thumbnail', 'gps_altitude', 'gps_altitude_ref', 'gps_latitude', 'gps_latitude_ref', 'gps_longitude', 'gps_longitude_ref', 'has_exif', 'make', 'metering_mode', 'model', 'orientation', 'photographic_sensitivity', 'pixel_x_dimension', 'pixel_y_dimension', 'resolution_unit', 'scene_capture_type', 'sensing_method', 'shutter_speed_value', 'subsec_time_digitized', 'subsec_time_original', 'white_balance', 'x_resolution', 'y_resolution']

Если вы смотрели EXIF-теги в документации (либо список стандартных тегов EXIF, либо расширенный список), вы могли заметить, что имена тегов EXIF находятся в PascalCase, а свойства EXIF в объектах exif-Image в snake_case. Это связано с тем, что авторы модуля exif стремятся следовать руководству по стилю Python и разработали Image для преобразования имён тегов EXIF в имена свойств Python.

Члены класса Image exif, не являющиеся тегами EXIF

Большинство членов класса любых экземпляров изображения будут свойствами, соответствующими тегам EXIF в изображении. Однако есть несколько членов для работы с метаданными EXIF. Они перечислены в таблице ниже:


Члены класса

Описание

delete(attribute)

Удаляет из изображения тег EXIF, указанный в строковом атрибуте.

delete_all

Удаляет все теги EXIF из изображения.

get(attribute, default=None)

Возвращает значение тега EXIF, заданное строковым атрибутом. Если тег недоступен или содержит значение, он возвращает значение, указанное в аргументе ключевого слова по умолчанию.

get_file

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

get_thumbnail

Возвращает двоичные данные для эскиза изображения.

has_exif

Логическое значение, которое возвращает True, если изображение в настоящее время содержит метаданные EXIF.

set(attribute, value)

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

В этой статье мы рассмотрим большинство из этих свойств и методов.

Получение основной информации о фотографиях

Ответим на первый вопрос об этих фотографиях. Были ли они сняты на одно устройство или на разные? У нас уже есть основания полагать, что ответ нет.

Марка и модель устройства, на котором было сделано фото.

У обоих изображений есть теги марки и модели, поэтому давайте с их помощью определим, какие устройства использовались для создания этих снимков:

for index, image in enumerate(images):    print(f"Device information - Image {index}")    print("----------------------------")    print(f"Make: {image.make}")    print(f"Model: {image.model}\n")

Вот результат кода выше:

Device information - Image 0----------------------------Make: motorolaModel: motorola one hyperDevice information - Image 1----------------------------Make: AppleModel: iPhone 12 Pro

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

Дополнительная информация об устройствах

Давайте соберём дополнительную информацию об устройствах, которые используются для фотосъёмки, а именно об их объективах и версиях операционной системы.

Не все устройства сообщают тип линзы в своих EXIF-метаданных, поэтому мы будем использовать метод Image get(), который аналогичен методу get(), используемому в Python. Подобно методу get() в Python, метод get(), предоставляемый объектом exif Image, изящно обрабатывает случай, когда данный ключ не существует.

В приведённом ниже коде используется get(), чтобы попытаться получить доступ к версиям объектива и операционной системы, используемым при съёмке фотографий. Если определённого свойства не существует, его значение будет отображаться как Неизвестно:

for index, image in enumerate(images):    print(f"Lens and OS - Image {index}")    print("---------------------")    print(f"Lens make: {image.get('lens_make', 'Unknown')}")    print(f"Lens model: {image.get('lens_model', 'Unknown')}")    print(f"Lens specification: {image.get('lens_specification', 'Unknown')}")    print(f"OS version: {image.get('software', 'Unknown')}\n")

Вот его результат:

Lens and OS - Image 0---------------------Lens make: UnknownLens model: UnknownLens specification: UnknownOS version: UnknownLens and OS - Image 1---------------------Lens make: AppleLens model: iPhone 12 Pro back triple camera 4.2mm f/1.6Lens specification: (1.5399999618512084, 6.0, 1.6, 2.4)OS version: 14.3

Обратите внимание, что телефон, используемый для съёмки изображения 0 (Motorola One Hyper), не предоставляет свойства lens_make, lens_model, lens_specification или software. Если бы мы попытались получить к ним доступ напрямую (например, image.lens_make), результатом была бы ошибка. Метод get() позволил нам предоставить альтернативное значение для этих несуществующих свойств.

Дата и время, когда была сделана фотография

Следующий вопрос: какое фото было сделано первым?

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

Некоторые телефоны также записывают свойство offset_time, которое мы можем использовать для определения смещения datetime относительно UTC.

Следующий код отображает дату и время, когда была сделана каждая из фотографий. Если фотография включает данные о временном сдвиге, код также отобразит это значение:

for index, image in enumerate(images):    print(f"Date/time taken - Image {index}")    print("-------------------------")    print(f"{image.datetime_original}.{image.subsec_time_original} {image.get('offset_time', '')}\n")

Вот результаты:

Date/time taken - Image 0-------------------------2021:01:22 15:08:46.327211 Date/time taken - Image 1-------------------------2021:01:22 15:08:59.383 -05:00

Как видно из вывода, изображение 0 было снято первым, а изображение 1 было снято через 13 секунд.

Определение места, где была сделана фотография

В этом разделе мы рассмотрим доступ к GPS-координатам в метаданных фотографии, форматирование этих координат и упрощение понимания этих координат путём их размещения на карте и преобразования их в названия страны, региона и города, где фото было сделано.

Получение GPS-координат фотографии

Формат EXIF определяет ряд тегов, начинающихся с gps, которые содержат полезную информацию о геолокации, включая широту и долготу, где была сделана фотография:

for index, image in enumerate(images):    print(f"Coordinates - Image {index}")    print("---------------------")    print(f"Latitude: {image.gps_latitude} {image.gps_latitude_ref}")    print(f"Longitude: {image.gps_longitude} {image.gps_longitude_ref}\n")

Вот результат кода выше:

Coordinates - Image 0---------------------Latitude: (28.0, 0.0, 1.56) NLongitude: (82.0, 26.0, 59.04) WCoordinates - Image 1---------------------Latitude: (28.0, 0.0, 1.54) NLongitude: (82.0, 26.0, 58.75) W

Обратите внимание, что свойства gps_latitude и gps_longitude возвращают широту и долготу в виде кортежа из трёх значений:

  1. Градусы.

  2. Минуты (1/60 градуса).

  3. Секунды (1/60 минуты или 1/3600 градуса).

Широта определяет угловое расстояние от экватора, которое может быть северным или южным. Свойство gps_latitude_ref указывает это направление, которое может быть N или S.

Долгота определяет угловое расстояние от меридиана, которое может быть восточным или западным. Свойство gps_longitude_ref указывает это направление: E или W.

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

Форматирование широты и долготы

Давайте определим пару функций для форматирования информации о широте и долготе, возвращаемой Image, в стандартные форматы:

  • Градусы, минуты и секунды. В этом формате широта изображения 0 будет записана как 28.0 0.0 '1.56 "N.

  • Десятичные градусы. В этом формате широта изображения 0 будет записана как 28,000433333333334. Северные широты и восточные долготы представлены положительными значениями, в то время как южные широты и западные долготы представлены отрицательными значениями.

Вот определения этих функций, а также некоторый код, который их использует:

def format_dms_coordinates(coordinates):    return f"{coordinates[0]} {coordinates[1]}\' {coordinates[2]}\""def dms_coordinates_to_dd_coordinates(coordinates, coordinates_ref):    decimal_degrees = coordinates[0] + \                      coordinates[1] / 60 + \                      coordinates[2] / 3600        if coordinates_ref == "S" or coordinates_ref == "W":        decimal_degrees = -decimal_degrees        return decimal_degreesfor index, image in enumerate(images):    print(f"Coordinates - Image {index}")    print("---------------------")    print(f"Latitude (DMS): {format_dms_coordinates(image.gps_latitude)} {image.gps_latitude_ref}")    print(f"Longitude (DMS): {format_dms_coordinates(image.gps_longitude)} {image.gps_longitude_ref}\n")    print(f"Latitude (DD): {dms_coordinates_to_dd_coordinates(image.gps_latitude, image.gps_latitude_ref)}")    print(f"Longitude (DD): {dms_coordinates_to_dd_coordinates(image.gps_longitude, image.gps_longitude_ref)}\n")

Вот результат:

Coordinates - Image 0---------------------Latitude (DMS): 28.0 0.0' 1.56" NLongitude (DMS): 82.0 26.0' 59.04" WLatitude (DD): 28.000433333333334Longitude (DD): -82.44973333333334Coordinates - Image 1---------------------Latitude (DMS): 28.0 0.0' 1.54" NLongitude (DMS): 82.0 26.0' 58.75" WLatitude (DD): 28.000427777777777Longitude (DD): -82.44965277777779

Отображение местоположения фотографий на карте

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

Один из способов использовать встроенный в Python модуль webbrowser для открытия новой вкладки браузера для каждой фотографии, используя десятичную версию EXIF-координат каждой фотографии в качестве параметров URL-адреса Google Maps. Мы создадим служебную функцию с именем draw_map_for_location(), которая сделает следующее:

def draw_map_for_location(latitude, latitude_ref, longitude, longitude_ref):    import webbrowser        decimal_latitude = dms_coordinates_to_dd_coordinates(latitude, latitude_ref)    decimal_longitude = dms_coordinates_to_dd_coordinates(longitude, longitude_ref)    url = f"https://www.google.com/maps?q={decimal_latitude},{decimal_longitude}"    webbrowser.open_new_tab(url)for index, image in enumerate(images):    draw_map_for_location(image.gps_latitude,                           image.gps_latitude_ref,                           image.gps_longitude,                          image.gps_longitude_ref)

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

Отображение города, региона и страны, где был сделан снимок

Другой способ отобразить местоположение фотографии использовать обратное геокодирование, то есть процесс преобразования географических координат в адрес или название места. Для этого нам нужно установить два модуля Python:

  • reverse_geocoder простой автономный обратный геокодер, который использует внутренние таблицы для преобразования набора координат в набор названий городов и штатов/провинций и кодов стран. Установите его, введя pip install reverse_geocoder в командной строке.

  • pycountry утилита поиска стран, которую мы будем использовать для преобразования кодов стран в их соответствующие названия. Установите это, введя pip install pycountry в командной строке.

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

import reverse_geocoder as rgimport pycountryfor index, image in enumerate(images):    print(f"Location info - Image {index}")    print("-----------------------")    decimal_latitude = dms_coordinates_to_dd_coordinates(image.gps_latitude, image.gps_latitude_ref)    decimal_longitude = dms_coordinates_to_dd_coordinates(image.gps_longitude, image.gps_longitude_ref)    coordinates = (decimal_latitude, decimal_longitude)    location_info = rg.search(coordinates)[0]    location_info['country'] = pycountry.countries.get(alpha_2=location_info['cc'])    print(f"{location_info}\n")

В коде используется метод search() из reverse_geocoder для преобразования десятичных значений широты и долготы каждой фотографии в набор следующей информации:

  • Название города, посёлка или деревни.

  • Главный административный регион, который обычно представляет собой штат или провинцию.

  • Незначительный административный район, который обычно является округом или районом.

  • Код страны.

Затем он использует метод get() из pycountry для преобразования кода страны, предоставленного reverse_geocoder, в кортеж, содержащий соответствующие общие и официальные названия стран.

Вот его результат:

Location info - Image 0-----------------------{'lat': '27.94752', 'lon': '-82.45843', 'name': 'Tampa', 'admin1': 'Florida', 'admin2': 'Hillsborough County', 'cc': 'US', 'country': Country(alpha_2='US', alpha_3='USA', name='United States', numeric='840', official_name='United States of America')}Location info - Image 1-----------------------{'lat': '27.94752', 'lon': '-82.45843', 'name': 'Tampa', 'admin1': 'Florida', 'admin2': 'Hillsborough County', 'cc': 'US', 'country': Country(alpha_2='US', alpha_3='USA', name='United States', numeric='840', official_name='United States of America')}

Другая полезная информация о датчиках

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

В каком направлении была обращена камера?

Магнитометр воспринимает магнитные поля, в том числе создаваемые Землёй. Его основная цель быть компасом телефона и определять направление, на которое он указывает. Эта информация записывается в EXIF в виде компасного курса каждый раз, когда вы делаете снимок.

Давайте определим, в каком направлении я смотрел, когда делал каждую из этих фотографий:


Мы будем использовать следующие свойства exif-Image, чтобы определить направление, в котором была направлена камера:

  • gps_img_direction: направление по компасу, то есть направление, в котором смотрела камера, когда был сделан снимок, выраженный в десятичных градусах. 0 север, 90 восток, 180 юг и 270 запад.

  • gps_img_direction_ref: контрольная точка для gps_img_direction. Это может быть либо T, что означает, что 0 относится к истинному или географическому северу, либо M, что означает, что 0 относится к магнитному северу. В большинстве случаев используется истинный север.

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

  • degrees_to_direction(): эта функция преобразует направления по компасу в основные направления (например, N, NE, NNE и т. д.);

  • format_direction_ref(): эта функция превращает значение в gps_img_direction_ref понятную для человека строку.

def degrees_to_direction(degrees):    COMPASS_DIRECTIONS = [        "N",        "NNE",        "NE",        "ENE",        "E",         "ESE",         "SE",         "SSE",        "S",         "SSW",         "SW",         "WSW",         "W",         "WNW",         "NW",         "NNW"    ]        compass_directions_count = len(COMPASS_DIRECTIONS)    compass_direction_arc = 360 / compass_directions_count    return COMPASS_DIRECTIONS[int(degrees / compass_direction_arc) % compass_directions_count]def format_direction_ref(direction_ref):    direction_ref_text = "(true or magnetic north not specified)"    if direction_ref == "T":        direction_ref_text = "True north"    elif direction_ref == "M":        direction_ref_text = "Magnetic north"    return direction_ref_text# Import imageslake_images = []for i in range(1, 5):    filename = f"lake {i}.jpg"    with open(f"./images/{filename}", "rb") as current_file:        lake_images.append(Image(current_file))# Display camera direction for each imagefor index, image in enumerate(lake_images):    print(f"Image direction - Image {index}")    print("-------------------------")    print(f"Image direction: {degrees_to_direction(image.gps_img_direction)} ({image.gps_img_direction})")    print(f"Image direction ref: {format_direction_ref(image.gps_img_direction_ref)}\n")

Когда вы запустите код, вы увидите следующий результат:

Image direction - Image 0-------------------------Image direction: ENE (78.416259765625)Image direction ref: True northImage direction - Image 1-------------------------Image direction: N (1.174224853515625)Image direction ref: True northImage direction - Image 2-------------------------Image direction: SSE (178.46739196870607)Image direction ref: True northImage direction - Image 3-------------------------Image direction: W (273.8248136315229)Image direction ref: True north

На какой высоте был сделан снимок?

В дополнение к предоставлению координат местоположения GPS также может использоваться для определения высоты. Некоторые смартфоны оснащены барометрами (которые определяют давление воздуха), которые они используют для повышения точности измерения высоты.

Давайте выясним, на каких высотах были сделаны эти фотографии:

Мы воспользуемся этими свойствами объекта Exif Image для определения высоты:

  • gps_altitude: высота в метрах;

  • gps_altitude_ref: контрольная точка для gps_altitude. Это значение равно 0, что означает, что значение в gps_altitude относится к метрам над уровнем моря, или 1, что означает, что значение в gps_altitude относится к метрам ниже уровня моря.

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

def format_altitude(altitude, altitude_ref):    altitude_ref_text = "(above or below sea level not specified)"    if altitude_ref == 0:        altitude_ref_text = "above sea level"    elif altitude_ref == 1:        altitude_ref_text = "below sea level"    return f"{altitude} meters {altitude_ref_text}"# Import imagesaltitude_images = []for i in range(1, 3):    filename = f"altitude {i}.jpg"    with open(f"./images/{filename}", "rb") as current_file:        altitude_images.append(Image(current_file))        # Display camera altitude for each imagefor index, image in enumerate(altitude_images):    print(f"Altitude - Image {index}")    print( "------------------")    print(f"{format_altitude(image.gps_altitude, image.gps_altitude_ref)}\n")

Вот результат:

Altitude - Image 0------------------14.025835763206075 meters above sea levelAltitude - Image 1------------------359.13079847908745 meters above sea level

Я мог бы сказать вам, где я сделал эти фотографии, но вы уже знаете, как это выяснить.

Двигался ли фотограф?

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

Мой Motorola One Hyper не записывает метаданные, связанные со скоростью, но мой iPhone это делает. Доступ к этим данным можно получить с помощью этих двух методов объекта exif Image:

  • gps_speed: скорость, выраженная в виде числа;

  • gps_speed_ref: единицы скорости, используемые для значения в gps_speed. Это значение может быть: K для километров в час; M для миль в час; N для морских миль в час, или узлов.

Рассмотрим следующие фото:

Вот код, который выводит записанную скорость во время съёмки каждой фотографии. Он включает функцию универсальности, format_speed_ref(), которая определяет единицы сообщаемой скорости:

def format_speed_ref(speed_ref):    speed_ref_text = "(speed units not specified)"    if speed_ref == "K":        speed_ref_text = "km/h"    elif speed_ref == "M":        speed_ref_text = "mph"    elif speed_ref == "N":        speed_ref_text = "knots"    return speed_ref_text# Import imagesspeed_images = []for i in range(1, 4):    filename = f"speed {i}.jpg"    with open(f"./images/speed {i}.jpg", "rb") as current_file:        speed_images.append(Image(current_file))    for index, image in enumerate(speed_images):    print(f"Speed - Image {index}")    print("---------------")    print(f"Speed: {image.gps_speed} {format_speed_ref(image.gps_speed_ref)}\n")

Вот результат:

Speed - Image 0---------------Speed: 0.0 km/hSpeed - Image 1---------------Speed: 20.19736291335287 km/hSpeed - Image 2---------------Speed: 5.520932607215793 km/h

Редактирование EXIF данных и их сохранение

Пока что мы ограничились простым чтением метаданных EXIF с фотографий. Следующим шагом является внесение изменений в эти данные, а затем сохранение результатов в виде нового файла фотографии.

Редактирование координат фотографии

Начнём с этой фотографии:

К настоящему времени вы должны знать, как читать координаты из EXIF и использовать эти координаты, чтобы открыть карту Google, чтобы показать местоположение, где фото снято.

Вот код, который это сделает. Он использует служебную функцию draw_map_for_location(), которую мы определили ранее:

with open(f"./images/hotel original.jpg", "rb") as hotel_file:    hotel_image = Image(hotel_file)    # Read the GPS dataprint("Original coordinates")print("--------------------")print(f"Latitude: {hotel_image.gps_latitude} {hotel_image.gps_latitude_ref}")print(f"Longitude: {hotel_image.gps_longitude} {hotel_image.gps_longitude_ref}\n")# Open a Google Map showing the location represented by these coordinatesdraw_map_for_location(hotel_image.gps_latitude,                      hotel_image.gps_latitude_ref,                      hotel_image.gps_longitude,                      hotel_image.gps_longitude_ref

Он выводит следующее:

Original coordinates--------------------Latitude: (28.0, 21.0, 58.44) NLongitude: (81.0, 33.0, 34.29) W

Кроме того, он открывает новую вкладку браузера, показывающую карту Google, которая отображает отели Swan и Dolphin, которые находятся в нескольких минутах ходьбы от Walt Disney World во Флориде.

Давайте сделаем вещи немного интереснее, изменив координаты, встроенные в данные EXIF фотографии, чтобы они сообщали, что она была сделана в Зоне 51. Если вы не слышали об этом месте, это военный объект в Неваде, в котором, как считают теоретики теории заговора, правительство США хранит тела пришельцев и космический корабль, захваченные в 1950-х годах. Его координаты: 37,0 14 '3,6 "северной широты, 115 48' 23,99" западной долготы.

Вы видели, что чтение значения тега EXIF с использованием объекта Exif Image это просто чтение значения в соответствующем свойстве. Точно так же редактирование его значения это просто вопрос присвоения нового значения этому свойству. В этом случае мы присвоим значения, определяющие координаты Зоны 51, свойствам изображения gps_latitude, gps_latitude_ref, gps_longitude и gps_longitude_ref:

# Boring. Let's change those coordinates to Area 51!hotel_image.gps_latitude = (37.0, 14, 3.6)hotel_image.gps_latitude_ref = 'N'hotel_image.gps_longitude = (115, 48, 23.99)hotel_image.gps_longitude_ref = 'W'# Read the revised GPS dataprint("Revised coordinates")print("-------------------")print(f"Latitude: {hotel_image.gps_latitude} {hotel_image.gps_latitude_ref}")print(f"Longitude: {hotel_image.gps_longitude} {hotel_image.gps_longitude_ref}\n")# Open a Google Map showing the location represented by the revised coordinatesdraw_map_for_location(hotel_image.gps_latitude,                      hotel_image.gps_latitude_ref,                      hotel_image.gps_longitude,                      hotel_image.gps_longitude_ref

Выполнение кода приведёт к следующему результату

Revised coordinates-------------------Latitude: (37.0, 14.0, 3.6) NLongitude: (115.0, 48.0, 23.99) W

... и он откроет новую вкладку с картой Google, в которой отображается Зона 51.

Заполнение неиспользуемых тегов EXIF

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

Примечание: модуль exif записывает только теги в спецификации EXIF, но не дополнительные теги, включённые поставщиками. Официальные теги в спецификации EXIF подчёркнуты.

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

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

  • ImageDescription: описание фотографии. В объектах Exif Image это отображается как свойство image_description.

  • Copyright: уведомление об авторских правах на фотографию. В объектах Exif Image это свойство copyright.

Вот код, который заполняет эти теги:

hotel_image.image_description = "The Dolphin Hotel in Orlando, viewed at sunset from the Swan Hotel"hotel_image.copyright = "Copyright 2021 (Your name here)"print(f"Description: {hotel_image.image_description}")print(f"Copyright: {hotel_image.copyright}")

Выполнение кода даёт следующий результат:

Description: The Dolphin Hotel in Orlando, viewed at sunset from the Swan HotelCopyright: Copyright 2021 (Your name here)

Сохранение фото с обновленными данными

Теперь, когда мы отредактировали данные EXIF на картинке, давайте сохраним их как новый файл с названием hotel_updated.jpg.

with open('./images/hotel updated.jpg', 'wb') as updated_hotel_file:    updated_hotel_file.write(hotel_image.get_file())

Этот код создаёт файловый объект updated_hotel_file для записи двоичных данных в файл с именем hotel_updated.jpg. Затем он использует метод Image get_file() для получения данных об изображении отеля в сериализуемой форме и записывает эти данные в файл.

Теперь у вас должен быть новый файл: hotel_updated.jpg. Загрузим его и убедимся, что изменённые и добавленные данные сохранены:

with open(f"./images/hotel updated.jpg", "rb") as hotel_file:    hotel_image = Image(hotel_file)    print("Coordinates")print("-----------")print(f"Latitude: {hotel_image.gps_latitude} {hotel_image.gps_latitude_ref}")print(f"Longitude: {hotel_image.gps_longitude} {hotel_image.gps_longitude_ref}\n")print("Other info")print("----------")print(f"Description: {hotel_image.image_description}")print(f"Copyright: {hotel_image.copyright}")# Open a Google Map showing the location represented by these coordinatesdraw_map_for_location(hotel_image.gps_latitude,                      hotel_image.gps_latitude_ref,                      hotel_image.gps_longitude,                      hotel_image.gps_longitude_ref

Код выдаст вам этот результат

Coordinates-----------Latitude: (37.0, 14.0, 3.6) NLongitude: (115.0, 48.0, 23.99) WOther info----------Description: The Dolphin Hotel in Orlando, viewed at sunset from the Swan HotelCopyright: Copyright 2021 (Your name here)

... и откроется ещё одна вкладка браузера, отображающая Зону 51 на карте.

Удаление данных EXIF и сохранение очищенной фотографии

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

Первый способ использовать метод delete(), предоставляемый объектом Exif-Image. Давайте используем его, чтобы удалить данные о широте:

hotel_image.delete('gps_latitude')hotel_image.delete('gps_latitude_ref')print("Latitude data")print("-------------")print(f"gps_latitude: {hotel_image.get('gps_latitude', 'Not found')}")print(f"gps_latitude_ref: {hotel_image.get('gps_latitude_ref', 'Not found')}")

Вот результат кода выше:

Latitude data-------------gps_latitude: Not foundgps_latitude_ref: Not found

Второй способ использовать оператор Python del, который удаляет объекты из памяти. В этом случае мы будем использовать его для удаления атрибутов hotel_image, в которых хранятся данные о долготе:

del hotel_image.gps_longitudedel hotel_image.gps_longitude_refprint("Longitude data")print("--------------")print(f"gps_longitude: {hotel_image.get('gps_longitude', 'Not found')}")print(f"gps_longitude_ref: {hotel_image.get('gps_longitude_ref', 'Not found')}")

Вот результат кода выше:

Longitude data--------------gps_longitude: Not foundgps_longitude_ref: Not found

Теперь, когда мы удалили данные о местоположении с фотографии, давайте сохраним её под новым именем, hotel_without_location_data.jpg:

with open('./images/hotel without location data.jpg', 'wb') as updated_hotel_file:    updated_hotel_file.write(hotel_image.get_file())

Наконец, если вы хотите просто удалить все данные EXIF с фотографии, вы можете использовать метод delete_all() объекта exif Image:

hotel_image.delete_all()dir(hotel_image)
['<unknown EXIF tag 322>', '<unknown EXIF tag 323>', '_segments', 'delete', 'delete_all', 'get', 'get_file', 'get_thumbnail', 'has_exif']

Опять же, как только вы использовали delete_all() для удаления всех тегов EXIF, вам нужно сохранить эти изменения. Приведённый ниже код сохраняет наше изображение со всеми удалёнными тегами как hotel_without_tags.jpg:

with open('./images/hotel without tags.jpg', 'wb') as updated_hotel_file:    updated_hotel_file.write(hotel_image.get_file())

Практические и технические соображения

Причины удаления метаданных EXIF

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

Одно из очевидных соображений это конфиденциальность. Метаданные GPS для отдельной фотографии могут определить ваше местоположение в определённую дату и время. Что гораздо более ценно, так это совокупный GPS из чьей-то камеры, поскольку он содержит шаблоны, которые можно проанализировать, чтобы определить, где вы живёте, работаете и какие места вы часто посещаете в повседневной жизни. Вот почему более авторитетные социальные сети удаляют эту информацию, когда вы публикуете свои фотографии в Интернете. Помните, что это означает лишь то, что ваши фотографии в галерее социальной сети не содержат данных GPS. Нет гарантии, что служба не записала информацию о местоположении чтобы передать её рекламодателю перед её удалением.

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

Некоторые фотографы больше озабочены секретностью, чем конфиденциальностью, когда речь идёт о метаданных их фотографий. Они не хотят разглашать настройки камеры, которые использовали для фотосъёмки.

Причины редактирования или добавления метаданных EXIF

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

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

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

Другие вещи, которые следует учитывать

Как мы видели в различных тегах, записанных устройствами iPhone и Android, используемыми для фотосъёмки в этой статье, разные камеры записывают разные наборы тегов EXIF. Программное обеспечение для редактирования фотографий часто записывает информацию в разные теги или добавляет свои собственные теги. Эти дополнительные метаданные часто могут указывать на то, что фотография была отредактирована.

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

Наконец, есть сообщения, которые отображаются в мобильных приложениях, использующих камеру, при первом использовании. Обычно они говорят что-то вроде Это приложение использует камеру. Вы согласны? , Но большинство из них не даёт понять, что камера добавляет местоположение и другие метаданные к каждой сделанной фотографии. Если вы пишете мобильные приложения, использующие камеру, вы можете сообщить об этом пользователю.

Знание Python это полезный hard skill не только для программиста, но и для аналитика и даже маркетолога. С ним вы можете сами написать простой парсер, написать небольшое приложение под свои задачи и даже увеличить свой доход, выполняя заказы на фрилансе. Если вы хотите дополнительно прокачать себя и освоить этот язык под чутким руководством наших опытных менторов обратите внимание на курс Fullstack-разработчик на Python и Python для веб-разработки.

Узнайте, как прокачаться в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

На репите повторяющиеся музыкальные треки помогают сконцентрироваться что можно послушать

20.06.2021 10:05:00 | Автор: admin

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

Фотография: Roman Synkevych. Источник: Unsplash.comФотография: Roman Synkevych. Источник: Unsplash.com

Дверь в состояние потока

Наша пропускная способность с точки зрения восприятия и работы нервной системы имеет ограничения. Когда мы сконцентрированы, организм направляет весь этот ресурс на решение конкретной задачи, и работа спорится. Один из эффективных способов добиться такого эффекта включить любимый трек и прослушивать его снова и снова во время выполнения того или иного действия. По словам психолога Элизабет Маргулис (Elizabeth Margulis), однообразность акустического фона не позволит разуму отвлекаться.

Дело в том, что наш мозг достаточно быстро привыкает к повторяющемуся паттерну и через какое-то время перестает его замечать в такой момент мы чуточку больше фокусируемся на важных вещах, которыми заняты под муз. сопровождение. Этот эффект еще в 2004 году описал Джефф Хокинс в своей книге Об интеллекте, посвященной теории работы мозга.

Жанр трека, играющего на повторе, также может влиять на подход к работе. Хотя стоит признать, что этот момент по большей части зависит от предпочтений слушателя. Так или иначе, многие программисты, предприниматели и дизайнеры зацикливают любимые треки, решая важные задачи. Далее мы обсудим, что они рекомендуют для прослушивания на репите.

Парочка вариантов для прослушивания

Основатель системы управления контентом WordPress Мэтт Мулленвег часто ставит музыку на повтор, когда программирует. Её он выбирает из своего плейлиста там есть работы как джазовых исполнителей вроде Декстера Гордона и Сонни Роллинза, так и хип-хоп треки от Jay-Z и Бейонсе. Аналогичного подхода придерживаются некоторые писатели и журналисты за время работы они могут прослушать одну и ту же песню 300400 раз.

Если перспектива крутить единственный трек вас не прельщает, на Hacker News рекомендуют включать на репите альбомы. Например, Dreamweapon: An Evening of Contemporary Sitar Music от британской альтернативной рок-группы Spacemen 3. Его легко слушать, так как он состоит из ненавязчивых ритмов и предсказуемых акустических решений.

Фотография: Garett Mizunaka. Источник: Unsplash.comФотография: Garett Mizunaka. Источник: Unsplash.com

В том случае, если подобная музыка вас отвлекает, а слова в любимых песнях не дают сосредоточиться, попробуйте обратить внимание на саундтреки к фильмам и видеоиграм. Их задача помочь игроку погрузиться в происходящее на экране, особенно если речь идет о музыке с мягким и спокойным звучанием вроде Lo-Fi. Сотрудники ИБ-компании Bishop Fox особеннорекомендуютподборки изстарых игрNintendo, выполненные в этом жанре. Конечно же, в тематических обсуждениях упоминают и саундтреки из линейки игр GTA.

Разумеется, какого-то секретного способа для подбора подходящего фона нет все зависит исключительно от ваших предпочтений. Однако Даг Белшоу, один из авторов обучающего проекта Web-грамотность в Mozilla, выделил три правила, на которые он ориентируется при выборе трека для прослушивания на повторе: а) существенная длительность (от трех минут); б) от 100 до 120 BPM (например, в эту категорию попадает Stayin Alive от Bee Gees); в) отсутствие вокальной составляющей. По словам Дага, ему нравится работать под трек Arcadia в исполнении немецкого музыканта Apparat. С таким фоном, он чувствует себя наиболее продуктивным.

А что играет на повторе у вас? Если будет такое желание, поделитесь в комментариях.


Больше материалов по теме в нашем Мире Hi-Fi:


Подробнее..

Просто возьми интеграл

17.06.2021 18:06:38 | Автор: admin

Как учить английские слова: неочевидное о вероятном

На вопрос Как учить слова? мы чаще всего получаем в ответ указание, где их взять, эти слова, и какие именно слова надо учить. Например, из сериалов и клише разговорной речи. На вопрос Как? еще отвечают: повторяй вслух за героями сериала, делай карточки в квизлете, лепи стикеры со словами на стену, приделывай суффикс - tion, придумывай ассоциации к английским словам. Наконец, самый любимый ответ препода - просто возьми и выучи.

Просто возьми интеграл! Просто возьми интеграл, просто почини мой компьютер, просто построй дом, запусти ракету на земную орбиту... Вот тебе список и перевод, учи.

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

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

Возьмём за основу утверждение: рассказать о 100 фразах не равно научить 100 фразам. Равно как и прочитать 100 слов не равно выучить 100 слов. Вот так просто, да. Не равно, не равно, не равно - даже если очень хочется.

Для того, чтобы понимать, как что-то делается, надо определить границы этого самого что-то. Предлагаем небольшой тест, который поможет в этом нелегком деле. Что значит выучить слово? Варианты ответа:

а) знать, что слово значит

б) знать, как слово употреблять

в) узнавать слово в речи/на письме

г) активно слово употреблять

д) всё из вышеперечисленного

Не важно, что думают преподы (как правило под выучить всё же подразумевается вариант д), важно, что очень часто заинтересованным в расширении своего словарного запаса людям предлагают только а. Реже говорят о б или в, практически никогда о ги тем более - д.

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

Если согласиться с тем, что выучить познакомиться, то дышать становится легче. Следующий логичный вопрос: а как учить-то? Отложите копировальные машинки, лайфхаки будут позже и совсем немного, потому как мы считаем, что карточки, приложения и иные хитрые способы мало что значат без осмысленного подхода. Мы пойдём другим путём.

Самое первое - и оно же самое неочевидное, на наш взгляд: изучение слов - это не цель, и никакого результата тут нет и быть не может. Иностранный язык всегда найдёт способ дать поддых, ведь он учится быстрее, лечится лучше и проживёт дольше любого человека, на нём говорящего, носитель он там или не носитель.

Из этого тезиса следуют два важных момента:

  1. 100 слов за месяц и всё такое прочее - не работает

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

Наша мысль такая: когда мы учим слова, мы работаем НЕ на результат. Если мы выучим 10 слов и больше никогда к ним не вернемся, то мы их забудем. Если мы вернемся к забытым словам, напереворачиваем с ними карточек, а потом опять забросим - мы мало что удержим в памяти. Для нашего мозга - забытые слова всё равно что абырвалг, он их не воспринимает как смыслы. Абырвалг, мутабор, consciousness - мозгу всё едино. Поэтому отбросим всякую надежду на результат. А что же останется? Процесс.

Вот именно это и есть изучение слов - это фон, долгоиграющий проект, одним словом - процесс, притом процесс нелинейный. Вы регулярно погружаетесь в некое облако слов, которое - внимание! - создаёте себе сами. Тут найдут себе применение всевозможные приложения и платформы с карточками и упражнениями, типа Quizlet, Anki, Brainscape -их много. Важно: создание облака - дело рук изучающего. Ничего, что делается бесплатно и/или кем-то другим, вам не пригодится и пользы не принесёт. Сами, всё сами.

А как же система? А её нет. У облака нет границ, структуры и определённости. Напоминаем: у вас нет цели, нет результата, которого надо достичь, есть только процесс и регулярное повторение практически одного и того же. Вы учите те слова, которые вам необходимы, нравятся, важны - нужное подчеркнуть, причём учите каждый день. Ну ладно, можно через день.

Понятно, что при таком подходе ни о каких 100 словах за месяц не может быть и речи. Но зато не нужно заучивать наизусть! Писать диктанты, пытаться запомнить всё до последней буковки - это тоже разрешите себе не делать. Позвольте себе забывать слова, догадываться об их значениях без точного перевода и расширять таким образом пассивный словарный запас. Вы, кстати, знали, что активный словарь - это только 10% от всей массы слов в нашем запасе?

Такой подход ещё и обеспечит гибкость в выборе техник. Читать любимую литературу и выделять интересные слова? Да, заверните два. Запоминать слова тематически, ассоциативно, при помощи стикеров на предметах обихода? Легко. Создавать языковые сценарии взаимодействия с миром и населяющими его индивидами? Без проблем. Попробуйте все известные вам техники, выкиньте на помойку те, что вам не подходят, и учите так, как удобно вам.

А по сколько слов в месяц - 10, 16, 20, 36, 38, 40 или 158 - это абсолютно не важно.

Одно из чисел в предыдущем предложении - это количество грамматических времён по версии Майкла Халлидея, "Introduction to Functional Grammar".

Подробнее..

Правила удалённой работы навсе времена (восемьштук)

14.09.2020 16:14:47 | Автор: admin

Google, Яндекс, а вслед за ними и другие компании изо всех сил стараются сделать офисы удобными и уютными, а процессы простыми и понятными. И дело не в том, что им некуда девать деньги, просто чтобы люди были эффективны, нужно продумать рабочее пространство и организовать всё так, чтобы сотрудники не уставали, могли легко сосредоточиться, меньше времени проводили на больничных и больше в офисе. И если у IT-компаний получается делать классные рабочие места и настраивать процессы для тысяч людей сразу, получится и у вас для себя лично. Сделать удаленную работу из дома комфортной не так уж и сложно, если учесть некоторые штуки, о которых нам рассказали опытные ребята: врачи, эйчары и удаленщики со стажем. Начнем с самых основ, а закончим тонкой настройкой.

1. Найдите хорошие стол и стул

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

  • Найдите кресло с хорошей анатомической спинкой с поддержкой поясницы. Отрегулируйте высоту сиденья так, чтобы его край не давил на ноги. Поясничный валик тоже настройте так, чтобы опираться на него спиной, а не держать ее постоянно зажатой. Если в кресле нет встроенной поддержки, хотя бы сверните валик из полотенца это лучше чем ничего.Вот неплохие кресла в порядке убывания цены: Herman Miller, Chairman, Метта, Kulik System, TetChair, Trendlines, Алвест, Nowy Stul.

  • Высота и наклон стола тоже важны, поэтому если у вас стол с регулировкамиэто прекрасно. Если нет, помните, что край стола не должен пережимать кровоток в предплечьях, а кисти во время работы по-хорошему должны находиться ниже локтей.Ключевые слова по столам: Tabula Sense (дорогие) и Икея (в среднем бюджетные).

2. Делайте перерывы

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

Системы национального здравоохранения стран первого мира сходятся в одном: все говорят о гиподинамии офисных работников. Проще говоря, главная проблема современностив длительном поддержании статического положения тела, которое ведет к огромному количеству заболеваний. А вам же не нужны заболевания, правда? Поэтому делайте перерывы!

Если хотите подробностей, на Хабре есть пост, где ортопед и массажистка отвечали на главные вопросы про осанку.

3. Постарайтесь придерживаться распорядка дня

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

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

Чтобы привыкнуть к регулярным паузам в работе из пункта выше, попробуйте помидорный таймер: 25 минут работаешь, 5 минут отдыхаешь. Поначалу это непривычно и включаться в работу после перерыва приходится долго, но со временем вы это станет естественным.

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

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

4. Объясните свой график родным

Когда дома есть свой кабинетэто идеальный вариант.Но, будем честны, в российских квартирах их устраивают редко. Поэтому лучше договориться с домочадцами заранее о том, что, скажем, с 10 до 18 вы только работаете и лучше вас не тревожить. Детям сложнее объяснить, что папа или мама заняты, хотя и находятся в том же помещении. Если ваша работа позволяет, для них можно выделить немного времени в середине дня. К тому же дети быстрее взрослых привыкают к некоторому распорядку. Но если уж пообещали малышу, что будете играть с ним после обеда, постарайтесь не отступать от правила. Об особом распорядке, если такой есть, стоит рассказать и коллегам.

5. Согласуйте график с коллегами

Дома, действительно, могут складываться индивидуальные расписания. Кому-то нужно обязательно выйти проветриться на час в середине дня, чтобы оставаться бодрым. Кто-то выделил особый час для общения с детьми. Другим нужно гулять с собакой. Обо всем этом лучше рассказать коллегам, чтобы они знали, что в это время вас лучше не тревожить.

При этом, конечно, график стоит выстраивать так, чтобы пересекаться с коллегами в онлайне минимум на 45 часов. Главное во всем этом четко выполнять задачи в срок, чтобы у руководителя не возникало вопросов к индивидуальному графику.

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

6. Найдите замену разговорам на кухне

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

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

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

7. Делайте перерывы

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

8. Следите за питанием

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

9. Продумайте связь и софт

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

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


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

P.S. В самом начале карантина мы проводили марафон удаленки. И после него на нашем ютуб-канале остались прикольные семинары. На всякий случай, держите и этот плейлист.

Подробнее..

Как понять, что пришел переломный момент в бизнесе или карьере

23.04.2021 00:07:18 | Автор: admin

В начале карьеры у меня не было проблем c формированием плана развития. Я выбирал релевантные моим целям сертификации, анализировал список тем и вопросов, на основе которых составлял себе план. После получения бизнес образования EMBA, старый проверенный метод перестал работать.На смену пришел длинный список рекомендованной литературы.

Со временем я понял, что недостаточно бездумно "проглатывать" очередную книгу. В идеале - применять полученные знания. Еще лучше - резюмировать, полученный опыт. Так появились статьи на хабре по мотивам книг "Смерть от совещаний" и "Проект Феникс".

Что же делать с теми книгами, которые еще не успел применить? Появилась идея "закреплять" прочитанное в виде саммари основных идей книги. Я решил начать делиться такими саммари с вами. Буду рад, обратной связи и надеюсь, они будут вам полезны.

В этом посте я предлагаю вашему вниманию книгу Энди Гроува, CEO Intel, "Выживают только параноики".

Основная мысль книги: в бизнесе или в карьере рано или поздно наступает "переломный" момент. Если после его наступления не сможете измениться, то скорее всего это будет конец вашей компании и/или карьеры.

Энди Гроув описывает кризис в Intel, когда японские конкуренты "отжимали" у компании рынок. Трагичность ситуации в том, что руководители компании не замечают изменений первыми. Точнее сказать, они узнают о таких изменениях последними. Руководители не видят предвестников изменений. Их замечают люди на "передовой", но не всегда могут "достучаться" до руководителей.

Мои примеры: Японские производители техники и автомобилей "щемили" компании из штатов, Netflix "отжал" рынок у Blockbuster, Viber потеснил Skype, и вместе с другими мессенджерами они убили рынок международных звонков.

Если быть настороже и следить за триггерами изменений, то можно вовремя перестроиться и использовать грядущие изменения себе во благо. Для этого Гроув предлагает использовать технику шести сил и принцип десятикратного усиления.

Важно анализировать шесть сил или направлений:

  • Покупатели - те, для кого ваш продукт или услуга представляет ценность. Даже больше - они готовы за это платить;

  • Поставщики - те, без которых вы не можете производить свой продукт или услугу. Так, поставщиками онлайн кинотеатров будут правообладатели и интернет провайдеры;

  • Внутриотраслевая конкуренция - ваши прямые конкуренты. Они предоставляют аналогичный товар или услугу;

  • Новые конкуренты - тут рассматриваются потенциальные конкуренты, которые могут выйти на рынок. Так, благодаря современным технологиям фрилансер из любой страны может с минимальными затратами составить конкуренцию местным специалистам;

  • Товары-заменители - товары или услуги, которые помогают решить одну и ту же проблему потребителя, но по разному. Общественный транспорт является товаром заменителем для служб такси, а шаурма заменяет бизнес ланч в ресторане;

  • Сопряженные компании - те, кто помогают продавать товар. Их бизнес связан с вашим. Для продавца процессоров - производитель компьютеров.

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

  • У поставщиков появится другой, более выгодный рынок сбыта, и они пересмотрят цены для вас или не смогут обеспечить требуемый вами уровень сервиса;

  • Конкуренты станут и дешевле и лучше одновременно;

  • Покупатели потеряют интерес к вашему товару, потому что появится более интересный им товар-заменитель.

Мало осознать, что грядут изменения, нужно принять решение измениться. Тяжело что-то менять, пока текущая модель все еще приносит вам прибыль. С другой стороны, когда вас "уволит" клиент - будет уже поздно. Велик шанс потерять все, и тогда придется начинать все с нуля. В идеале, нужно меняться еще до того, как настанет "переломный" момент. Тогда старая бизнес модель будет топливом для новой. Чтобы принять непростое решение, попробуйте представить, что вы увольняетесь, а на ваше место приходит новый руководитель. Что бы он сделал?

Гроув описывает "переломный" момент как точка на графике роста вашей компании, в которой вторая производная меняет знак. Иначе говоря, момент, в который организация перестает расти и начинает деградировать.

Как можно понять, что скоро произойдет переломный момент?

Автор предлагает не ждать и налаживать информационные каналы, которые помогут выявить "триггеры" :

  • наладить поставку информации с "передовой" без искажений;

  • иметь в поле зрения компании-конкуренты;

  • отслеживать технологические изменения;

  • следить за изменениями поведения клиентов;

  • плотнее общаться с сопряженными компаниями.

В качестве заключения Энди Гроув предлагает сравнить себя и свою карьеру с компанией. Тут есть над чем задуматься.

Для себя я выбрал следующие информационные каналы:

  • прошу обратную связь у ребят, с которыми работаю;

  • составил к прочтению список автобиографий успешных руководителей;

  • подписался на рассылки открытых вакансий моего профиля, чтобы отслеживать востребованные технологии и бизнес-домены;

  • вступил в сообщество руководителей для обмена опытом.

Какие информационные каналы используете вы?

Подробнее..

Как стать высокооплачиваемым инженером в области электроники

09.04.2021 16:11:40 | Автор: admin

В статье рассмотрены практические советы по тому, как инженер может получить более выгодные условия работы в сфере проектирования электроники.

Статья подготовлена действующим инженером-конструктором 1-й категории АО ИСС Дмитрием Савиным на основе своего проектного и руководящего опыта.

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

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

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

1. Всегда проявляйте разумную инициативу

Многие молодые специалисты, особенно на первых порах, по разным причинам стараются избегать лишней работы. Иногда причиной этому становится банальная лень, но есть ситуации в которых даже не глупый и не ленивый инженер отказывается браться за какую-либо новую работу, так как боится, что не справится с ней. И это в корне неверный подход. Раз и навсегда нужно понять: Ошибаться это нормально; но при этом: Ненормально допускать одни и те же ошибки снова и снова.

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

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

2. Постарайтесь адекватно оценить уровень вашей компетенции

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

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

3. Старайтесь быть универсальным

Схемотехника, конструирование и технология это три опорных столба, на которых держится производство электроники любой сложности. Любой инженер в области электроники должен быть и конструктором, и схемотехником, и технологом, или хотя бы понимать, чем занимаются его коллеги. Это наверное наиболее важный момент, потому что большинство инцидентов происходит из-за банального недопонимания между специалистами в различных подразделениях.

Если вы инженер-конструктор, то вам следует как минимум научиться быстро читать и понимать новые для вас электрические схемы и базовые технологические процессы на производстве, чтобы разговаривать на одном языке с теми, с кем вы, по сути, делаете одно дело.

То же самое относится и к инженеру-схемотехнику, и к инженеру-технологу. Для этого нужно установить контакт с коллегами и узнать с чего начинается их работа, с помощью каких инструментов они её выполняют и в какой срок.

4. Постоянно развивайтесь

В инженерном деле всегда есть к чему стремиться и куда расти. Читайте профессиональную литературу (например Джонсон Г. Грэхэм М Конструирование высокоскоростных цифровых устройств. Начальный курс черной магии или книги по электронике за авторством Кечиева Л.Н.), отраслевые издания (напримерhttps://www.soel.ru/), проходите курсы повышения квалификации.

Не ждите когда информация дойдет до вас самотеком, ищите её сами (см. 1). Полезными источниками информации для инженера электронщика могут быть не только книги и журналы, а так же сайты (напримерhttp://personeltest.ru/aways/habr.com/) и YouTube-каналы.

5. Будьте на волне

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

Ищите людей интересующихся тем же, чем интересуетесь вы, вступайте в сообщества электронщиков (напримерhttps://t.me/Altium_and_electronics), иногда можно открыть для себя что-то абсолютно новое, или помочь кому-нибудь решить проблему, решение которой для вас уже стало очевидным.

6. Перенимайте любой полезный опыт

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

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

7. Выучите английский

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

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

Научитесь правильно использовать онлайн-словари (напримерhttps://www.multitran.com) и переводчики, и ни в коем случае не верьте в то, что они способны выдать вам правильный результат с первого раза, перепроверяйте их работу и сверяйтесь с контекстом.

8. Попробуйте создать устройство своими руками

От идеи до воплощения. Всё зависит от уровня вашей заинтересованности и подготовленности, а наши коллеги из Азии, за символическую плату доставят вам всё что угодно, от готового набора для сборки устройства, до мешка с электрорадиоизделиями. И это позволит вам почувствовать на себе, что такое программирование микроконтроллера, разводка платы, пайка компонентов.

От простых мигалок-гуделок до сложных роботизированных систем, в наше время практически любой человек при желании может попробовать сделать что-нибудь сам, да здравствует эра DIY. И помимо всего прочего это позволит вам стать чуть более развитым и универсальным (см. Советы 3 и 4).

9. Любите свою работу

Если вы искренне заинтересованы не только в том, чтобы вам стабильно повышали оклад, но еще и в том, чтобы ваши устройства работали без сбоев, а планы выполнялись своевременно, то разумеется, это не останется без внимания. Помните Hard work pays off (см. 7).

10. Не бойтесь перемен

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

После пары собеседований вы поймете, чего стоите как специалист, и если новое предложение вас устроит, то при прочих равных, имеет смысл сменить место работы.

Изначально данная статья подготовлена для порталаaltium-u.ruи публикуется с разрешения редакции.

Подробнее..

Категории

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

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru