Filtr splotowy obrazu - Qt

0

Witam

Mam za zadanie napisać algorytm Filtru splotowego.

Wzorowałem się na artykule z pewnego blogu, jest tam również przykładowy kod lecz C# nie umiem za dobrze więc napisałem swój własny algorytm w oparciu o tekst.

http://dikamilo.net/entry/filtry-splotowe-w-c-sharp/

Mój problem polega na tym że dla danych wartości w masce algorytm albo rozmywa albo wyostrza prawie zawsze w ten sam sposób. Tak jak by wartości w masce nie miały większego znaczenia. Przykładowe wypełnienia maski na dole artykułu nie działają niestety u mnie...

Algorytm jeszcze bez warunków brzegowych dlatego te 20 w petlach.
Algorytm trzeba wykonać dla wszystkich pixeli obrazka i jak i składowych RGB.

tab[][] - maska.
r2 - promień maski (dla 3x3 maski = 1, dla 5x5 = 2 itd....)
clamp() - zaokrągla wartości do przedziału 0-255.

void Splot::maska(QImage *img)
{
    QImage img_copy = *img;

    uchar *p = 0;

    for(int y=20; y<img->height()-20; ++y)
    {
        for(int x=20; x<4*img->width()-20; x+=4)
        {
            double  sumaX = 0;
            double  suma_mnozenia_R = 0,
                    suma_mnozenia_G = 0,
                    suma_mnozenia_B = 0;

            int a = -1,b = -1;

            for (int j = y-r2; j< y+r2; j++)
            {
                p = img_copy.scanLine(j);
                b++;
                for (int i = x-r2*4; i< x+r2*4; i+=4)
                {
                    a++;
                    sumaX += tab[a][b];

                    suma_mnozenia_R += p[2+i] * tab[a][b];
                    suma_mnozenia_G += p[1+i] * tab[a][b];
                    suma_mnozenia_B += p[i] * tab[a][b];
                }
            }

            if (sumaX == 0)
                continue;
            else
            {
                p = img->scanLine(y);
                p[x+2] = clamp(suma_mnozenia_R / sumaX,0,255);
                p[x+1] = clamp(suma_mnozenia_G / sumaX,0,255);
                p[x  ] = clamp(suma_mnozenia_B / sumaX,0,255);
            }
        }
    }
}

Wartości do maski wczytuje z QTableWidget - działa to 100%.
Cała reszta też powinna działać prawidłowo, problem jest raczej z tym algorytmem.

1

Splot to jest coś co przyjmuje dwie funkcje/obrazy a zwraca jedną funkcję/obraz. To ma być coś takiego (dla QImage z 32 bitowym kolorem RGB).

class Maska {
public:
    virtual ~Maska() {}
    virtual int width() const = 0;
    virtual int height() const = 0;
    virtual int norm() const = 0;
    virtual const int *operator[](int row) const  = 0;
};

template<int w, int h>
class DefMask : public Maska {
     int data[h][w];
     mutable int norma;
public:
    DefMask () : norma(0) {
        std::fill(data[0], data[0]+h*w, 0);
    }

    int width() const {return w;}
    int height() const {return h;}
    int norm() const {
        if (!norma)
            norma = policzNorme();
        return norma;
    }
    const int *operator[](int row) const { return data[row]; }
    int *operator[](int row) { norma= 0; return data[row]; }
    int policzNorme() const {
         int suma = 0;
         for(int i=0; i<h; ++i)
         for(int j=0; j<w; ++j)
             suma += data[i][j]*data[i][j];
         return ceil(sqrt(suma*w*h));
    }
};

QImage splot(const QImage& img, const Maska &maska) {
    Q_ASSERT(QImage::Format_RGB32 == img.format());
    int maskW(maska.width()), maskH(maska.height());
    QImage result(img.width()-maskW, img.height()-maskH, img.format());
    int norm = maska.norm();

    for (int y=0; y<result.height(); ++y) {
         for (int x=0; x<result.width(); ++x) {
             int r(0), g(0), b(0);
             for (int i=0; i<maskH; ++i) {
                 const int *maskRow = maska[i];
                 const QRgb *imgLine = reinterpret_cast<const QRgb*>(img.scanLine(y+i));
                 for(int j=0; j<maskW; ++j) {
                      QRgb color = imgLine[j+x];
                      b += maskRow[j]*qBlue(color);
                      g += maskRow[j]*qGreen(color);
                      r += maskRow[j]*qRed(color);
                 }
             }
             r = qBound(0, r/norm, 0xff);
             g = qBound(0, g/norm, 0xff);
             b = qBound(0, b/norm, 0xff);
             result.setPixel(x,y,qRgb(r,g,b));
         }
    }
   return result;
}
0

