пʼятниця, 9 травня 2025 р.

Як правильно виконати редірект після Google OAuth 2.0 у Flask (PythonAnywhere)

✅ Як правильно виконати редірект після Google OAuth 2.0 у Flask (PythonAnywhere)

Цей допис — нагадування самому собі, як реалізувати повернення користувача на потрібну сторінку після авторизації через Google OAuth 2.0, та як уникати помилок, які забирають півдня.

📦 Залежності

  • flask
  • authlib
  • python-dotenv
  • werkzeug.middleware.proxy_fix.ProxyFix

1. Правильна реєстрація Google OAuth


google = oauth.register(
    name='google',
    client_id=GOOGLE_CLIENT_ID,
    client_secret=GOOGLE_CLIENT_SECRET,
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    api_base_url='https://openidconnect.googleapis.com/v1/',
    client_kwargs={'scope': 'openid email profile'}
)

⚠️ Без server_metadata_url буде помилка: Missing "jwks_uri" in metadata
⚠️ Без api_base_url буде помилка: Invalid URL 'userinfo'

2. Установка змінних середовища (.env)


GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
SECRET_KEY=...

У WSGI-файлі обов’язково:


from dotenv import load_dotenv
load_dotenv('/home/your_user/myauth/.env')

3. Налаштування Flask


from flask import Flask
from werkzeug.middleware.proxy_fix import ProxyFix

app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)
app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_SAMESITE="None",
    PREFERRED_URL_SCHEME="https",
    SECRET_KEY=os.getenv("SECRET_KEY")
)

4. Обробка авторизації та редіректу


@app.route('/login')
def login():
    redirect_uri = url_for('authorize_route', _external=True)
    return google.authorize_redirect(redirect_uri)

@app.route('/authorize')
def authorize_route():
    try:
        token = google.authorize_access_token()
        userinfo_response = google.get('userinfo')
        userinfo_response.raise_for_status()
        user = userinfo_response.json()
        session['user'] = user
        return redirect(url_for('index'))
    except Exception as e:
        print("❌ Помилка під час авторизації:", e)
        return "Внутрішня помилка сервера", 500

5. Типові помилки й способи вирішення

❌ Помилка ✅ Рішення
Missing "jwks_uri" in metadata Додай server_metadata_url при реєстрації OAuth
Invalid URL 'userinfo': No scheme supplied Додай api_base_url з базовим URL
BuildError: Could not build url for endpoint 'authorize' Використовуй правильну назву функції (наприклад, authorize_route) у url_for()
TemplateNotFound: index.html Створи папку templates/ у тій самій директорії, де й main.py
Internal Server Error після login Додай логування в /authorize, перевір SECRET_KEY та сесію

🔚 Після авторизації

Функція повинна зробити редірект на:

  • url_for('index') — головна сторінка
  • Або будь-яку іншу: redirect(url_for('dashboard'))

📌 Поради

  • Ніколи не використовуй ~ у шляху до `.env` у WSGI — краще os.path.join
  • Завжди додавай ProxyFix у Flask + HTTPS
  • Перевір ідеальну відповідність redirect URI в Google Console
  • Використовуй print() або logging.debug() для налагодження

Налаштування Google OAuth 2.0 у Flask (PythonAnywhere)

Налаштування Google OAuth 2.0 у Flask (PythonAnywhere)

Цей допис — покрокова інструкція для себе, щоб наступного разу налаштування авторизації через Google у Flask не забрало півдня і не викликало MismatchingStateError.

📦 Залежності

  • flask
  • authlib
  • python-dotenv
  • flask-login (опційно)
  • werkzeug.middleware.proxy_fix.ProxyFix

1. Створити файл .env


GOOGLE_CLIENT_ID=тут_твій_id
GOOGLE_CLIENT_SECRET=тут_секрет
SECRET_KEY=фіксований_ключ

2. Налаштування flask_app.py


from flask import Flask
from werkzeug.middleware.proxy_fix import ProxyFix
from dotenv import load_dotenv
import os
from pathlib import Path

project_root = Path(__file__).resolve().parent
load_dotenv(dotenv_path=project_root / '.env')

app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)

app.config.update(
    SECRET_KEY=os.getenv("SECRET_KEY"),
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_SAMESITE="None",
    PREFERRED_URL_SCHEME="https",
    GOOGLE_CLIENT_ID=os.getenv("GOOGLE_CLIENT_ID"),
    GOOGLE_CLIENT_SECRET=os.getenv("GOOGLE_CLIENT_SECRET"),
)

from auth import auth_bp, oauth, login_manager, init_oauth
init_oauth(app)
app.register_blueprint(auth_bp)
oauth.init_app(app)
login_manager.init_app(app)

3. Налаштування auth.py


from flask import Blueprint
from authlib.integrations.flask_client import OAuth
from flask_login import LoginManager

auth_bp = Blueprint('auth', __name__)
oauth = OAuth()
login_manager = LoginManager()

google = None

def init_oauth(app):
    global google
    google = oauth.register(
        name='google',
        client_id=app.config.get("GOOGLE_CLIENT_ID"),
        client_secret=app.config.get("GOOGLE_CLIENT_SECRET"),
        access_token_url='https://oauth2.googleapis.com/token',
        authorize_url='https://accounts.google.com/o/oauth2/auth',
        client_kwargs={
            'scope': 'openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile'
        }
    )

4. Роути /login та /auth/callback


@auth_bp.route('/login')
def login():
    redirect_uri = url_for('auth_callback', _external=True, _scheme="https")
    print(f"[DEBUG] session before redirect: {dict(session)}")
    return google.authorize_redirect(redirect_uri)

