/
SM_DAMIR
/
limb
Обзор
Документация
Войти
/
SM_DAMIR
/
limb
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/processingworker.cpp
1 450 строк
43 KB
DamirSM
chore: init
16 июл 2026, 12:14
16 июл 2026, 12:14
bb10094
Код
Авторство
О чём код?
#include "processingworker.h" #include <QFile> #include <QDebug> #include <QElapsedTimer> #include <cmath> // ───────────────────────────────────────────────────────────── // Constructor / Destructor // ───────────────────────────────────────────────────────────── ProcessingWorker::ProcessingWorker(QObject *parent) : QObject(parent) , m_cancelled(false) { m_modelBasePath = "/usr/share/ru.template.limb/model/"; auto config = [](ncnn::Net &net) { net.opt.num_threads = 2; net.opt.use_vulkan_compute = false; net.opt.use_fp16_packed = false; net.opt.use_fp16_storage = false; net.opt.use_fp16_arithmetic = false; }; config(m_modnet); config(m_styleNet); config(m_zeroDCE); qDebug() << "ProcessingWorker created"; } ProcessingWorker::~ProcessingWorker() { m_modnet.clear(); m_styleNet.clear(); m_zeroDCE.clear(); } // ───────────────────────────────────────────────────────────── // Cancel // ───────────────────────────────────────────────────────────── void ProcessingWorker::cancelOperation(const QImage &baseImage, int brightness, int contrast, int whiteBalance, int styleIndex, int styleStrength, bool useZeroDCE) { m_cancelled = true; if (!baseImage.isNull()) { m_base = baseImage; m_brightness = brightness; m_contrast = contrast; m_whiteBalance = whiteBalance; m_styleIndex = styleIndex; m_styleStrength = qBound(0, styleStrength, 100); m_useZeroDCE = useZeroDCE; // m_enhanced/m_current не пересчитываем — их пересчитает // следующая команда через updateEnhanced()/updateCurrent(). } } // ───────────────────────────────────────────────────────────── // Pipeline helpers // ───────────────────────────────────────────────────────────── void ProcessingWorker::updateEnhanced() { if (m_base.isNull()) return; if (m_useZeroDCE) { QImage r = runZeroDCE(m_base); m_enhanced = r.isNull() ? m_base : r; } else { QImage r = applyManualEnhancement( m_base, m_brightness, m_contrast, m_whiteBalance); m_enhanced = r.isNull() ? m_base : r; } } void ProcessingWorker::updateCurrent() { if (m_enhanced.isNull()) return; if (m_styleIndex >= 0) { QImage r = runStyleTransfer( m_enhanced, m_styleIndex, m_styleStrength); m_current = r.isNull() ? m_enhanced : r; } else { m_current = m_enhanced; } } // ───────────────────────────────────────────────────────────── // Load image // ───────────────────────────────────────────────────────────── void ProcessingWorker::loadImage(const QString &filePath) { qDebug() << "=== loadImage ===" << filePath; m_cancelled = false; m_useZeroDCE = false; emit processingProgress(0); if (filePath.isEmpty()) { emit errorOccurred("Path is empty"); return; } if (!QFile::exists(filePath)) { emit errorOccurred("File not found: " + filePath); return; } QElapsedTimer timer; timer.start(); QImage image(filePath); if (image.isNull()) { emit errorOccurred("Failed to load: " + filePath); return; } emit timingMeasured("load_disk", timer.elapsed()); timer.restart(); if (image.width() > MAX_INPUT_SIZE || image.height() > MAX_INPUT_SIZE) { image = image.scaled( MAX_INPUT_SIZE, MAX_INPUT_SIZE, Qt::KeepAspectRatio, Qt::SmoothTransformation); } m_original = image.convertToFormat(QImage::Format_RGB32); m_base = m_original; m_brightness = 0; m_contrast = 0; m_whiteBalance = 0; m_styleIndex = -1; m_styleStrength = 100; m_useZeroDCE = false; updateEnhanced(); updateCurrent(); emit timingMeasured("load_preprocess", timer.elapsed()); emit processingProgress(100); emit imageLoaded(m_original); emit processingFinished(m_current, m_base); qDebug() << "=== loadImage done ===" << m_original.size(); } // ───────────────────────────────────────────────────────────── // Reset // ───────────────────────────────────────────────────────────── void ProcessingWorker::resetToOriginal() { qDebug() << "=== resetToOriginal ==="; if (m_original.isNull()) { emit errorOccurred("No image loaded"); return; } m_base = m_original; m_brightness = 0; m_contrast = 0; m_whiteBalance = 0; m_styleIndex = -1; m_styleStrength = 100; m_useZeroDCE = false; updateEnhanced(); updateCurrent(); emit processingProgress(100); emit processingFinished(m_current, m_base); } // ───────────────────────────────────────────────────────────── // Restore state (undo / redo) // ───────────────────────────────────────────────────────────── void ProcessingWorker::restoreState( const QImage &baseImage, int brightness, int contrast, int whiteBalance, int styleIndex, int styleStrength, bool useZeroDCE) { qDebug() << "=== restoreState ===" << "style:" << styleIndex << "strength:" << styleStrength; if (baseImage.isNull()) { emit errorOccurred("No image to restore"); return; } m_cancelled = false; m_base = baseImage; m_brightness = brightness; m_contrast = contrast; m_whiteBalance = whiteBalance; m_styleIndex = styleIndex; m_styleStrength = qBound(0, styleStrength, 100); m_useZeroDCE = useZeroDCE; emit processingProgress(20); updateEnhanced(); if (m_cancelled) return; // 🔥 КРИТИЧЕСКИ ВАЖНОЕ ИСПРАВЛЕНИЕ: // Перед применением стиля мы ДОЛЖНЫ загрузить правильную модель, // иначе runStyleTransfer использует веса предыдущего стиля из m_styleNet. if (m_styleIndex >= 0) { if (!loadStyleIfNeeded(m_styleIndex)) { emit errorOccurred("Failed to load style model for restore"); return; } } emit processingProgress(60); updateCurrent(); if (m_cancelled) return; emit processingProgress(100); emit processingFinished(m_current, m_base); } // ───────────────────────────────────────────────────────────── // U2Net / Modnet // ───────────────────────────────────────────────────────────── bool ProcessingWorker::loadModnetIfNeeded() { if (m_modnetLoaded) return true; QString param = m_modelBasePath + "u2net-opt.param"; QString bin = m_modelBasePath + "u2net-opt.bin"; qDebug() << "Loading U2Net from:" << param; if (!QFile::exists(param)) { emit errorOccurred("File not found: " + param); return false; } if (!QFile::exists(bin)) { emit errorOccurred("File not found: " + bin); return false; } m_modnet.clear(); QElapsedTimer timer; timer.start(); if (m_modnet.load_param(param.toStdString().c_str()) != 0) { emit errorOccurred("Failed to load u2net param"); return false; } if (m_modnet.load_model(bin.toStdString().c_str()) != 0) { emit errorOccurred("Failed to load u2net bin"); return false; } emit timingMeasured("u2net_load", timer.elapsed()); m_modnetLoaded = true; qDebug() << "U2Net loaded OK"; return true; } QImage ProcessingWorker::runModnet(const QImage &image) { qDebug() << "=== runModnet ==="; if (image.isNull()) { emit errorOccurred("runModnet: image is null"); return QImage(); } QElapsedTimer timer; timer.start(); // === ПРЕПРОЦЕССИНГ === QImage resized = image .scaled(NET_W, NET_H, Qt::IgnoreAspectRatio, Qt::SmoothTransformation) .convertToFormat(QImage::Format_RGB888); if (resized.isNull()) { emit errorOccurred("runModnet: resize failed"); return QImage(); } ncnn::Mat in = ncnn::Mat::from_pixels( resized.bits(), ncnn::Mat::PIXEL_RGB, NET_W, NET_H, resized.bytesPerLine()); if (in.empty()) { emit errorOccurred("runModnet: Mat creation failed"); return QImage(); } const float mean_vals[3] = {123.675f, 116.28f, 103.53f}; const float norm_vals[3] = { 1.0f / 58.395f, 1.0f / 57.12f, 1.0f / 57.375f }; in.substract_mean_normalize(mean_vals, norm_vals); emit timingMeasured("u2net_preprocess", timer.elapsed()); timer.restart(); // === ИНФЕРЕНС === ncnn::Extractor ex = m_modnet.create_extractor(); if (ex.input("in0", in) != 0) { emit errorOccurred("runModnet: input failed"); return QImage(); } ncnn::Mat out; if (ex.extract("out0", out) != 0 || out.empty()) { emit errorOccurred("runModnet: extract failed"); return QImage(); } if (out.w != NET_W || out.h != NET_H) { emit errorOccurred("runModnet: output size mismatch"); return QImage(); } emit timingMeasured("u2net_inference", timer.elapsed()); timer.restart(); // === ПОСТОБРАБОТКА === QImage mask(NET_W, NET_H, QImage::Format_Grayscale8); if (mask.isNull()) { emit errorOccurred("runModnet: mask creation failed"); return QImage(); } for (int y = 0; y < NET_H; ++y) { uchar *line = mask.scanLine(y); const float *row = nullptr; if (out.dims == 3 && out.c >= 1) row = out.channel(0).row(y); else if (out.dims == 2) row = out.row(y); else { emit errorOccurred("runModnet: unsupported output dims"); return QImage(); } if (!row) { emit errorOccurred("runModnet: row is null"); return QImage(); } for (int x = 0; x < NET_W; ++x) { float val = row[x]; val = 1.0f / (1.0f + std::exp(-val)); line[x] = static_cast<uchar>( qBound(0.f, val * 255.f, 255.f)); } } emit timingMeasured("u2net_postprocess", timer.elapsed()); qDebug() << "=== runModnet done ==="; return mask.scaled( image.width(), image.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } // ───────────────────────────────────────────────────────────── // Fast box blur // ───────────────────────────────────────────────────────────── QImage ProcessingWorker::fastBoxBlur( const QImage &src, int radius) { if (src.isNull()) return QImage(); QImage tmp = src.convertToFormat(QImage::Format_RGB32); QImage dst = tmp; const int w = tmp.width(); const int h = tmp.height(); const int r = qMax(1, radius); // Горизонтальный проход for (int y = 0; y < h; ++y) { const QRgb *sLine = reinterpret_cast<const QRgb*>(tmp.constScanLine(y)); QRgb *dLine = reinterpret_cast<QRgb*>(dst.scanLine(y)); long sumR = 0, sumG = 0, sumB = 0; int count = 0; for (int k = 0; k <= r && k < w; ++k) { sumR += qRed(sLine[k]); sumG += qGreen(sLine[k]); sumB += qBlue(sLine[k]); ++count; } for (int x = 0; x < w; ++x) { dLine[x] = qRgb( static_cast<int>(sumR / count), static_cast<int>(sumG / count), static_cast<int>(sumB / count)); int addIdx = x + r + 1; if (addIdx < w) { sumR += qRed(sLine[addIdx]); sumG += qGreen(sLine[addIdx]); sumB += qBlue(sLine[addIdx]); ++count; } int remIdx = x - r; if (remIdx >= 0) { sumR -= qRed(sLine[remIdx]); sumG -= qGreen(sLine[remIdx]); sumB -= qBlue(sLine[remIdx]); --count; } } } QImage dst2 = dst; // Вертикальный проход for (int x = 0; x < w; ++x) { long sumR = 0, sumG = 0, sumB = 0; int count = 0; for (int k = 0; k <= r && k < h; ++k) { QRgb px = reinterpret_cast<const QRgb*>( dst.constScanLine(k))[x]; sumR += qRed(px); sumG += qGreen(px); sumB += qBlue(px); ++count; } for (int y = 0; y < h; ++y) { reinterpret_cast<QRgb*>(dst2.scanLine(y))[x] = qRgb( static_cast<int>(sumR / count), static_cast<int>(sumG / count), static_cast<int>(sumB / count)); int addIdx = y + r + 1; if (addIdx < h) { QRgb px = reinterpret_cast<const QRgb*>( dst.constScanLine(addIdx))[x]; sumR += qRed(px); sumG += qGreen(px); sumB += qBlue(px); ++count; } int remIdx = y - r; if (remIdx >= 0) { QRgb px = reinterpret_cast<const QRgb*>( dst.constScanLine(remIdx))[x]; sumR -= qRed(px); sumG -= qGreen(px); sumB -= qBlue(px); --count; } } } return dst2; } // ───────────────────────────────────────────────────────────── // Apply mask to background // ───────────────────────────────────────────────────────────── QImage ProcessingWorker::applyMaskToBackground( const QImage &original, const QImage &mask, int mode, int r, int g, int b, int blurRadius) { if (original.isNull() || mask.isNull()) { qDebug() << "applyMaskToBackground: null input"; return QImage(); } if (original.size() != mask.size()) { QImage scaledMask = mask.scaled( original.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); return applyMaskToBackground( original, scaledMask, mode, r, g, b, blurRadius); } QImage blurred; if (mode == 1) { blurred = fastBoxBlur(original, blurRadius); if (blurred.isNull()) return QImage(); } QImage::Format fmt = (mode == 0) ? QImage::Format_ARGB32 : QImage::Format_RGB32; QImage result(original.width(), original.height(), fmt); if (result.isNull()) return QImage(); QImage src32 = original.convertToFormat(QImage::Format_ARGB32); QImage maskG = mask.convertToFormat(QImage::Format_Grayscale8); const int w = original.width(); const int h = original.height(); for (int y = 0; y < h; ++y) { const QRgb *srcLine = reinterpret_cast<const QRgb*>(src32.constScanLine(y)); const uchar *maskLine = maskG.constScanLine(y); QRgb *dstLine = reinterpret_cast<QRgb*>(result.scanLine(y)); for (int x = 0; x < w; ++x) { QRgb px = srcLine[x]; int alpha = maskLine[x]; double a = alpha / 255.0; switch (mode) { case 0: dstLine[x] = qRgba( qRed(px), qGreen(px), qBlue(px), alpha); break; case 1: { const QRgb *bLine = reinterpret_cast<const QRgb*>( blurred.constScanLine(y)); QRgb bp = bLine[x]; dstLine[x] = qRgb( static_cast<int>( qRed(px) * a + qRed(bp) * (1.0 - a)), static_cast<int>( qGreen(px) * a + qGreen(bp) * (1.0 - a)), static_cast<int>( qBlue(px) * a + qBlue(bp) * (1.0 - a))); break; } case 2: dstLine[x] = qRgb( static_cast<int>( qRed(px) * a + r * (1.0 - a)), static_cast<int>( qGreen(px) * a + g * (1.0 - a)), static_cast<int>( qBlue(px) * a + b * (1.0 - a))); break; default: dstLine[x] = px; break; } } } return result; } // ───────────────────────────────────────────────────────────── // Gradient background // ───────────────────────────────────────────────────────────── QImage ProcessingWorker::applyGradientBackground( const QImage &original, const QImage &mask, int r1, int g1, int b1, int r2, int g2, int b2) { if (original.isNull() || mask.isNull()) return QImage(); const int w = original.width(); const int h = original.height(); QImage result(w, h, QImage::Format_RGB32); QImage src32 = original.convertToFormat(QImage::Format_ARGB32); QImage maskG = mask.convertToFormat(QImage::Format_Grayscale8); for (int y = 0; y < h; ++y) { const QRgb *srcLine = reinterpret_cast<const QRgb*>(src32.constScanLine(y)); const uchar *maskLine = maskG.constScanLine(y); QRgb *dstLine = reinterpret_cast<QRgb*>(result.scanLine(y)); double t = static_cast<double>(y) / qMax(1, h - 1); int bgR = static_cast<int>(r1*(1.0-t) + r2*t); int bgG = static_cast<int>(g1*(1.0-t) + g2*t); int bgB = static_cast<int>(b1*(1.0-t) + b2*t); for (int x = 0; x < w; ++x) { QRgb px = srcLine[x]; double a = maskLine[x] / 255.0; dstLine[x] = qRgb( static_cast<int>( qRed(px) * a + bgR * (1.0 - a)), static_cast<int>( qGreen(px) * a + bgG * (1.0 - a)), static_cast<int>( qBlue(px) * a + bgB * (1.0 - a))); } } return result; } // ───────────────────────────────────────────────────────────── // Background operations // ───────────────────────────────────────────────────────────── void ProcessingWorker::removeBackground(int objectType) { Q_UNUSED(objectType) m_cancelled = false; if (m_base.isNull()) { emit errorOccurred("No image"); return; } QImage workBase = m_original; emit processingProgress(10); if (!loadModnetIfNeeded()) return; if (m_cancelled) return; emit processingProgress(30); QImage mask = runModnet(workBase); if (m_cancelled) return; if (mask.isNull()) { emit errorOccurred("Segmentation failed"); return; } // ─── 1. БИНАРИЗАЦИЯ С ВЫСОКИМ ПОРОГОМ ─── // Используем порог 180 вместо 128 — только уверенные пиксели объекта mask = mask.convertToFormat(QImage::Format_Grayscale8); const int maskBpl = mask.bytesPerLine(); const uchar *maskData = mask.constBits(); QImage binaryMask(mask.size(), QImage::Format_Grayscale8); const int binBpl = binaryMask.bytesPerLine(); uchar *binData = binaryMask.bits(); const int THRESHOLD = 180; // Высокий порог для четких краев for (int y = 0; y < mask.height(); ++y) { const uchar *srcLine = maskData + y * maskBpl; uchar *dstLine = binData + y * binBpl; for (int x = 0; x < mask.width(); ++x) { dstLine[x] = (srcLine[x] >= THRESHOLD) ? 255 : 0; } } // ─── 2. МОРФОЛОГИЧЕСКАЯ ЭРОЗИЯ (1 пиксель) ─── // Убираем полупрозрачные края, которые создают "бледность" QImage erodedMask = binaryMask; const int erodedBpl = erodedMask.bytesPerLine(); uchar *erodedData = erodedMask.bits(); for (int y = 1; y < binaryMask.height() - 1; ++y) { const uchar *prevLine = binData + (y - 1) * binBpl; const uchar *currLine = binData + y * binBpl; const uchar *nextLine = binData + (y + 1) * binBpl; uchar *dstLine = erodedData + y * erodedBpl; for (int x = 1; x < binaryMask.width() - 1; ++x) { // Пиксель остается 255 только если все соседи тоже 255 if (currLine[x] == 255 && currLine[x-1] == 255 && currLine[x+1] == 255 && prevLine[x] == 255 && nextLine[x] == 255) { dstLine[x] = 255; } else { dstLine[x] = 0; } } } // ─── 3. НЕБОЛЬШОЕ РАЗМЫТИЕ ДЛЯ СГЛАЖИВАНИЯ ─── // Применяем box blur с радиусом 1 для мягких краев QImage smoothMask = fastBoxBlur(erodedMask, 1); emit processingProgress(70); // ─── 4. ПРИМЕНЕНИЕ ЧЕРЕЗ ПРОЗРАЧНЫЙ РЕЖИМ ─── // Сначала создаем изображение с альфа-каналом (режим 0) QImage transparentResult = applyMaskToBackground( workBase, smoothMask, 0, 0, 0, 0, 0); if (m_cancelled) return; if (transparentResult.isNull()) { emit errorOccurred("Mask apply failed"); return; } // ─── 5. НАЛОЖЕНИЕ НА БЕЛЫЙ ФОН ─── // Конвертируем ARGB в RGB с белым фоном QImage baseRGB(transparentResult.size(), QImage::Format_RGB32); baseRGB.fill(Qt::white); for (int y = 0; y < transparentResult.height(); ++y) { const QRgb *src = reinterpret_cast<const QRgb*>( transparentResult.constScanLine(y)); QRgb *dst = reinterpret_cast<QRgb*>(baseRGB.scanLine(y)); for (int x = 0; x < transparentResult.width(); ++x) { double a = qAlpha(src[x]) / 255.0; dst[x] = qRgb( static_cast<int>( qRed(src[x]) * a + 255 * (1.0 - a)), static_cast<int>( qGreen(src[x]) * a + 255 * (1.0 - a)), static_cast<int>( qBlue(src[x]) * a + 255 * (1.0 - a))); } } if (m_cancelled) return; m_base = baseRGB; updateEnhanced(); updateCurrent(); emit processingProgress(100); emit processingFinished(m_current, m_base); } void ProcessingWorker::blurBackground( int objectType, int radius) { Q_UNUSED(objectType) m_cancelled = false; if (m_base.isNull()) { emit errorOccurred("No image"); return; } emit processingProgress(10); if (!loadModnetIfNeeded()) return; if (m_cancelled) return; emit processingProgress(30); QImage mask = runModnet(m_base); if (m_cancelled) return; if (mask.isNull()) { emit errorOccurred("Segmentation failed"); return; } emit processingProgress(60); QImage result = applyMaskToBackground( m_base, mask, 1, 0, 0, 0, radius); if (m_cancelled) return; if (result.isNull()) { emit errorOccurred("Mask apply failed"); return; } m_base = result; updateEnhanced(); updateCurrent(); emit processingProgress(100); emit processingFinished(m_current, m_base); } void ProcessingWorker::colorBackground( int objectType, int r, int g, int b) { Q_UNUSED(objectType) m_cancelled = false; if (m_base.isNull()) { emit errorOccurred("No image"); return; } emit processingProgress(10); if (!loadModnetIfNeeded()) return; if (m_cancelled) return; emit processingProgress(30); QImage mask = runModnet(m_base); if (m_cancelled) return; if (mask.isNull()) { emit errorOccurred("Segmentation failed"); return; } emit processingProgress(70); QImage result = applyMaskToBackground( m_base, mask, 2, r, g, b, 0); if (m_cancelled) return; if (result.isNull()) { emit errorOccurred("Mask apply failed"); return; } m_base = result; updateEnhanced(); updateCurrent(); emit processingProgress(100); emit processingFinished(m_current, m_base); } void ProcessingWorker::gradientBackground( int objectType, int r1, int g1, int b1, int r2, int g2, int b2) { Q_UNUSED(objectType) m_cancelled = false; if (m_base.isNull()) { emit errorOccurred("No image"); return; } emit processingProgress(10); if (!loadModnetIfNeeded()) return; if (m_cancelled) return; emit processingProgress(30); QImage mask = runModnet(m_base); if (m_cancelled) return; if (mask.isNull()) { emit errorOccurred("Segmentation failed"); return; } emit processingProgress(70); QImage result = applyGradientBackground( m_base, mask, r1, g1, b1, r2, g2, b2); if (m_cancelled) return; if (result.isNull()) { emit errorOccurred("Gradient apply failed"); return; } m_base = result; updateEnhanced(); updateCurrent(); emit processingProgress(100); emit processingFinished(m_current, m_base); } // ───────────────────────────────────────────────────────────── // Manual enhancement // ───────────────────────────────────────────────────────────── QImage ProcessingWorker::applyManualEnhancement( const QImage &src, int brightness, int contrast, int whiteBalance) { if (src.isNull()) return QImage(); QImage result = src.convertToFormat(QImage::Format_RGB32); const double brightnessStrength = 0.8; const double contrastStrength = 0.5; const double whiteBalanceStrength = 0.2; const int bDelta = qRound(brightness * brightnessStrength); const double cFactor = 1.0 + (contrast / 100.0) * contrastStrength; const int warmR = qRound(whiteBalance * whiteBalanceStrength); const int warmB = -qRound(whiteBalance * whiteBalanceStrength); const int w = result.width(); const int h = result.height(); for (int y = 0; y < h; ++y) { QRgb *line = reinterpret_cast<QRgb*>(result.scanLine(y)); for (int x = 0; x < w; ++x) { QRgb px = line[x]; int cr = qBound(0, qRed(px) + bDelta, 255); int cg = qBound(0, qGreen(px) + bDelta, 255); int cb = qBound(0, qBlue(px) + bDelta, 255); cr = qBound(0, qRound((cr - 128) * cFactor + 128), 255); cg = qBound(0, qRound((cg - 128) * cFactor + 128), 255); cb = qBound(0, qRound((cb - 128) * cFactor + 128), 255); cr = qBound(0, cr + warmR, 255); cb = qBound(0, cb + warmB, 255); line[x] = qRgb(cr, cg, cb); } } return result; } void ProcessingWorker::applyEnhancement( int brightness, int contrast, int whiteBalance) { m_cancelled = false; if (m_base.isNull()) { emit errorOccurred("No image"); return; } QElapsedTimer timer; timer.start(); emit processingProgress(20); m_useZeroDCE = false; m_brightness = brightness; m_contrast = contrast; m_whiteBalance = whiteBalance; updateEnhanced(); if (m_cancelled) return; updateCurrent(); if (m_cancelled) return; emit timingMeasured("enhancement", timer.elapsed()); emit processingProgress(100); emit processingFinished(m_current, m_base); } // ───────────────────────────────────────────────────────────── // ZeroDCE // ───────────────────────────────────────────────────────────── bool ProcessingWorker::loadZeroDCEIfNeeded() { if (m_zeroDCELoaded) return true; QString param = m_modelBasePath + "zerodce-opt.param"; QString bin = m_modelBasePath + "zerodce-opt.bin"; if (!QFile::exists(param)) { emit errorOccurred("File not found: " + param); return false; } if (!QFile::exists(bin)) { emit errorOccurred("File not found: " + bin); return false; } m_zeroDCE.clear(); QElapsedTimer timer; timer.start(); if (m_zeroDCE.load_param( param.toStdString().c_str()) != 0) { emit errorOccurred("Failed to load zerodce param"); return false; } if (m_zeroDCE.load_model( bin.toStdString().c_str()) != 0) { emit errorOccurred("Failed to load zerodce bin"); return false; } emit timingMeasured("zerodce_load", timer.elapsed()); m_zeroDCELoaded = true; qDebug() << "ZeroDCE loaded OK"; return true; } QImage ProcessingWorker::runZeroDCE(const QImage &image) { qDebug() << "=== runZeroDCE ==="; if (image.isNull()) return QImage(); QElapsedTimer timer; timer.start(); const int ZDCE_MAX = 512; QImage src = image; if (src.width() > ZDCE_MAX || src.height() > ZDCE_MAX) { src = src.scaled( ZDCE_MAX, ZDCE_MAX, Qt::KeepAspectRatio, Qt::SmoothTransformation); } QImage rgb = src.convertToFormat(QImage::Format_RGB888); if (rgb.isNull()) return image; ncnn::Mat in = ncnn::Mat::from_pixels( rgb.bits(), ncnn::Mat::PIXEL_RGB, rgb.width(), rgb.height(), rgb.bytesPerLine()); if (in.empty()) return image; const float norm[3] = { 1.f / 255.f, 1.f / 255.f, 1.f / 255.f}; in.substract_mean_normalize(nullptr, norm); emit timingMeasured("zerodce_preprocess", timer.elapsed()); timer.restart(); ncnn::Extractor ex = m_zeroDCE.create_extractor(); if (ex.input("in0", in) != 0) return image; ncnn::Mat out; if (ex.extract("out0", out) != 0 || out.empty()) return image; if (out.c < 3) { emit errorOccurred("ZeroDCE: wrong output channels"); return image; } emit timingMeasured("zerodce_inference", timer.elapsed()); timer.restart(); QImage result(src.width(), src.height(), QImage::Format_RGB32); for (int y = 0; y < src.height(); ++y) { QRgb *line = reinterpret_cast<QRgb*>(result.scanLine(y)); const float *rRow = out.channel(0).row(y); const float *gRow = out.channel(1).row(y); const float *bRow = out.channel(2).row(y); for (int x = 0; x < src.width(); ++x) { line[x] = qRgb( static_cast<int>( qBound(0.f, rRow[x]*255.f, 255.f)), static_cast<int>( qBound(0.f, gRow[x]*255.f, 255.f)), static_cast<int>( qBound(0.f, bRow[x]*255.f, 255.f))); } } emit timingMeasured("zerodce_postprocess", timer.elapsed()); qDebug() << "=== runZeroDCE done ==="; if (result.size() != image.size()) { return result.scaled( image.width(), image.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } return result; } void ProcessingWorker::applyZeroDCE() { m_cancelled = false; if (m_base.isNull()) { emit errorOccurred("No image"); return; } emit processingProgress(10); if (!loadZeroDCEIfNeeded()) return; if (m_cancelled) { emit processingFinished(m_current, m_base); return; } emit processingProgress(30); m_useZeroDCE = true; m_brightness = 0; m_contrast = 0; m_whiteBalance = 0; updateEnhanced(); if (m_cancelled) return; updateCurrent(); if (m_cancelled) return; emit processingProgress(100); emit processingFinished(m_current, m_base); } // ───────────────────────────────────────────────────────────── // Style transfer // ───────────────────────────────────────────────────────────── bool ProcessingWorker::loadStyleIfNeeded(int styleIndex) { if (m_loadedStyleIndex == styleIndex) return true; const QStringList styles = QStringList() << "mosaic-opt" << "rain_princess-opt" << "candy-opt" << "udnie-opt"; if (styleIndex < 0 || styleIndex >= styles.size()) { emit errorOccurred( "Wrong style index: " + QString::number(styleIndex)); return false; } QString param = m_modelBasePath + styles[styleIndex] + ".param"; QString bin = m_modelBasePath + styles[styleIndex] + ".bin"; if (!QFile::exists(param)) { emit errorOccurred("File not found: " + param); return false; } if (!QFile::exists(bin)) { emit errorOccurred("File not found: " + bin); return false; } m_styleNet.clear(); m_loadedStyleIndex = -1; QElapsedTimer timer; timer.start(); if (m_styleNet.load_param( param.toStdString().c_str()) != 0) { emit errorOccurred( "Failed load_param: " + styles[styleIndex]); return false; } if (m_styleNet.load_model( bin.toStdString().c_str()) != 0) { emit errorOccurred( "Failed load_model: " + styles[styleIndex]); return false; } emit timingMeasured("style_load", timer.elapsed()); m_loadedStyleIndex = styleIndex; qDebug() << "Style loaded OK:" << styles[styleIndex]; return true; } QImage ProcessingWorker::runStyleTransfer( const QImage &image, int styleIndex, int strength) { if (image.isNull()) return image; QElapsedTimer timer; timer.start(); // === ПРЕПРОЦЕССИНГ === const int STYLE_SIZE = 256; QImage resized = image .scaled(STYLE_SIZE, STYLE_SIZE, Qt::IgnoreAspectRatio, Qt::SmoothTransformation) .convertToFormat(QImage::Format_RGB888); const int w = resized.width(); const int h = resized.height(); ncnn::Mat in = ncnn::Mat::from_pixels( resized.bits(), ncnn::Mat::PIXEL_RGB2BGR, w, h, resized.bytesPerLine()); emit timingMeasured("style_preprocess", timer.elapsed()); timer.restart(); // === ИНФЕРЕНС === ncnn::Extractor ex = m_styleNet.create_extractor(); ex.set_light_mode(true); if (ex.input("in0", in) != 0) return image; ncnn::Mat out; if (ex.extract("out0", out) != 0 || out.empty() || out.c != 3) return image; emit timingMeasured("style_inference", timer.elapsed()); timer.restart(); // === ПОСТОБРАБОТКА === const int outW = out.w; const int outH = out.h; QImage origScaled = resized.scaled(outW, outH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation) .convertToFormat(QImage::Format_RGB32); QImage styled(outW, outH, QImage::Format_RGB32); double alpha = qBound(0, strength, 100) / 100.0; double beta = 1.0 - alpha; for (int y = 0; y < outH; ++y) { QRgb *dstLine = reinterpret_cast<QRgb*>(styled.scanLine(y)); const QRgb *origLine = reinterpret_cast<const QRgb*>( origScaled.constScanLine(y)); const float *bRow = out.channel(0).row(y); const float *gRow = out.channel(1).row(y); const float *rRow = out.channel(2).row(y); for (int x = 0; x < outW; ++x) { float fB = bRow[x]; float fG = gRow[x]; float fR = rRow[x]; fR = qBound(0.f, fR, 255.f); fG = qBound(0.f, fG, 255.f); fB = qBound(0.f, fB, 255.f); if (alpha < 1.0) { fR = static_cast<float>( fR * alpha + qRed(origLine[x]) * beta); fG = static_cast<float>( fG * alpha + qGreen(origLine[x]) * beta); fB = static_cast<float>( fB * alpha + qBlue(origLine[x]) * beta); } dstLine[x] = qRgb( static_cast<int>(fR), static_cast<int>(fG), static_cast<int>(fB)); } } emit timingMeasured("style_postprocess", timer.elapsed()); return styled.scaled( image.width(), image.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } void ProcessingWorker::applyStyle( int styleIndex, int strength) { m_cancelled = false; if (m_enhanced.isNull()) { emit errorOccurred("No image"); return; } if (styleIndex < 0) { m_styleIndex = -1; m_current = m_enhanced; if (m_cancelled) return; emit processingProgress(100); emit processingFinished(m_current, m_base); return; } emit processingProgress(10); if (!loadStyleIfNeeded(styleIndex)) return; if (m_cancelled) return; emit processingProgress(40); m_styleIndex = styleIndex; m_styleStrength = qBound(0, strength, 100); updateCurrent(); if (m_cancelled) return; emit processingProgress(100); emit processingFinished(m_current, m_base); } // ───────────────────────────────────────────────────────────── // Detect object // ───────────────────────────────────────────────────────────── void ProcessingWorker::detectObject(int objectType) { qDebug() << "=== detectObject ===" << objectType; m_cancelled = false; if (m_base.isNull()) { emit errorOccurred("No image"); return; } emit processingProgress(10); if (!loadModnetIfNeeded()) return; if (m_cancelled) return; emit processingProgress(30); QImage mask = runModnet(m_base); if (mask.isNull()) { emit errorOccurred("Segmentation failed"); return; } if (m_cancelled) return; emit processingProgress(70); QImage result = drawSelectionContour( m_base, mask, objectType); emit processingProgress(100); emit previewFinished(result); } // ───────────────────────────────────────────────────────────── // Draw contour // ───────────────────────────────────────────────────────────── QImage ProcessingWorker::drawSelectionContour( const QImage &original, const QImage &mask, int objectType) { Q_UNUSED(objectType) if (original.isNull() || mask.isNull()) return QImage(); QImage result = original.convertToFormat(QImage::Format_ARGB32); QImage grayMask = mask.convertToFormat(QImage::Format_Grayscale8); const int w = result.width(); const int h = result.height(); const int threshold = 128; const int borderSize = 3; const QRgb contourColor = qRgba(0, 180, 255, 255); for (int y = 0; y < h; ++y) { QRgb *line = reinterpret_cast<QRgb*>(result.scanLine(y)); const uchar *maskLine = grayMask.constScanLine(y); for (int x = 0; x < w; ++x) { bool isFg = maskLine[x] >= threshold; if (isFg) { bool isBorder = false; for (int dy = -borderSize; dy <= borderSize && !isBorder; ++dy) { for (int dx = -borderSize; dx <= borderSize && !isBorder; ++dx) { int nx = qBound(0, x+dx, w-1); int ny = qBound(0, y+dy, h-1); if (grayMask.constScanLine(ny)[nx] < threshold) isBorder = true; } } if (isBorder) line[x] = contourColor; } else { QRgb px = line[x]; line[x] = qRgb( qBound(0, qRound(qRed(px) * 0.4), 255), qBound(0, qRound(qGreen(px) * 0.4), 255), qBound(0, qRound(qBlue(px) * 0.4), 255)); } } } return result; }