Dość ciężki kod jak dla mnie, ale spróbuje przetestować.

Maska w moim zadaniu ma być po prostu 2 wymiarową tablicą intów o nieparzystym rozmiarze (np. 5x5). Więc moim zdaniem te dwie klasy nie są konieczne.

Jeszcze nie mogę dojść co to jest to norm

int norm() const { return 10; }
0

Witam

dziś wczoraj wziąłem się za kod, ale nie mogę go poprawnie dodać do mojego programu.

Kilka uwag i pytań zarazem.

  1. Klasa Maska jest klasą abstrakcyjną ? czyż funkcje w niej nie powinny być wirtualne ? Inaczej błąd :initializer specified for non-virtual method 'int Maska::width() const' itp...

const quint32 *imgLine = static_cast<const quint32 *>(img.scanLine(y+i));

Błąd : błąd:invalid static_cast from type 'uchar*' to type 'const quint32*', Czy po prostu nie lepiej skanować do uchar* ?

3.przy próbie utworzenia obiektu -

 DefMask<5,5> mask;

tutaj wywala błąd

int norm() const {
        if (!norma)
            norma = policzNorme();
        return norma;
}

passing 'const DefMask<5, 5>' as 'this' argument of 'int DefMask<w, h>::policzNorme() [with int w = 5, int h = 5]' discards qualifiers
usunięcie const niby pomaga ale pewnie to nie jest rozwiązanie problemu.

Q_ASSERT(QImage::Format_RGB888 == img.format());

Tutaj mi w ogóle program wywala - wczytuję .bmp 24 bitowe.

Gdy w końcu jakoś udało mi się skompilować dostaje zawsze czarny obraz...

Kod funkcji na chwilę obecną:

QImage Splot::fun(QImage img)
{
    mask.toData(tab,r);

    //Q_ASSERT(QImage::Format_RGB888 == img.format());

    QImage result(img.width()-r, img.height()-r, img.format());

    int norm = mask.norm();

    for (int y=0; y<result.height(); ++y) {
             for (int x=0; x<result.width(); ++x) {
                 int r(0), g(0), b(0);
                 for (int i=0; i<r; ++i) {
                     const uchar *maskRow = mask[y+i];
                     const uchar *imgLine = img.scanLine(y+i);
                     for(int j=0; j<r; ++j) {
                          uchar color = imgLine[j+x]; // typ ?
                          b += maskRow[j]*qBlue(color);
                          g += maskRow[j]*qGreen(color);
                          r += maskRow[j]*qRed(color);
                     }
                 }
                 r = qBound(0, r/norm, 0xff);
                 g = qBound(0, g/norm, 0xff);
                 b = qBound(0, b/norm, 0xff);
                 result.setPixel(x,y,qRgb(r,g,b));
             }
        }
    return result;
}

Maskę mam zdefiniowaną w .h

0

Pisałem z palca wiec, brakuje tego i owego.
Ad1. tak brakowało słowa virtual
Ad2. powinno być reinterpret_cast
Ad3. na to będę musiał spojrzeć samo potem. Spokojnie możesz tam wpisać stałą, np. 1.
Ad4. moja implementacja dotyczy konkretnego formatu QImage ta asercja sprawdza, czy format jest poprawny. Masz dwa wyjścia: napisać obsługę innych formatów, albo wykonywać konwersję QImage do formatu obsługiwanego przez moją funkcję.

Ogólnie napisałem funkcję poglądową nad detalami możesz się sam pomęczyć.

0

Pomijając już sposób implementacji największym problemem jest taki że ani mój (ten z 1 postu) ani Twój algorytm nie działa mi poprawnie...a żeby poprawiać detale chciałbym chociaż aby to coś mi w końcu zadziałało bo męczę się z tym któryś dzień a dalej nie wiem gdzie jest błąd.

1

Edytując post poprawiłem swój kod ze wszystkich literówek (było ich troszkę :) ). Poza tym dla wygody zmieniłem format obrazka na QImage::Format_RGB32, bo okazało się, że w formacie QImage::Format_RGB888 piksele są ciasno upakowane, a nie wyrównywane do podwójnego słowa. Przed użyciem tej funkcji należy zadbać by QImage był we właściwym formacie:

if (image.format()!=QImage::Format_RGB32)
    image=image.convertToFormat(QImage::Format_RGB32);

Kod testowałem i raczej działa jak należy.


a tu napisałem maskę którą można edytować za pomocą QTableView (fajny przykład jak należy korzystać z modeli danych):
  • nagłówek:
#ifndef EDITABLEMASK_H
#define EDITABLEMASK_H

#include <QAbstractTableModel>
#include "maska.h"