@auth_bp.route('/auth/callback')
def auth_callback():
    print(f"[DEBUG] session on callback: {dict(session)}")
    try:
        token = google.authorize_access_token()
        userinfo = google.parse_id_token(token)
        print("[DEBUG] userinfo:", userinfo)
        return redirect(url_for("review"))
    except Exception as e:
        print("[ERROR]", e)
        return "OAuth error"

❌ Типові помилки і як їх уникнути

ПомилкаПричинаРішення
MismatchingStateErrorКука не зберігається або не повертаєтьсяДодати ProxyFix, SESSION_COOKIE_SAMESITE="None", SECURE=True
client_id=Noneclient_id зчитується до load_dotenv()Зберігати client_id у app.config, а не через os.getenv під час імпорту
RuntimeError: working outside of application contextcurrent_app використовується під час імпортуВикликати oauth.register у функції init_oauth(app)
Cookie зникає після редиректуSESSION_COOKIE_SAMESITE неправильнийПоставити SESSION_COOKIE_SAMESITE="None", SECURE=True

🧪 Як перевірити

  • У браузері відкрий DevTools (F12) → Application → Cookies → leox.pythonanywhere.com
  • Перевір, чи є session-cookie
  • У вкладці Network → auth/callback → заголовки запиту → перевір, чи передалась кука

✅ Тестування


print("[DEBUG] session:", dict(session))
print("[DEBUG] redirect_uri:", redirect_uri)

Якщо після логіну все працює — побачите токен, і Flask сесія залишиться живою після редиректу.

---

🧠 Урок: у Flask найважливіше — порядок ініціалізації, контекст і cookie. Якщо ці три речі під контролем — навіть Google OAuth здається логічним.

неділя, 27 жовтня 2024 р.

Очищення фону

Як замінити фон зображення на білий за допомогою OpenCV

Як замінити фон зображення на білий за допомогою OpenCV

У цій статті ми розглянемо, як можна за допомогою Python та бібліотеки OpenCV замінити фон зображення на білий. Це може бути корисним для обробки підписів, логотипів або інших об'єктів, де потрібен чистий білий фон. Нижче наведено код, який можна використовувати для цієї задачі.

Пояснення коду

Функція replace_background_with_white приймає зображення як вхідні дані, виконує обробку та зберігає результат з білим фоном у поточному каталозі. Вона використовує HSV-простір для точнішого виділення кольорів і маскування об'єкта. Цей підхід дозволяє залишити об'єкт видимим, а фон замінити на білий.

Код


import cv2
import numpy as np
import os

def replace_background_with_white(image, output_filename="processed_image.png"):
    """
    Замінює фон зображення на білий і зберігає його в поточному каталозі.

    Параметри:
    image (numpy.ndarray): Вхідне зображення у форматі OpenCV.
    output_filename (str): Назва файлу для збереження обробленого зображення.

    Повертає:
    str: Шлях до збереженого обробленого зображення.
    """
    # Перетворити зображення в HSV для кращого виділення кольорів
    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

    # Встановити діапазон для світлих кольорів (білий фон)
    lower_white = np.array([0, 0, 200])
    upper_white = np.array([180, 20, 255])

    # Створити маску для виділення білого фону
    mask = cv2.inRange(hsv, lower_white, upper_white)

    # Інвертувати маску, щоб виділити об'єкт (підпис)
    mask_inv = cv2.bitwise_not(mask)

    # Застосувати маску до оригінального зображення
    result = cv2.bitwise_and(image, image, mask=mask_inv)

    # Створити білий фон
    white_bg = np.full_like(image, 255)  # Білий фон

    # Поєднати об'єкт з білим фоном
    final_image = np.where(result == 0, white_bg, result)

    # Зберегти зображення в поточний каталог
    output_path = os.path.join(os.getcwd(), output_filename)
    cv2.imwrite(output_path, final_image)

    return output_path

# Приклад використання
input_path = "sign.jpg"  # Заміни на шлях до твого файлу

# Завантажити зображення
image = cv2.imread(input_path)

# Виклик функції для обробки зображення та збереження результату
output_path = replace_background_with_white(image, "sign_white_bg.png")

print(f"Зображення збережено за адресою: {output_path}")
Як видно з прикладу, цей код автоматично визначає білі області на зображенні та замінює фон на білий. Ви можете використовувати цю функцію для різних типів зображень, що потребують видалення або заміни фону. Збереження відбувається в поточному каталозі, що робить процес простим і зручним для роботи.

субота, 13 квітня 2024 р.

Доступ до камери

Доступ до відеокамер у Python можна ефективно реалізувати за допомогою бібліотеки OpenCV, яка надає інструменти для захоплення відеопотоків з локальних та зовнішніх джерел. Користуючись класом `VideoCapture`, можна легко інтегрувати функціональність відеозахоплення в програми, що працюють як з веб-камерами, так і з файлами відео. Це дозволить взаємодіяти з відеопотоками в реальному часі, що відкриває можливості для створення програм відеонагляду, відеочатів, аналітики зображень та інших програм, які вимагають відеоаналітики.
 import cv2
import sys

# Ініціалізація змінної для визначення джерела відео
s = 0
# Перевірка чи передані аргументи командного рядка
if len(sys.argv) > 1:
    s = sys.argv[1]  # Використання першого аргументу як шляху до файлу або ідентифікатора камери

# Створення об'єкта VideoCapture для захоплення відео з камери або відтворення файлу
source = cv2.VideoCapture(s)

# Назва вікна, яке буде відображати відео
win_name = 'Camera Preview'
# Створення вікна для відображення відео з можливістю зміни розміру
cv2.namedWindow(win_name, cv2.WINDOW_NORMAL)

# Головний цикл для читання кадрів і відображення їх у вікні
while cv2.waitKey(1) != 27: # Чекати 1 мс і перервати цикл, якщо натиснуто Escape (27)
    has_frame, frame = source.read()  # Зчитування кадру
    if not has_frame:  # Якщо кадри закінчились, перервати цикл
        break
    cv2.imshow(win_name, frame)  # Відображення кадру у вікні

