Посмотрим как LLM может анализировать изображения, распознавать текст со сканов даже плохого качества.
В современном мире обработка изображений с помощью искусственного интеллекта становится всё более востребованной — от автоматического распознавания документов до анализа скриншотов и чеков. В этой статье мы покажем, как с помощью простого, но мощного инструмента — визуально-лингвистической модели (VLM) — можно извлекать текст, понимать содержание изображений и структурировать данные в формате JSON, пригодном для дальнейшей обработки.
Мы используем модель Qwen2.5-VL через API сервиса OpenRouter, который позволяет легко взаимодействовать с передовыми мультимодальными моделями, не разворачивая их локально. Наша задача — продемонстрировать, как один и тот же код может решать разнообразные практические кейсы: распознавание текста на сканах документов, извлечение реквизитов (например, ОГРН), анализ чеков, интерпретация учебных материалов, перевод и анализ постов социальных сетей и анализ фото с камер наблюдения.
В листинге кода вы увидите, как мы загружаем изображение, кодируем его в формат base64 и отправляем в модель с точным текстовым запросом. Ответ приходит в виде структурированного текста или JSON — в зависимости от поставленной задачи. Мы разбираем примеры с плохим качеством изображений, скриншотами книг, официальными документами и сценами из реальной жизни, чтобы показать надёжность и гибкость подхода.
Этот код можно легко адаптировать под автоматизацию обработки документов, интеграцию в CRM-системы, парсинг чеков или создание умных помощников. Главное — правильно сформулировать запрос, предоставить изображение, и модель сделает остальное. Давайте посмотрим, как это работает на практике.
Импорты нужных библиотек.
import requests
import json
import base64
from dotenv import load_dotenv
import os
from PIL import Image
from IPython.display import display
# создайте файл env с содержимым OPEN_ROUTER_KEY=ваш_ключ
load_dotenv('env')
True
# для работы нужен ключ, созданный на https://openrouter.ai/
OPENROUTER_API_KEY = os.environ['OPEN_ROUTER_KEY']
Файлы с которыми будем работать.
! ls -l ./assets
итого 3988
-rw-rw-r-- 1 alex alex 643692 июл 25 15:39 cheque.jpg
-rw-rw-r-- 1 alex alex 88807 июл 25 16:56 code_book_bad.jpg
-rw-rw-r-- 1 alex alex 298443 июл 25 16:54 code_book.jpg
-rw-rw-r-- 1 alex alex 45148 июл 25 20:04 doc.jpg
-rw-rw-r-- 1 alex alex 1915183 июл 25 15:42 doc_scan.jpg
-rw-rw-r-- 1 alex alex 36969 июл 25 17:16 fight.jpg
-rw-rw-r-- 1 alex alex 19260 июл 25 15:47 page_pic_bad_2.jpg
-rw-rw-r-- 1 alex alex 73681 июл 25 15:46 page_pic.jpg
-rw-rw-r-- 1 alex alex 831007 июл 25 15:41 page_scan.jpg
-rw-rw-r-- 1 alex alex 74747 июл 25 15:50 scan_bad.jpg
-rw-rw-r-- 1 alex alex 35230 июл 25 15:54 screen_bad.jpg
Вспомогательные функции.
# функция кодирования изображения в base64
def encode_image_to_base64(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
# функция показа изображения в Jupyter Notebook
def show_image(filepath: str):
image = Image.open(filepath)
image.thumbnail((600, 600))
display(image)
Модель с которой будем работать.
# функция обращения к VLM через сервис OpenRouter
def response_img(query: str, filepath: str, model: str):
base64_image = encode_image_to_base64(filepath)
response = requests.post(
url="https://openrouter.ai/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
"Content-Type": "application/json",
"HTTP-Referer": "test", # Optional. Site URL for rankings on openrouter.ai.
"X-Title": "test", # Optional. Site title for rankings on openrouter.ai.
},
data=json.dumps({
"model": model,
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": query
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
}
}
]
}
],
"temperature": 0.01
})
)
return response.json()
Начинаем эксперименты. Проанализируем фото кассового чека.
query = 'что на этом изображении?'
filepath = './assets/cheque.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
# qwen/qwen2.5-vl-32b-instruct
show_image(filepath)
Ответ модели
res = response_img(query, filepath, model)
res
{'id': 'gen-1753693827-TDzmA5U6JAs9Ucfh1pIp',
'provider': 'DeepInfra',
'model': 'qwen/qwen2.5-vl-32b-instruct',
'object': 'chat.completion',
'created': 1753693827,
'choices': [{'logprobs': None,
'finish_reason': 'stop',
'native_finish_reason': 'stop',
'index': 0,
'message': {'role': 'assistant',
'content': 'На изображении представлен чек от магазина DNS (DNS 2350) с информацией о покупке. Вот основные детали, которые можно увидеть:\n\n### **Основная информация о чеке:**\n- **Магазин:** DNS 2350\n- **Адрес магазина:** Ростов-на-Дону, Ростовская область, Космонавтов, 3д, 2/2\n- **Телефон магазина:** 88007707999\n- **Дата и время покупки:** 02.05.23, 20:16\n- **Терминал:** 22935087\n- **Мастер-терминал:** 211000004035\n- **Тип карты:** MasterCard\n- **Номер карты:** Частично виден (последние 4 цифры: 0809)\n- **Сумма покупки:** 4199.00 рублей\n- **Комиссия за операцию:** 0 рублей\n- **Код авторизации:** 013319\n- **Номер ссылки:** 719967494147\n- **Временный НИИ-код:** 7D069CE29DAED11DFE1D558CDA6D7C1C4EF04AC\n\n### **Информация о товаре:**\n- **Товар:** Смартфон BQ 5565L FEST 2/16GB [5, 45/LTE/2Mp/2200/Black] (Гарантия - 12 месяцев)\n- **Количество:** 1 шт.\n- **Цена за штуку:** 199,00 рублей\n- **Итоговая стоимость товара:** 199,00 рублей\n\n### **Дополнительные сведения:**\n- **Налог НДС:** 20% (699,83 рублей)\n- **Итоговая сумма с учетом НДС:** 4199,00 рублей\n- **Адрес доставки:** 344092, Ростовская область, Ростов-на-Дону, Космонавтов, дом 2, корпус 2\n- **Информация о сайте:** dns-shop.ru\n- **Номер заказа:** 344068\n- **Место расчетов:** Ростов ТРЦ Бавария Гипер\n- **Адрес ТРЦ:** Ростов-на-Дону, Космонавтов, дом 2, корпус 2\n- **АВТОМАТ:** EY0000000000000215\n- **QR-код:** Присутствует в правом нижнем углу чека\n- **Сайт для проверки чека:** ofdp.platformaofd.ru\n- **ИНН:** 2540167061\n- **Кассир:** 6826\n\n### **Дополнительные данные:**\n- **Сумма НДС:** 699,83 рублей\n- **Сумма без НДС:** 3499,17 рублей\n- **Сумма с НДС:** 4199,00 рублей\n\n### **Вывод:**\nЭто чек о покупке смартфона BQ 5565L FEST 2/16GB в магазине DNS в Ростове-на-Дону. Сумма покупки составила 4199,00 рублей, включая НДС. Товар заказан с доставкой по указанному адресу.',
'refusal': None,
'reasoning': None}}],
'usage': {'prompt_tokens': 4371,
'completion_tokens': 909,
'total_tokens': 5280,
'prompt_tokens_details': None}}
Только текстовая часть ответа модели без метаданных.
print(res['choices'][0]['message']['content'])
На изображении представлен чек от магазина DNS (DNS 2350) с информацией о покупке. Вот основные детали, которые можно увидеть:
### **Основная информация о чеке:**
- **Магазин:** DNS 2350
- **Адрес магазина:** Ростов-на-Дону, Ростовская область, Космонавтов, 3д, 2/2
- **Телефон магазина:** 88007707999
- **Дата и время покупки:** 02.05.23, 20:16
- **Терминал:** 22935087
- **Мастер-терминал:** 211000004035
- **Тип карты:** MasterCard
- **Номер карты:** Частично виден (последние 4 цифры: 0809)
- **Сумма покупки:** 4199.00 рублей
- **Комиссия за операцию:** 0 рублей
- **Код авторизации:** 013319
- **Номер ссылки:** 719967494147
- **Временный НИИ-код:** 7D069CE29DAED11DFE1D558CDA6D7C1C4EF04AC
### **Информация о товаре:**
- **Товар:** Смартфон BQ 5565L FEST 2/16GB [5, 45/LTE/2Mp/2200/Black] (Гарантия - 12 месяцев)
- **Количество:** 1 шт.
- **Цена за штуку:** 199,00 рублей
- **Итоговая стоимость товара:** 199,00 рублей
### **Дополнительные сведения:**
- **Налог НДС:** 20% (699,83 рублей)
- **Итоговая сумма с учетом НДС:** 4199,00 рублей
- **Адрес доставки:** 344092, Ростовская область, Ростов-на-Дону, Космонавтов, дом 2, корпус 2
- **Информация о сайте:** dns-shop.ru
- **Номер заказа:** 344068
- **Место расчетов:** Ростов ТРЦ Бавария Гипер
- **Адрес ТРЦ:** Ростов-на-Дону, Космонавтов, дом 2, корпус 2
- **АВТОМАТ:** EY0000000000000215
- **QR-код:** Присутствует в правом нижнем углу чека
- **Сайт для проверки чека:** ofdp.platformaofd.ru
- **ИНН:** 2540167061
- **Кассир:** 6826
### **Дополнительные данные:**
- **Сумма НДС:** 699,83 рублей
- **Сумма без НДС:** 3499,17 рублей
- **Сумма с НДС:** 4199,00 рублей
### **Вывод:**
Это чек о покупке смартфона BQ 5565L FEST 2/16GB в магазине DNS в Ростове-на-Дону. Сумма покупки составила 4199,00 рублей, включая НДС. Товар заказан с доставкой по указанному адресу.
Выведем анализ чека в структурированный вид.
query = 'выведи всю информацию с данного чека в формате json.'
filepath = './assets/cheque.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
res = response_img(query, filepath, model)
Ответ модели.
res
{'id': 'gen-1753693955-fjJ10R01IVpT2tKR8a7Y',
'provider': 'DeepInfra',
'model': 'qwen/qwen2.5-vl-32b-instruct',
'object': 'chat.completion',
'created': 1753693955,
'choices': [{'logprobs': None,
'finish_reason': 'stop',
'native_finish_reason': 'stop',
'index': 0,
'message': {'role': 'assistant',
'content': 'Вот информация с чека в формате JSON:\n\n```json\n{\n "shop": "DNS 2350",\n "address": "Ростов-на-Дону, Ростовская область, пп-кт Космонавтов, 3д 2/2",\n "phone": "88007707999",\n "date": "02.05.23",\n "time": "20:16",\n "check_number": "0030",\n "terminal": "22935087",\n "merchant": "211000004035",\n "card": {\n "type": "MASTERCARD",\n "number": "**** **** **** 0809",\n "amount": 4199.00,\n "commission": 0.00,\n "status": "ОАОБРЕНО"\n },\n "authorization_code": "013319",\n "reference_number": "719967494147",\n "transaction_id": "7D069CE29DAED11DFE1D558CDAA6D7C1C4EF04AC",\n "delivery_address": "344092, Ростовская, обл, Ростов-на-Дону, г, Космонавтов, пп-кт, дом 2, корпус 2, т.",\n "items": [\n {\n "description": "4888290 Смартфон BQ 5565L FEST 2/16GB [5, 45/LTE/2MpX/2200/Black] (Гарантия - 12 мес)",\n "quantity": 1,\n "price": 199.00,\n "total": 199.00\n }\n ],\n "discount": {\n "percentage": 20,\n "amount": 699.83\n },\n "total": 4199.00,\n "document_number": "E-08631902",\n "shop_website": "dns-shop.ru",\n "delivery_point": {\n "address": "Ростов-на-Дону, пп-кт им. Михаила Нагибина, д. 40, пом. 36, 38",\n "number": 344068\n },\n "payment_details": {\n "amount": 4199.00,\n "discount_amount": 699.83,\n "final_amount": 4199.00\n },\n "delivery_address": "344092, Ростовская, обл, Ростов-на-Дону, г, Космонавтов, пп-кт, дом 2, корпус 2",\n "payment_terminal": "EY00000000000000215",\n "payment_location": "Ростов ТРЦ Вавилон Гипер",\n "payment_method": "000 \\"Звотор ОфД\\"",\n "kkt_serial_numbers": [\n "0005443236043929",\n "0484480005028820"\n ],\n "fiscal_document_number": "06367",\n "fiscal_storage_number": "9960440502798330",\n "fiscal_register_number": "2540167061",\n "fiscal_document_date": "02.05.23",\n "fiscal_document_time": "20:22",\n "fiscal_document_code": "6826",\n "fiscal_document_number": "4021146396",\n "additional_info": {\n "url": "ofdp.platformaofd.ru",\n "tax_info": "www.nalog.gov.ru"\n }\n}\n```\n\nЭтот JSON содержит все ключевые данные из чека, включая информацию о магазине, товарах, оплате, адресе доставки и других деталях.',
'refusal': None,
'reasoning': None}}],
'usage': {'prompt_tokens': 4381,
'completion_tokens': 998,
'total_tokens': 5379,
'prompt_tokens_details': None}}
Только текстовая часть ответа модели.
print(res['choices'][0]['message']['content'])
Вот информация с чека в формате JSON:
```json
{
"shop": "DNS 2350",
"address": "Ростов-на-Дону, Ростовская область, пп-кт Космонавтов, 3д 2/2",
"phone": "88007707999",
"date": "02.05.23",
"time": "20:16",
"check_number": "0030",
"terminal": "22935087",
"merchant": "211000004035",
"card": {
"type": "MASTERCARD",
"number": "**** **** **** 0809",
"amount": 4199.00,
"commission": 0.00,
"status": "ОАОБРЕНО"
},
"authorization_code": "013319",
"reference_number": "719967494147",
"transaction_id": "7D069CE29DAED11DFE1D558CDAA6D7C1C4EF04AC",
"delivery_address": "344092, Ростовская, обл, Ростов-на-Дону, г, Космонавтов, пп-кт, дом 2, корпус 2, т.",
"items": [
{
"description": "4888290 Смартфон BQ 5565L FEST 2/16GB [5, 45/LTE/2MpX/2200/Black] (Гарантия - 12 мес)",
"quantity": 1,
"price": 199.00,
"total": 199.00
}
],
"discount": {
"percentage": 20,
"amount": 699.83
},
"total": 4199.00,
"document_number": "E-08631902",
"shop_website": "dns-shop.ru",
"delivery_point": {
"address": "Ростов-на-Дону, пп-кт им. Михаила Нагибина, д. 40, пом. 36, 38",
"number": 344068
},
"payment_details": {
"amount": 4199.00,
"discount_amount": 699.83,
"final_amount": 4199.00
},
"delivery_address": "344092, Ростовская, обл, Ростов-на-Дону, г, Космонавтов, пп-кт, дом 2, корпус 2",
"payment_terminal": "EY00000000000000215",
"payment_location": "Ростов ТРЦ Вавилон Гипер",
"payment_method": "000 \"Звотор ОфД\"",
"kkt_serial_numbers": [
"0005443236043929",
"0484480005028820"
],
"fiscal_document_number": "06367",
"fiscal_storage_number": "9960440502798330",
"fiscal_register_number": "2540167061",
"fiscal_document_date": "02.05.23",
"fiscal_document_time": "20:22",
"fiscal_document_code": "6826",
"fiscal_document_number": "4021146396",
"additional_info": {
"url": "ofdp.platformaofd.ru",
"tax_info": "www.nalog.gov.ru"
}
}
```
Этот JSON содержит все ключевые данные из чека, включая информацию о магазине, товарах, оплате, адресе доставки и других деталях.
query = 'выведи всю информацию с данного изображения в формате json.'
filepath = './assets/doc_scan.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
show_image(filepath)
res = response_img(query, filepath, model)
res
{'id': 'gen-1753694222-VZ50sGeF29BCtlKqmjsc',
'provider': 'DeepInfra',
'model': 'qwen/qwen2.5-vl-32b-instruct',
'object': 'chat.completion',
'created': 1753694222,
'choices': [{'logprobs': None,
'finish_reason': 'stop',
'native_finish_reason': 'stop',
'index': 0,
'message': {'role': 'assistant',
'content': 'Вот информация с изображения в формате JSON:\n\n```json\n{\n "document_type": "Свидетельство о государственной регистрации некоммерческой организации",\n "issuing_authority": "Министерство юстиции Российской Федерации",\n "organization_name": "Благотворительный фонд «Мальтийская служба помощи Аугсбург и Берлин»",\n "address": {\n "index": "107140",\n "city": "г. Москва",\n "street": "ул. Краснопрудная",\n "house": "д. 24/2",\n "building": "стр. 1"\n },\n "registration_decision_date": "26 апреля 1996 г.",\n "registration_in_egrl_date": "02 июля 2002 г.",\n "registration_number": "1027700000712",\n "issuer": {\n "position": "Исполняющий обязанности начальника Главного управления Министерства юстиции Российской Федерации по Москве",\n "name": "Л.Г. Ильина"\n },\n "account_number": "77140111375",\n "issue_date": "03 декабря 2012 г."\n}\n```\n\nЭта структура JSON содержит все ключевые данные, извлеченные из изображения.',
'refusal': None,
'reasoning': None}}],
'usage': {'prompt_tokens': 4994,
'completion_tokens': 324,
'total_tokens': 5318,
'prompt_tokens_details': None}}
print(res['choices'][0]['message']['content'])
Вот информация с изображения в формате JSON:
```json
{
"document_type": "Свидетельство о государственной регистрации некоммерческой организации",
"issuing_authority": "Министерство юстиции Российской Федерации",
"organization_name": "Благотворительный фонд «Мальтийская служба помощи Аугсбург и Берлин»",
"address": {
"postal_code": "107140",
"city": "г. Москва",
"street": "ул. Краснопрудная",
"house_number": "д. 24/2",
"building": "стр. 1"
},
"registration_decision_date": "26 апреля 1996 г.",
"registration_in_egrl_date": "02 июля 2002 г.",
"registration_number": "1027700000712",
"issuer": {
"position": "Исполняющий обязанности начальника Главного управления Министерства юстиции Российской Федерации по Москве",
"name": "Л.Г. Ильина"
},
"account_number": "77140111375",
"issue_date": "03 декабря 2012 г."
}
```
Эта структура JSON содержит все ключевые данные, извлеченные из изображения.
query = 'извлеки ОГРН и название компании из данного скана. Сложи в json структуру с полями name: str и ogrn: str'
filepath = './assets/doc_scan.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
Из скана извлечен следующий ОГРН и название компании:
- **ОГРН**: 1027700000712
- **Название компании**: Благотворительный фонд «Мальтийская служба помощи Аугсбург и Берлин»
Сформированная JSON-структура:
```json
{
"name": "Благотворительный фонд «Мальтийская служба помощи Аугсбург и Берлин»",
"ogrn": "1027700000712"
}
```
query = 'выведи всю информацию с данного изображения в формате json.'
filepath = './assets/page_pic_bad_2.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
show_image(filepath)
res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
Вот информация с изображения в формате JSON:
```json
{
"section": "3.1 Arduino Hardware",
"figures": [
{
"figure_number": "3.2",
"caption": "Arduino Nano connected to a laptop PC",
"description": "Изображение показывает Arduino Nano, подключенный к ноутбуку через USB-кабель."
},
{
"figure_number": "3.3",
"caption": "Arduino app with empty program",
"description": "Изображение показывает интерфейс Arduino IDE с пустой программой, содержащей две функции: `setup()` и `loop()`. Код в окне редактора выглядит следующим образом:\n\n```c\nvoid setup() {\n // put your setup code here, to run once:\n}\n\nvoid loop() {\n // put your main code here, to run repeatedly:\n}\n```"
}
],
"text": {
"paragraph": "Для написания программы для Arduino и загрузки её на контроллер используется бесплатная среда разработки Arduino IDE (Integrated Development Environment). При запуске на Mac, Windows или Linux PC пользователь видит пустую программу с двумя функциями на C: `setup()` и `loop()`, как показано на рисунке 3.3."
},
"footnote": {
"number": "1",
"text": "Arduino IDE online at https://www.arduino.cc"
},
"page_number": 55
}
```
Этот JSON-объект включает все ключевые элементы изображения, такие как номера рисунков, их подписи, описания и текстовый контент.
query = 'распознай весь текст со скриншота максимально точно'
filepath = './assets/code_book_bad.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
show_image(filepath)
res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
Вот текст, распознанный со скриншота:
---
### Левая страница (страница 242)
**Inserted Sauteed String Beans**
**Inserted Confucius "Chicken"**
Запись строки в файл формата CSV выполняется аналогично чтению из строки из такого файла. С этой целью вызывается функция `fputcsv()`, принимающая в качестве аргументов дескриптор файла и массив значений, заранее подготовленных в формате CSV для записи в файл. Так, в примере 9.11 демонстрируется применение функций `fputcsv()` и `fopen()` для записи в файл формата CSV информации, извлеченной из таблицы базы данных.
#### Пример 9.11. Запись данных в файл формата CSV
```php
try {
$db = new PDO('sqlite:/tmp/restaurant.db');
} catch (Exception $e) {
print "Couldn't connect to database: " . $e->getMessage();
exit();
}
// открыть файл формата CSV для записи
$fh = fopen('dish-list.csv', 'wb');
$dishes = $db->query('SELECT dish_name, price, is_spicy FROM dishes');
while ($row = $dishes->fetch(PDO::FETCH_NUM)) {
// записать в массив $row данные в виде строки
// формата CSV. Функция fputcsv() добавляет
// знак перевода строки в конце записываемой строки
fputcsv($fh, $row);
}
fclose($fh);
```
Чтобы отправить веб-клиенту обратно страницу, состоящую только из данных в формате CSV, функции `fputcsv()` нужно дать команду направить данные в стандартный для PHP поток вывода вместо записи в файл. Необходимо также вызвать встроенную в PHP функцию `header()`, чтобы уведомить веб-клиента, что ему предполагается передать документ формата CSV, а не HTML. В примере 9.12 показано, каким образом функция `header()` вызывается с соответствующими аргументами.
#### Пример 9.12. Смена типа страницы на CSV
```php
// уведомить веб-клиента, что ему предполагается передать
// файл формата CSV
header('Content-Type: text/csv');
// уведомить веб-клиента, что содержимое файла формата CSV
// следует просматривать в отдельной программе
header('Content-Disposition: attachment; filename="dishes.csv"');
```
В примере 9.13 показана законченная программа, посылающая правильный заголовок в формате CSV, извлекающая строки из таблицы базы данных и выводящая их на экран. Результат, выводимый из этой программы, может быть непосредственно загружен в программу электронных таблиц из веб-браузера пользователя.
---
### Правая страница (страница 243)
#### Пример 9.13. Отправка файла формата CSV в браузер
```php
try {
$db = new PDO('sqlite:/tmp/restaurant.db');
} catch (Exception $e) {
print "Couldn't connect to database: " . $e->getMessage();
exit();
}
// уведомить веб-клиента, что ему передается файл формата CSV
// под названием dishes.csv
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="dishes.csv"');
// открыть файл с дескриптором потока вывода
$fh = fopen('php://output', 'wb');
// извлечь информацию из таблицы базы данных и
// вывести ее на экран
$dishes = $db->query('SELECT dish_name, price, is_spicy FROM dishes');
while ($row = $dishes->fetch(PDO::FETCH_NUM)) {
fputcsv($fh, $row);
}
```
В коде из примера 9.13 в качестве аргумента функции `fputcsv()` передается `php://output` — встроенный дескриптор файла, специально предназначенный для отправки данных туда же, куда они выводятся по команде `print`. Чтобы сформировать более сложные электронные таблицы, включающие в себя формулы, изображения и средства форматирования, следует воспользоваться пакетом PHPExcel (https://packagist.org/packages/phpoffice/phpexcel) из библиотечного набора PHPOffice.
Подробнее о том, как пользоваться пакетами, речь пойдет в главе 16.
---
### Проверка полномочий доступа к файлам
Как упоминалось в начале этой главы, в программах на PHP можно организовать только чтение и запись файлов, если интерпретатор PHP разрешает это делать. Но при этом нельзя слепо полагаться на сообщения об ошибках, чтобы выяснить полномочия на доступ к файлу. В языке PHP предоставляются функции, с помощью которых можно определить полномочия отдельных программ.
---
### Нижние метки страниц
- **Левая страница:** 242 | Глава 9. Манипулирование файлами
- **Правая страница:** Проверка полномочий доступа к файлам | 243
---
Текст распознан максимально точно. Если есть вопросы или необходимы уточнения, сообщите!
query = 'распознай весь текст со скриншота максимально точно'
filepath = './assets/doc_mid.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
show_image(filepath)
res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
### Распознанный текст с изображения:
---
**ПРОТОКОЛ РАЗНОГЛАСИЙ**
к проекту типового договора поставки промышленных товаров № 85/5 от «25» января 2024 г., предложенного Поставщиком к заключению
#### Таганрог
Общество с ограниченной ответственностью «________», именуемое в дальнейшем «Поставщик», в лице Генерального Директора __________, действующего на основании Устава, с одной стороны, и ООО «________», в лице Представителя по доверенности __________, именуемое в дальнейшем «Покупатель», с другой стороны, при обобщенном упоминании — «Стороны», рассмотрели проект типового договора поставки, предложенного Поставщиком к заключению на 2024 год. Стороны подняли настолько противоразличий к нему, на нижеследующих условиях:
| № п/п | Редакция в соответствии с типовым Договором поставки, предложенного Поставщиком к подписанию Сторонами | Редакция, согласованная Сторонами, как окончательный текст Договора поставки |
| --- | --- | --- |
| 1 | Дополнить Раздел 1 «Предмет договора» следующим пунктом: | Пунктом следующего содержания: |
| | | 1.5. Квартальный ассортимент, количество, цена, общая стоимость, условия и порядок поставки и оплаты товара указываются в Завках Покупателя, являющихся Приложениями к настоящему Договору, которые подписывается Сторонами на каждую поставку партии товара и является неотъемлемой частью настоящего Договора. |
| 2 | Дополнить Раздел 1 «Предмет договора» следующим пунктом: | Пунктом следующего содержания: |
| | | 1.6. Согласованные и подписанные Сторонами Завки Покупателя к настоящему Договору, являются обязательными для исполнения Сторонами. |
| 3 | Дополнить Раздел 1 «Предмет договора» следующим пунктом: | Пунктом следующего содержания: |
| | | 1.7. Завки Покупателя формируются Поставщиком в течение 2-3 (Двух) рабочих дней на основании поступивших заказов от Покупателя. Завка также считается принятой Поставщиком, если в течение 24 часов с момента получения заказа Покупателя, Поставщик не уведомил Покупателя об отказе выполнить полностью или частично, либо приступил к ее исполнению. При отсутствии подтверждения получения заказа от Поставщика дата, получения соответствующего заказа считается дата его направления Поставщику. |
| 4 | Дополнить Раздел 1 «Предмет договора» следующим пунктом: | Пунктом следующего содержания: |
| | | 1.8. Заказ, направленный к обязательному исполнению. Отказ Поставщика от исполнения Заказа Покупателя является обоснованным только в случае наступления обстоятельств непреодолимой силы. |
| 5 | Дополнить Раздел 1 «Предмет договора» следующим пунктом: | Пунктом следующего содержания: |
| | | 1.9. При первой поставке товара Покупателю, Поставщик обязан предоставить надлежащим образом заверенную копию подписанного уполномоченными представителями Сторон и скрепленного печатями Сторон Приложения №1 (Спецификации/Прайс-лист) к настоящему Договору. При согласовании Сторонами изменений в ранее подписанное ими Приложение №1 путем оформления его в новой редакции (либо путем приложения к нему Спецификаций/Прайс-листов с указанием их порядкового номера, указание Приложения), Поставщик обязуется предоставить Покупателю надлежащим образом заверенную копию новой редакции Приложения №1 и/или копию(и) документа (документов) дополняющего(их) соответствующее Приложение одновременно с первой поставкой товара, соответствующего внесенным изменениям. |
| 6 | Пункт 2.2 - по тексту представленного Поставщиком проекта Договора поставки: | Изложить в следующей редакции: |
| | 2.2. Поставщик обязуется поставить Товары в комплекте с относящейся к ним документацией, необходимой для осуществления торговли Товарами, в том числе с заверенными надлежащим образом | 2.2. Поставщик обязуется поставить Товары в комплекте с необходимой для осуществления торговли Товарами на территории Российской Федерации, в том числе: с заверенными надлежащим образом сертификатами соответствия, качества, гигиеническими сертификатами, Удостоверениями качества, декларациями. Состав необходимой документации определяется для каждой группы Товаров в отдельности в соответствии с требованиями действующего законодательства Российской Федерации, указанная документация должна быть на русском языке. К моменту |
---
### Объяснение:
1. **Структура документа**: Это протокол разногласий к проекту типового договора поставки промышленных товаров. Документ включает предложения по корректировке текста договора, предложенного Поставщиком.
2. **Участники**: Стороны — Поставщик (ООО) и Покупатель (ООО). Документ ведется в Таганроге.
3. **Основные изменения**:
- Добавление пунктов в Раздел 1 «Предмет договора».
- Уточнение условий поставки, включая:
- Формирование Завок Покупателя.
- Обязательность исполнения Заказов.
- Порядок предоставления документации (включая сертификаты и декларации).
- Изменение текста пункта 2.2 для соответствия требованиям торговли на территории РФ.
### Примечание:
Текст распознан максимально точно, но возможны незначительные опечатки или нечитаемые символы в оригинальном изображении.
query = 'переведи текст изображения на русский язык. заполни json с метаданными о изображении'
filepath = './assets/tweet.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
show_image(filepath)
res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
### Перевод текста на русский язык:
Я отказался от повышения по службе этой недели.
Мой босс был шокирован, не понимая, почему я отвергаю повышение на 30% после многих лет тяжелой работы и поздних ночей. Я объяснил, что боюсь, что зарабатывание столько денег может заставить меня потерять мотивацию и перестать работать так усердно.
В момент просветления он предложил вместо этого сократить зарплату на 20%, что я гордо принял. Я был рад помочь улучшить финансовые показатели компании и сохранить свою неуклонную приверженность посредственности.
Помните, друзья из LinkedIn, настоящий успех заключается в принятии красоты стагнации и никогда не становиться слишком финансово стабильным.
Подписывайтесь на меня за более полезными советами по карьере
#HumbleBrag #CareerGoals #Motivation #CorporateMartyr #LivingTheDream
---
### JSON с метаданными о изображении:
```json
{
"author": "Alexander Cohen",
"platform": "LinkedIn",
"post_time": "now",
"content": "Я отказался от повышения по службе этой недели. Мой босс был шокирован, не понимая, почему я отвергаю повышение на 30% после многих лет тяжелой работы и поздних ночей. Я объяснил, что боюсь, что зарабатывание столько денег может заставить меня потерять мотивацию и перестать работать так усердно. В момент просветления он предложил вместо этого сократить зарплату на 20%, что я гордо принял. Я был рад помочь улучшить финансовые показатели компании и сохранить свою неуклонную приверженность посредственности. Помните, друзья из LinkedIn, настоящий успех заключается в принятии красоты стагнации и никогда не становиться слишком финансово стабильным. Подписывайтесь на меня за более полезными советами по карьере",
"hashtags": [
"#HumbleBrag",
"#CareerGoals",
"#Motivation",
"#CorporateMartyr",
"#LivingTheDream"
],
"interactions": {
"likes": 0,
"comments": 0,
"shares": 0,
"impressions": 4
},
"analytics": "Доступна"
}
```
### Объяснение:
1. **author**: Имя автора поста.
2. **platform**: Платформа, на которой опубликован пост (LinkedIn).
3. **post_time**: Время публикации (указано как "now").
4. **content**: Текст поста на русском языке.
5. **hashtags**: Хэштеги, использованные в посте.
6. **interactions**: Метрики взаимодействия с постом (лайки, комментарии, репосты, просмотры).
7. **analytics**: Указание на доступность аналитики по посту.
Приведем пример анализа фото, например с камеры видеонаблюдения.
query = 'что происходит на изображении?'
filepath = './assets/fight.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
show_image(filepath)
res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
На изображении происходит конфликт или нападение на улице. Ситуация выглядит следующим образом:
1. **Лежащий человек**: На земле лежит мужчина, который, похоже, находится в состоянии беспомощности или травмирован. Он лежит на боку, и его поза указывает на то, что он может быть связан или удерживается.
2. **Действия нападающих**: Над лежащим человеком стоят несколько человек, которые, вероятно, нападают или удерживают его. Один из них наклонился над ним, возможно, причиняя ему физическое насилие или связывая его.
3. **Свидетели или участники**: В группе людей, окружающих лежащего, есть несколько человек, которые могут быть нападающими или свидетелями. Один из них стоит рядом, держа руки на поясе, что может указывать на его участие в происходящем.
4. **Место действия**: Сцена происходит на улице, возможно, на пешеходном переходе или вблизи дороги. В фоне видны фонари, машины и светофоры, что указывает на городскую среду.
5. **Обстановка**: Атмосфера напряженная, и ситуация выглядит как нападение или агрессивное нападение на человека. Люди, окружающие лежащего, одеты в повседневную одежду, что делает их похожими на обычных прохожих или участников конфликта.
### Вывод:
На изображении происходит нападение или агрессивное нападение на человека, который находится на земле. Ситуация требует немедленного вмешательства правоохранительных органов или других заинтересованных лиц для предотвращения дальнейшего насилия.
query = 'изображение отражает драку или сцену насилиля или несчастный случай? Отвечай только да или нет'
filepath = './assets/fight.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
Да.
query = 'посчитай количество людей на изображении. выведи только число в качестве ответа'
filepath = './assets/fight.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
6
Как видим модель хорошо отработала все предложенные кейсы и вы можете использовать ее в работе. Данная модель содержит 72 млрд. параметров.
Более маленькие модели (7-10 млрд. параметров) отрабатывают существенно хуже и не применимы для сложных кейсов.
Средние модели 20-35 млрд параметров отрабатывают хуже данной на текущий момент (01.08.2025) но в целом тоже могут использоваться для решения практических задач.