const int MaxMaskSize = 16;
const int MaxMaskRadius = (MaxMaskSize-1)/2;

class EditableMask : public QAbstractTableModel , public Maska
{
    Q_OBJECT

    Q_PROPERTY(int radius
               READ radius
               WRITE setRadius
               NOTIFY radiusChanged)

    Q_PROPERTY(int norm
               READ norm
               WRITE setNorm
               NOTIFY normChanged
               RESET setDefaultNorm)

public:
    explicit EditableMask(QObject *parent = 0);
    
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;

    bool setData(const QModelIndex &index, const QVariant &value, int role);
    Qt::ItemFlags flags(const QModelIndex &index) const;

    int width() const;
    int height() const;
    int norm() const;
    const int *operator[](int row) const;

    int radius() const;

    int defaultNorm() const;

signals:
    void radiusChanged(int);
    void normChanged(int);
    
public slots:
    void setRadius(int);

    void setNorm(int norma);
    void setDefaultNorm();
    
private:
    int mOffset;
    int mSize;
    mutable int mNorma;
    int mData[MaxMaskSize][MaxMaskSize];
};

#endif // EDITABLEMASK_H
  • plik z kodem:
#include "editablemask.h"

EditableMask::EditableMask(QObject *parent) :
    QAbstractTableModel(parent)
{
    std::fill(mData[0], mData[0]+MaxMaskSize*MaxMaskSize, 0);
    mSize = 3;
    mOffset = MaxMaskRadius - 1;
    mData[MaxMaskRadius][MaxMaskRadius] = 1;
    mNorma = 1;
}

int EditableMask::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return mSize;
}

int EditableMask::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return mSize;
}

QVariant EditableMask::data(const QModelIndex &index, int role) const
{
    switch(role) {
    case Qt::DisplayRole:
    case Qt::EditRole:
        return QString::number(mData[index.row()+mOffset][index.column()+mOffset]);
    default:
        return QVariant();
    }
}

bool EditableMask::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if (role!=Qt::EditRole)
        return false;
    bool ok = false;
    int x = value.toInt(&ok);
    if (ok) {
        mData[index.row()+mOffset][index.column()+mOffset] = x;
        return true;
    }
    return false;
}

Qt::ItemFlags EditableMask::flags(const QModelIndex &index) const
{
    Q_UNUSED(index)
    return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

int EditableMask::width() const
{
    return mSize;
}

int EditableMask::height() const
{
    return mSize;
}

int EditableMask::norm() const
{
    return mNorma;
}

const int *EditableMask::operator [](int row) const
{
    return mData[mOffset+row]+mOffset;
}

int EditableMask::radius() const
{
    return (mSize-1)/2;
}

void EditableMask::setRadius(int newRadius)
{
    int oldRadius = (mSize-1)/2;
    newRadius = qBound(1, newRadius, MaxMaskRadius);
    if (newRadius>0 && oldRadius!=newRadius) {
        int diff = newRadius-oldRadius;
        if (diff>0) {
            beginInsertRows(   QModelIndex(), 0, diff-1);
            beginInsertColumns(QModelIndex(), 0, diff-1);
            mOffset -= diff;
            mSize += diff;
            endInsertRows();
            endInsertColumns();

            beginInsertRows(   QModelIndex(), mSize, mSize + diff-1);
            beginInsertColumns(QModelIndex(), mSize, mSize + diff-1);
            mSize += diff;
            endInsertRows();
            endInsertColumns();
        } else {
            beginRemoveRows(   QModelIndex(), 0, -diff-1);
            beginRemoveColumns(QModelIndex(), 0, -diff-1);
            mOffset -= diff;
            mSize += diff;
            endRemoveRows();
            endRemoveColumns();

            beginRemoveRows   (QModelIndex(), mSize+diff, mSize-1);
            beginRemoveColumns(QModelIndex(), mSize+diff, mSize-1);
            mSize += diff;
            endRemoveRows();
            endRemoveColumns();
        }
        emit radiusChanged(newRadius);
    }
}

void EditableMask::setNorm(int norma)
{
    if (norma == 0) {
        norma = defaultNorm();
        if (norma == 0)
            norma = 1;
    }
    if (norma != mNorma) {
        mNorma = norma;
        emit normChanged(norma);
    }
}

void EditableMask::setDefaultNorm()
{
    setNorm(0);
}

int EditableMask::defaultNorm() const
{
    int sum = 0;
    for(int i=0; i<mSize; ++i)
    for(int j=0; j<mSize; ++j)
        sum += mData[mOffset+i][mOffset+j];
    return sum;
}

Tę maskę można edytować, powiększać pomniejszać

1 użytkowników online, w tym zalogowanych: 0, gości: 1