# Звільнення ресурсів та закриття вікон
source.release()
cv2.destroyWindow(win_name)
  

вівторок, 19 вересня 2023 р.

Вилучення метаданих із pdf файлу

Вилучення метаданих із PDF файлу

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

У цьому дописі ми розглянемо, як використовувати бібліотеку PyPDF2 для вилучення метаданих із PDF файлу.

Встановлення бібліотеки

Для початку потрібно встановити бібліотеку PyPDF2:

pip install PyPDF2
    

Вилучення метаданих

Ось приклад коду для вилучення метаданих із PDF файлу:

import PyPDF2

def remove_metadata(pdf_path, output_path):
    with open(pdf_path, 'rb') as file:
        reader = PyPDF2.PdfReader(file)
        
        # Створення нового PDF-файлу
        writer = PyPDF2.PdfWriter()
        
        # Копіювання всіх сторінок 
        for page_num in range(len(reader.pages)):
            page = reader.pages[page_num]
            writer.add_page(page)

        # Вилучення метаданих
        writer.add_metadata({
            '/Producer': '',
            '/Creator': '',
            '/CreationDate': '',
            '/ModDate': '',
            '/Author': ''
        })

        with open(output_path, 'wb') as output_file:
            writer.write(output_file)
             
    

Після запуску цього коду метадані у вихідному PDF-файлі будуть видалені.

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

Для вилучення метаданих із вашого PDF файлу, ви можете використати нашу функцію таким чином:

# Приклад використання
remove_metadata('your_input_file.pdf', 'output_without_metadata.pdf')

Де 'your_input_file.pdf' - це шлях до вашого вхідного файлу, а 'output_without_metadata.pdf' - шлях, за яким буде збережено новий PDF файл без метаданих.

Зміна метаданих PDF файлу

Якщо ви бажаєте змінити деякі метадані, а не видаляти їх повністю, ви можете використати наступну функцію:

def modify_metadata(input_file, output_file, new_metadata):
    # Відкриваємо вхідний файл
    with open(input_file, 'rb') as file:
        reader = PyPDF2.PdfReader(file)
        
        # Створюємо новий PDF файл
        writer = PyPDF2.PdfWriter()
        
        # Додаємо сторінки з вхідного файлу
        for page in reader.pages:
            writer.add_page(page)
            
        # Змінюємо метадані
        writer.add_metadata(new_metadata)
        
        # Зберігаємо новий PDF файл
        with open(output_file, 'wb') as output:
            writer.write(output)

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

Для зміни метаданих вашого PDF файлу:


new_data = {
    '/Title': 'Новий заголовок',
    '/Author': 'Новий автор',
    '/Subject': 'Нова тема',
    # ... (інші метадані для зміни) ...
}

# Приклад використання
modify_metadata('your_input_file.pdf', 'output_with_modified_metadata.pdf', new_data)

Де 'your_input_file.pdf' - це шлях до вашого вхідного файлу, 'output_with_modified_metadata.pdf' - шлях, за яким буде збережено новий PDF файл із зміненими метаданими, а new_data - словник з новими метаданими.

вівторок, 8 серпня 2023 р.

Конвертація TeX-Файлу з Формулами в DOCX за допомогою Pandoc

Конвертація TeX-Файлу з Формулами в DOCX за допомогою Pandoc

TeX є потужним інструментом для створення складних документів, особливо коли йдеться про математичні формули. Але інколи може виникнути потреба конвертувати TeX-файл в формат DOCX. В цьому пості ми розглянемо, як це зробити за допомогою Pandoc.

Встановлення Pandoc

Спершу вам потрібно встановити Pandoc. Це можна зробити за допомогою наступної команди:

pip install pandoc

Конвертація TeX-Файлу в DOCX

За допомогою Pandoc ви можете конвертувати TeX-файл в DOCX за допомогою такої команди:

pandoc myfile.tex -o myfile.docx

де myfile.tex - це ім'я вашого TeX-файлу, а myfile.docx - ім'я вихідного DOCX-файлу.

Збереження Формул

Pandoc автоматично зберігає більшість формул при конвертації з TeX в DOCX. Проте, деякі специфічні формули можуть потребувати додаткового налаштування або редагування вручну.

Заключення

Конвертація TeX-файлу в DOCX може бути здійснена за допомогою Pandoc з лише кількома командами. Це забезпечує гнучкий спосіб перетворення ваших наукових і математичних документів в загальноприйнятий формат, який можна легко відкрити та редагувати в багатьох текстових редакторах.

Автоматизація Конвертації за допомогою Bat-Файлу

Якщо вам потрібно конвертувати багато TeX-файлів, ви можете автоматизувати цей процес за допомогою bat-файлу. Ось як це зробити:

  1. Створіть новий текстовий файл з розширенням .bat, наприклад, convert.bat.
  2. Відкрийте файл в текстовому редакторі та додайте наступний рядок:
  3. pandoc %1.tex -o %1.docx
  4. Збережіть і закрийте файл.
  5. Тепер ви можете перетягнути ваш TeX-файл на цей bat-файл або запустити його з командного рядка, передавши ім'я TeX-файлу без розширення як аргумент, наприклад:
  6. convert myfile

Цей bat-файл використовує Pandoc для конвертації вказаного TeX-файлу в DOCX. Ви можете легко конвертувати кілька файлів, перетягуючи їх на bat-файл або запускаючи його з командного рядка для кожного файлу окремо.

Об'єднання кількох docx файлів в один файл

Вступ

Об'єднання кількох DOCX-файлів в один файл може бути корисним у різних ситуаціях, особливо при створенні звітів, конспектів та інших документів. У цьому пості я поясню, як можна об'єднати кілька DOCX-файлів в один за допомогою мови програмування Python та бібліотеки python-docx.

Підготовка

Перш ніж почати, вам потрібно встановити бібліотеку python-docx. Ви можете зробити це за допомогою наступної команди:

pip install python-docx

Об'єднання DOCX-файлів

Тепер ми можемо написати код для об'єднання DOCX-файлів. Ось основні кроки:

  1. Імпортування необхідних бібліотек:
  2. from docx import Document
  3. Створення нового об'єкта документа:
  4. merged_document = Document()
  5. Об'єднання кожного файлу: Ітеруйте через кожен файл, який потрібно об'єднати, та додайте його вміст до об'єднаного документа.
  6. files = ['file1.docx', 'file2.docx', 'file3.docx']
    for file_name in files:
        sub_doc = Document(file_name)
        for element in sub_doc.element.body:
            merged_document.element.body.append(element)
  7. Збереження об'єднаного документа:
  8. merged_document.save('merged.docx')

За допомогою цього підходу можна легко об'єднати кілька DOCX-файлів в один. Це може бути корисно для автоматизації робочих процесів та збільшення продуктивності.

Об'єднання DOCX-файлів з Малюнками

Об'єднання DOCX-файлів з малюнками вимагає додаткового кроку, оскільки малюнки мають бути коректно впорядковані у новому документі. Ми можемо використовувати бібліотеку docxcompose для цього завдання.

Встановлення docxcompose

Спершу вам потрібно встановити бібліотеку docxcompose:

pip install docxcompose

Код для об'єднання DOCX-файлів з малюнками

Тепер ми можемо використовувати docxcompose для об'єднання файлів з малюнками. Ось приклад коду:

from docxcompose.composer import Composer
from docx import Document

merged_document = Document()
composer = Composer(merged_document)

files = ['file1.docx', 'file2.docx', 'file3.docx']
for file_name in files:
    sub_doc = Document(file_name)
    composer.append(sub_doc)

composer.save('merged_with_images.docx')

Цей код зчитує кожен файл зі списку файлів та об'єднує їх в один документ, зберігаючи всі малюнки.

Об'єднання DOCX-файлів з малюнками може бути трохи складнішим, але з правильними інструментами це все одно досить просто. За допомогою бібліотеки docxcompose ми можемо зберегти всі малюнки та інші елементи форматування при об'єднанні файлів.

СhatCPT4.5

середа, 2 серпня 2023 р.

Візуалізація фільтрів згорткових шарів

Згорткові нейронні мережі (CNN) використовуються для багатьох завдань машинного навчання, особливо для обробки зображень. Одним з ключових елементів CNN є згорткові шари, які використовують набори ваг (або фільтрів) для сканування вхідного зображення та виявлення різних характеристик, таких як краї, текстури та кольори.

Ми можемо візуалізувати ці фільтри, щоб отримати краще уявлення про те, як CNN "бачить" зображення. В цьому пості ми покажемо, як це зробити за допомогою TensorFlow і Keras.

Спочатку, давайте навчимо модель класифікації на наборі MNIST:

import tensorflow as tf
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt

mnist = tf.keras.datasets.mnist.load_data()
(X_train_full, y_train_full), (X_test, y_test) = mnist
X_train_full = X_train_full / 255.
X_test = X_test / 255.
X_train, X_valid = X_train_full[:-5000], X_train_full[-5000:]
y_train, y_valid = y_train_full[:-5000], y_train_full[-5000:]

X_train = X_train[..., np.newaxis]
X_valid = X_valid[..., np.newaxis]
X_test = X_test[..., np.newaxis]

model= tf.keras.Sequential([
    tf.keras.layers.Conv2D(32, kernel_size=13, padding="same",
                           activation="relu", kernel_initializer="he_normal"),
    tf.keras.layers.Conv2D(64, kernel_size=9, padding="same",
                           activation="relu", kernel_initializer="he_normal"),
    tf.keras.layers.MaxPool2D(),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dropout(0.25),
    tf.keras.layers.Dense(128, activation="relu",
                          kernel_initializer="he_normal"),
    tf.keras.layers.Dropout(0.5),
    tf.keras.layers.Dense(10, activation="softmax")
])

model.compile(loss="sparse_categorical_crossentropy", optimizer="nadam",
              metrics=["accuracy"])

model.fit(X_train, y_train, epochs=4, validation_data=(X_valid, y_valid))
model.evaluate(X_test, y_test)

Після тренування моделі ми можемо отримати ваги першого згорткового шару:

filters, biases = model.layers[0].get_weights()

Це дає нам масив фільтрів, які можна візуалізувати за допомогою Matplotlib:

fig, ax = plt.subplots(nrows=4, ncols=8, figsize=(12, 6))
for i in range(4):
    for j in range(8):
        ax[i][j].imshow(filters[:, :, 0, i*8+j], cmap='cividis')
        ax[i][j].axis('off')
plt.show()

На виході отримаємо 32 візуалізованих фільтри, які показують, як кожен фільтр реагує на вхідні дані. Це може бути корисним інструментом для розуміння того, як CNN працює.

Для виводу фільтрів другого шару використаємо такий код:

filters, biases = model.layers[1].get_weights()
fig, ax = plt.subplots(nrows=8, ncols=8, figsize=(12, 12))
for i in range(8):
    for j in range(8):
        ax[i][j].imshow(filters[:, :, 0, i*8+j], cmap='cividis')
        ax[i][j].axis('off')
plt.show()
Розміри ядер було вибрано великими для кращої візуалізації.

понеділок, 31 липня 2023 р.

Зменшення розміру зображення в два рази за допомогою Keras

В даному пості ми розглянемо, як використовувати бібліотеку глибокого навчання Keras для зменшення розміру зображення в два рази. Це можна зробити за допомогою так званого "пулінгового" шару.

Що таке пулінговий шар?

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

Створення пулінгового шару в Keras

Ми можемо створити пулінговий шар за максимумом в Keras за допомогою класу MaxPooling2D:
from keras.models import Sequential
from keras.layers import MaxPooling2D

# створення моделі з одним шаром MaxPooling2D, який зменшує розмір зображення в два рази
model = Sequential()
model.add(MaxPooling2D(pool_size=(2, 2), input_shape=(None, None, 3)))
Ця модель прийматиме зображення будь-якого розміру з 3 каналами кольору (RGB) і зменшить його в два рази. Використання моделі для зменшення зображення

Тепер, коли у нас є модель, ми можемо використовувати її для зменшення розміру зображення:

import cv2
import numpy as np
Lena = cv2.imread('Lena.jpg')
image=Lena/255.0
image = np.expand_dims(image, axis=0)
output = model.predict(image)
output_image = output[0, :, :, :3]
У цьому коді ми завантажуємо та нормалізуємо зображення 'Lena.jpg', а потім передаємо його в модель Keras для зменшення розміру в два рази. На виході ми отримуємо зображення, розмір якого зменшений в два рази за допомогою моделі, та витягуємо RGB канали цього зображення.

Рядок

np.expand_dims(image, axis=0)
додає нову вісь до масиву image на позиції 0. Наприклад, якщо вихідне зображення має форму (висота, ширина, канали), після додавання нової осві форма зображення змінюється до (1, висота, ширина, канали). Це робиться для того, щоб підготувати зображення до передачі в нейронну мережу, оскільки багато функцій Keras очікують вхідні дані у формі батчу (масиву зразків), навіть якщо цей батч містить лише один зразок. Також в моделях можна використати інші типи пулінгових шарів попередньо імпортувавши їх
 from keras.layers import AveragePooling2D
# Створення моделі
model = Sequential()
# Додавання шару AveragePooling2D
model.add(AveragePooling2D(pool_size=(2, 2), input_shape=(None, None, 3)))
У цьому прикладі, pool_size=(2, 2) означає, що пулінговий шар буде брати середнє значення кожного вікна розміром 2x2 пікселів в початковому зображенні.

Вхідне зображення повинно мати 3 канали кольору (RGB).

Це все можна було зробити і без створення моделі, просто застосувавши пулінговий шар як оператор до зображення

avg_pool_layer = AveragePooling2D(pool_size=(2, 2))
output = avg_pool_layer(image)
output_image = output[0, :, :, :].numpy()

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

неділя, 29 січня 2023 р.

Клас Counter

Клас Counter з модуля collections у Python є підкласом dict для підрахунку хешованих об’єктів. Це контейнер, який буде зберігати підрахунок кожного з елементів, присутніх у контейнері. Елементи в контейнері зберігаються як ключі словника, а їх кількість зберігається як значення словника. Клас Counter схожий на словник, але він має деякі додаткові функції, такі як можливість підраховувати елементи в ітерації та повертати найпоширеніші елементи в контейнері. Одним із основних випадків використання класу Counter є підрахунок елементів у списку, наприклад:
from collections import Counter

# create a list of elements
elements = ['apple', 'banana', 'apple', 'orange', 'banana', 'orange']

# create a counter object
counter = Counter(elements)

# Print the count of each element
print(counter)

Counter({'apple': 2, 'banana': 2, 'orange': 2})
Об’єкт counter підраховує кількість входжень кожного елемента та повертає об’єкт, подібний до словника, де ключі є елементами, а значення – підрахунками. Іншою корисною функцією Counter є метод most_common(), який повертає список із n найпоширеніших елементів і їх підрахунок від найпоширенішого до найменшого, наприклад:
# get the top two most common elements
print(counter.most_common(2))
[('apple', 2), ('banana', 2)]
Клас Counter також підтримує математичні операції, такі як додавання та віднімання лічильників, наприклад:
# create two counter objects
counter1 = Counter(['apple', 'banana', 'apple'])
counter2 = Counter(['orange', 'banana', 'orange'])

# add two counters
counter3 = counter1 + counter2

# subtract two counters
counter4 = counter1 - counter2

пʼятниця, 12 серпня 2022 р.

Рядки

Під рядком розуміється послідовність ( одновимірний масив) символів. Рядки в Python оточені або одинарними, або подвійними лапками, s='hello' є тим самим що і s="hello". Символьний тип в Python відсутній, символ трактується як рядок довжини 1. Рядок є масивом, доступ до конкретного символу відбувається за номером його позиції.

s='hello'
print(s[0])
h  
Рядки є незмінюваним(inmutable) типом даних, елемент рядка не можна змінити переприсвоєнням. Спроба запустити такий код видає помилку

s='hello'
s[0]='a'
Рядки великої довжини беруться в потрійні лапки. Перебирати елементи рядка можна в циклі

for x in "апельсин":
  print(x) 
Перевірка чи підрядок t міститься ( не міститься) в рядку s виконується логічним оператором t in s (not in). Методи для роботи з рядками
 
s.upper()# всі літери великі
s.capitalize()# тільки перший символ великий
s.lower()# всі літери малі
s.casefold()# те саме тільки для більшої кількості символів( для UniCode) 
s.strip()# вилучає пробіли на початку і кінці рядка
s.strip(str(['b','c']))# вилучає вказані літери з початку і кінця,  до місця            зустрічі із літерою яка не в списку
s.lstrip(*), s.rstrip(*)# те саме тільки зліва чи справа
s.replace("H", "J")# Замінює в тексті першу літеру на другу
s.replace("Hre", "Jwew")#або один рядок на інший
s.translate(str.maketrans({"H": "J",'e':'o'}))#множинна заміна, попередньо maketrans  формує таблицю заміни із словника
s.translate(str.maketrans("He", "Jo"))# це саме без словника
s.maketrans(x, y[, z])# перші два аргументи для визначення заміни, третій рядок буде виключений із результату
s.split(","[,n])#видає список із слів які знаходяться між комами. Замість коми може  бути будь-який сепаратор. n задає кількість розбитів, за умовчанням  --максимальна
sq+s2# конкатенація 
txt*3# повторення рядка  три рази,без пробілів
s.center(20[,char])# вирівнює тес посередині добавляючи 20 пробілів (або інших символів), по 10 спереду і ззаду
s.count('value'[, start, end])# підраховує скільки раз значення зустрілося в рядку ( за умовчанням), або у його частині
s.endswith('value'[, start, end])# Повертає True якщо рядок закінчується вказаним виразом
s.find('value'[, start, end])# знаходить номер першого входження символу, або -1 якщо немає входження
s.isalnum()# Повертає True якщо всі символи рядка літери a-z або числа
s.isalpha()# Повертає True якщо всі символи рядка літери
s.isdecimal()# Повертає True якщо всі символи рядка є числа, в в тому числі записані  в  юнікодах. Схожі методи isnumeric(),  isdecimal(),  з деякими відмінностями.
s.isspace()# Повертає True якщо всі символи рядка є пробіли
s.isupper()# Повертає True якщо всі літери в  рядку  є великі 
"sep".join(s)# якщо s рядок то повертає рядок де між символами вставлений сепаратор, якщо s список   чи  кортеж символів, то об'єднує елементи в один рядок з вказаним сепаратором 
s.ljust(n[, character])# вирівнює рядок вставкою зліва n символів, пробілів за умовчанням
s.rjust(n[, character])# те саме тільки справа
s.partition(value)# розбиває рядок на кортеж з трьох підрядків -  до value, value і  після  value.Якщо value на початку чи в кінці  то будуть порожні підрядки
s.rfind(value[, start, end])# повертає  номер позиції останнього входження підрядка, або -1 якщо не знайдено підрядок
s.rindex(value[, start, end])# Те саме тільки замість -1 видає помилку
s.startswith(value[, start, end])# повертає True якщо рядок починається із слова value
s.swapcase()# змінює великі літери на малі і навпаки
chr(i)# повертає символ зайого номером в юнікоді
ord(char)# повертає юнікод символа 

Множина

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

S.add(x)# включає елемент у  множину
S.update(L)# об'єднує  S з елементами об'єкту L, не обов'язково  інша множина
S1.union(S2,S3,...)# звичайне об'єднання множин
S1.intersection(S2)# повертає нову множину, яка є перетином множин
S1.intersection_update(S2)# замінює S1 на перетин 
S1.difference(S2),S1.difference_update(S2)# різниця множин з нюансами
S1.symmetric_difference(S2)# повертає нову множину яка є симетричною різницею
S1.symmetric_difference_update(S2)# замінює S1 на  симетричну різницю
S1.isdisjoint(S2)# перевіряє чи множини мають спільні елементи
S1.issubset(S2)# перевіряє чи S1 є підмножиною S2.
S1.issuperset(S2)# перевіряє чи S2 є підмножиною S1.
S.remove( x)# видаляє  елемент x, якщо його немає  то видає помилку
S.discard( x)# видаляє  елемент x, якщо його немає  то ігнорує
S.pop()# повертає  випадковий елемент з множини,видаляючи його з неї
S.clear()# спорожнює множину
S.del()# повністю знищує множину
S.copy() # створює копію множини   

неділя, 31 липня 2022 р.

Підрахунок кількості повторень елементів в списку

Будується словник
from collections import Counter
grid=[1,1,2,3,1]
C=Counter(grid)
Counter({1: 3, 2: 1, 3: 1})
C[1]
3
Задача Скільки спільних елементів є в двох списках?
from collections import Counter
  #Дано два списки L1,L2
C1=Counter(L1)
C1=Counter(L1)
len(C1&C2)
#sum([C1[x]*C2[x] for x in C1  ])
Клас Counter() модуля collections - це підклас словника dict для підрахунку хеш-об'єктів - незмінних (inmutable), таких як рядки, числа, кортежі.

пʼятниця, 17 червня 2022 р.

OpenVINO

OpenVINO™ — OpenVINO (Open Visual Inference and Neural Network Optimization) — це набір інструментів, розроблений Intel для розробки та розгортання програм комп’ютерного бачення та глибокого навчання. Він містить набір оптимізованих бібліотек для фреймворків глибокого навчання, оптимізатор моделі для перетворення моделей у формат, який можна запускати на апаратному забезпеченні Intel, і набір інструментів для розгортання моделей на різних типах пристроїв, включаючи процесори, графічні процесори та VPU. OpenVINO призначений для прискорення розробки програм комп’ютерного бачення, дозволяючи розробникам швидко й легко оптимізувати та розгортати свої моделі на різноманітних платформах.

неділя, 6 червня 2021 р.

Збільшення зображення в покращеній якості (Super Resolution)

Навчені моделі EDSR_x2.pb EDSR_x4.pb EDSR_x8.pb знаходяться тут

Приклад використання для збільшення в 4 рази

SupRes = cv2.dnn_superres.DnnSuperResImpl_create() # створюємо клас
path = "EDSR_x4.pb"
SupRes.readModel(path)
SupRes.setModel("edsr",4)
new_image = SupRes.upsample(img) 
Також збільшувати зображеня безкоштовно можна на сайтах BigJpg, AI Image Enlarger, Lunapic і у Фотошоп - Зображення -> Розмір -> Зберегти деталі 2.0

Огляд 15 найкращих збільшувачів зображень

понеділок, 31 травня 2021 р.

неділя, 30 травня 2021 р.

Підготовка даних для нейронної мережі

Розглянемо на конкретному прикладі деякі особливості роботи з даними, які ще не розбиті на навчальні та тестові групи так як це було вище і які не можна використовувати для навчання нейронної мережі.

Завантажимо набір даних за посиланням. Він складається із трьох типів зображень по 1000 зображень кожного типу, які знаходяться у трьох папках cats , dogs , panda і ці папки самі розміщені у папці animals. Створимо ще одну папку label в якій будемо зберігати бінарні файли міток train_labels, test_labels. Нагадаємо, що з такими файлами ми вже працювали коли завантажували набори даних MNIST.

# ініціалізуємо данні і мітки
data = []
labels = []
# Утворюємо список  всіх шляхів до файлів із зображеннями та перемішуємо їх
imagePaths = sort(list(paths.list_images("dog_cat_panda\\animals")))
random.seed(42)
random.shuffle(imagePaths)
# Організовуємо цикл по зображенням
for imagePath in imagePaths:
	# Завантажуємо зображення, змінюємо його розмір до 32 x 32 пікселів, 
	# сплющуємо його  в 32x32x3=3072  пікселі і закидаємо в список
	image = cv2.imread(imagePath)
	image = cv2.resize(image, (32, 32)).flatten()
	data.append(image)
	# витягуємо назву мітки з імені шляху  і закидаємо її в список міток
	label = imagePath.split(os.path.sep)[-2]
	labels.append(label)
Використання os.path.sep зрозуміле із наступних прикладів:
'dog_cat_panda\animals\cats\cats_00003.jpg'.split(os.path.sep)
['dog_cat_panda\x07nimals', 'cats', 'cats_00003.jpg']
 'dog_cat_panda\animals\cats\cats_00003.jpg'.split(os.path.sep)[-2]
'cats'
Cписок зображень перетворюємо в NumPy-масив і нормуємо його. Також список міток перетворюємо в масив.
data = np.array(data, dtype="float") / 255.0
labels = np.array(labels) 
Нарешті розіб'ємо данні на навчальні і тестові у співвідношенні 3:1 використовуючи scikit-learn функцію для розщеплення даних train_test_split:
(trainX, testX, trainY, testY) = train_test_split(data,
	labels, test_size=0.25, random_state=42)
Мітки у нас задані як символи, але Keras працює з мітками заданими у числовому форматі. Тому перетворимо мітки у числа використовуючи scikit-learn клас LabelBinarizer:
lb = LabelBinarizer()
trainY = lb.fit_transform(trainY)
testY = lb.transform(testY) 
В результаті отримаємо таку бінаризацію міток
[1, 0, 0] # для  cats
[0, 1, 0] # для dogs
[0, 0, 1] # для panda 
Тепер набір даних частково підготовлений для роботи з нейронною мережею. При подальшій роботі з цими наборами даних, після їхнього завантаження, потрібно уточнити структуру цих даних і виконати необхідну попередню підготовку даних.

субота, 29 травня 2021 р.

Виявлення людських силуетів HOG-детектором

Силуети пішоходів можна виявляти каскадним класифікатором Хаара haarcascade_fullbody.xml проте із цим завданнями краще справляються HOG-класифікатори, а саме класифікатор hogcascade_pedestrians.xml який використовується за умовчанням в функції HOGDescriptor_getDefaultPeopleDetector. HOG-класифікатори використовують алгоритм відмінний від алгоритму Віоли-Джонса.

Приклад використання HOG-класифікатора виявлення силуетів людей.

import cv2 
import numpy as np 
 
scaling_factor = 0.5
hog = cv2.HOGDescriptor()
hog.setSVMDetector( cv2.HOGDescriptor_getDefaultPeopleDetector() )
frame = cv2.imread('foto/pep1.jpg')
face_rects= hog.detectMultiScale(frame, winStride=(8,8), padding=(32,32), scale=1.05)

for (x,y,w,h) in face_rects[0]: 
        cv2.rectangle(frame, (x,y), (x+w,y+h), (0,255,0), 3)
 
cv2.imshow('Input image', frame)
print ("Знайдено {0} пішоходи(ів)!".format(len(face_rects[0])))

Знайдено 3 пішоходи(ів!  
Отримуємо
Інший приклад роботи HOG-детектора

Навчання згорткової нейронної мережі на готових наборах даних

Mathedemo
Ми вже бачили, що найвідповідальнішим і найскладнішим етапом в роботі з нейронними мережами є підготовка наборів даних. Данні, якщо їх немає в Keras, можна взяти готовими з різних вільних баз даних в інтернеті або створити свій набір даних під конкретну задачу. В цьому пості ми навчимося використовувати готові набори даних для побудови нейронної мережі, а також створювати і готувати свої набори даних

Нейронні мережі з якими ми працювали раніше, використовували навчальні набори даних з MNIST які вже є Keras і легко завантажуються. Тепер ми розглянемо принципи роботи із готовими зовнішніми даними, які відсутні в Кeras, або з набором даних створеним користувачем.

Робота з готовими наборами даних.

Розглянемо задачу класифікації двох типів зображень -- літаків і автомобілів. Архів з готовими даними завантажуємо звідси

Структура даних така

  • Тренувальні данні ( Train data): Данні містять 200 зображень кожного автомобіля та літака, тобто всього ми маємо 400 зображень у навчальному наборі даних
  • Тестові данні ( Test data): Тестові данні містять 50 зображень кожного автомобіля та літака, тобто загальна кількість їх становить 100 зображень
Розмір всіх зображень -- $224 \times 224.$ Нам потрібно підготувати ці данні (data preprocessing) перед завантаженням їх у нейронну мережу. Розпаковуємо архівний файл, данні в якому організовані наступним чином:
Тепер будуємо нейронну мережу.
#Завантажуємо необхідні бібліотеки
from keras.preprocessing.image import ImageDataGenerator 
from keras.models import Sequential 
from keras.layers import Conv2D, MaxPooling2D 
from keras.layers import Activation, Dropout, Flatten, Dense 
from keras import backend as K 
Задаємо параметри даних, та шлях до даних, деякі гіперпараметри мережі
img_width, img_height = 224, 224
train_data_dir = 'v_data/train'
validation_data_dir = 'v_data/test'
nb_train_samples = 400 
nb_validation_samples = 100
epochs = 10
batch_size = 16
Тут, train\_data\_dir -- це каталог тренувальних даних, validation\_data\_dir -- це каталог для тестових даних, nb_train_samples -- загальна кількість тренувальних даних, nb_validation_samples -- загальна кількість тестових даних.

Задаємо формат вхідних даних в залежності від того чи RGB-канал знаходиться на першому місці чи на останньому

if K.image_data_format() == 'channels_first': 
    input_shape = (3, img_width, img_height) 
else: 
    input_shape = (img_width, img_height, 3)
Визначаємо згорткову нейронну мережу та компілюємо її із стандартними параметрами компіляції. Мережа містить три згорткові шари із 32, 32, 64 карт ознак з $2 \times 2$-ядрами, за кожним з яких йде агрегуючий шар. На вхід повнозв'язної частини подається вектор довжини 64. На виході отримуємо останній шар із одного вузла.
model = Sequential() 
model.add(Conv2D(32, (2, 2), input_shape = input_shape)) 
model.add(Activation('relu')) 
model.add(MaxPooling2D(pool_size =(2, 2))) 
  
model.add(Conv2D(32, (2, 2))) 
model.add(Activation('relu')) 
model.add(MaxPooling2D(pool_size =(2, 2))) 
  
model.add(Conv2D(64, (2, 2))) 
model.add(Activation('relu')) 
model.add(MaxPooling2D(pool_size =(2, 2))) 
  
model.add(Flatten()) 
model.add(Dense(64)) 
model.add(Activation('relu')) 
model.add(Dense(1)) 
model.add(Activation('sigmoid')) 
  
model.compile(loss ='binary_crossentropy', 
                     optimizer ='rmsprop', 
                   metrics =['accuracy'])
Отримуємо мережу з такою архітектурою

Тепер підготуємо доповненні дані з нашого набору

train_datagen = ImageDataGenerator( 
                rescale = 1. / 255, 
                 shear_range = 0.2, 
                  zoom_range = 0.2, 
            horizontal_flip = True) 
  
test_datagen = ImageDataGenerator(rescale = 1. / 255) 
  
train_generator = train_datagen.flow_from_directory(train_data_dir, 
                              target_size =(img_width, img_height), 
                     batch_size = batch_size, class_mode ='binary') # або 'categorical' за  умовчанням
  
validation_generator = test_datagen.flow_from_directory( 
                                    validation_data_dir, 
                   target_size =(img_width, img_height), 
          batch_size = batch_size, class_mode ='binary') 
Отже ImageDataGenerator поділив зображення на 255, виконав косе перетворення в деякому діапазоні, масштабував зображення і виконав горизонтальне віддзеркалення. Функція flow_from_directory використовується для підготовки даних з вказаного каталогу і вказаного розміру зображення.

Навчання і збереження моделі

model.fit_generator(train_generator, 
    steps_per_epoch = nb_train_samples // batch_size, 
    epochs = epochs, validation_data = validation_generator, 
    validation_steps = nb_validation_samples // batch_size) 
model.save('model_saved.h5')

Робота із моделлю

Завантажуємо модель, зображення, перетворюємо його у необхідний формат і передаємо функції model.predict(), яка повертає ймовірність належності зображення до першого класу, тобто до класу літаків.

from keras.models import load_model
import numpy as np
import  cv2

model = load_model('model_saved.h5')
label=["літак", "авто"]
import cv2
tst=cv2.imread('file.jpg',1)# Завантажуємо зображення як кольорове 
tst=cv2.resize(tst, (224, 224))
tst = tst.reshape( 1,224 , 224,3)
tst=tst.astype('float32') / 255
pred=list(model.predict(tst)[0])
if pred[0]>0.5: print(label[0])
else: print(label[1])

пʼятниця, 28 травня 2021 р.

Доповнення набору даних (data augmentation)

Бібліотека глибокого навчання Keras надає можливість автоматично збільшувати кількість даних (data augmentation) при навчанні моделі. Це досягається за допомогою класу ImageDataGenerator який створює пакети (batch) тензорів збільшуючи кількість даних у режимі реального часу. Дані будуть пропущені в мережу в циклі (партіями).

Нагадаємо, що в Python під ітератором, спрощено, розуміється такий складний об'єкт, елементи якого можна послідовно перебирати використовуючи метод next(). Генератор це клас для генерації ітераторів, причому в пам'яті буде зберігатися лише один елемент ітератора, що зручно при роботі з великими масивами даних.

ImageDataGenerator формує пакети даних, виконуючи над кожною фотографією деякі перетворення для того щоб збільшити кількість навчальних даних. Клас має кілька методів, але ми зосередимо увагу на таких основних типах примноження зображень, а саме:

  • width_shift_range, height_shift_range: горизонтальний та вертикальний Зсув зображення
  • horizontal_flip та vertical_flip: горизонтальне та вертикальне відзеркалення
  • rotation_range: Обертання зображень
  • lightness_range: Зміна яскравості зображення
  • zoom_range: Зміна розмірів зображення
  • rescale: Нормування зображення множенням на коефіцієнт
  • shear_range: Перекошування зображення, аргумент вказується у градусах
Для прикладу, якщо до зображення літака із нашого набору даних
застосувати генератор зображень з параметрами
datagen = ImageDataGenerator(shear_range = 20)
то ми отримаємо нові скошені(shear) зображення літака:
які випадково згенеровані у вказаному діапазоні зсуву, які додадуться на нашого набору даних і будуть використані при навчанні нейронної мережі