14. OpenCV шаг за шагом. Матрица


1. OpenCV шаг за шагом. Введение.
2. Установка.
3. Hello World.
4. Загрузка картинки.
5. Вывод видео
6. Ползунок
7. Захват видео с камеры
8. Запись видео
9. События от мышки
10. Обработка изображения — сглаживание
11. Обработка изображения — изменение размеров
12. ROI — интересующая область изображения
13. Типы данных OpenCV
14. Матрица

CvMat — матрица

Рассмотрим ещё немножко основ. Функции можно просто пробежать глазами — самое интересное в этом шаге — в самом конце приводятся куски кода обхода элементов матрицы и пикселей изображения.

CvArr — массив — его можно считать «абстрактным базовым классом» для CvMat и далее IplImage (CvArr->CvMat->IplImage)

	typedef void CvArr;

Вы уже, наверное, обратили внимание, что прототипы функций OpenCV принимают в качестве параметров указатель на CvArr. Фактически это означает, что они принимают массив CvMat* или изображение IplImage*.

CvMat

//
// матрица
//

typedef struct CvMat
{
    int type;
    int step;

    /* for internal use only */
    int* refcount;
    int hdr_refcount;

    union
    {
        uchar* ptr;
        short* s;
        int* i;
        float* fl;
        double* db;
    } data;

    union
    {
        int rows;
        int height;
    };
    union
    {
        int cols;
        int width;
    };
}
CvMat;

//
// конструктор:
// (данные не резервируются - для этого нужно использовать cvCreateData() 
// или просто использовать cvCreateMat() )
//
CV_INLINE CvMat cvMat( int rows, int cols, int type, void* data CV_DEFAULT(NULL))
{
    CvMat m;

    assert( (unsigned)CV_MAT_DEPTH(type) <= CV_64F );
    type = CV_MAT_TYPE(type);
    m.type = CV_MAT_MAGIC_VAL | CV_MAT_CONT_FLAG | type;
    m.cols = cols;
    m.rows = rows;
    m.step = m.cols*CV_ELEM_SIZE(type);
    m.data.ptr = (uchar*)data;
    m.refcount = NULL;
    m.hdr_refcount = 0;

    return m;
}

пример создания матрицы:

CvMat* mat = cvCreateMatHeader( rows, cols, type );
cvCreateData( mat );
CVAPI(CvMat*)  cvCreateMatHeader( int rows, int cols, int type );

— создание загловка матрицы (без выделения памяти под данные)
rows — число строк матрицы
cols — число столбцов матрицы
type — тип формата матрицы

CVAPI(void)  cvCreateData( CvArr* arr );
CVAPI(void)  cvReleaseData( CvArr* arr );

— резервирует/освобождает массив данных

arr — указатель на заголовок массива

CVAPI(CvMat*)  cvCreateMat( int rows, int cols, int type );

— создане заголовка матрицы и резервирование данных
rows — число строк матрицы
cols — число столбцов матрицы
type — тип формата матрицы:
CV_<глубина в битах><s|u|f>C<число каналов>,
S — знаковый
U — беззнаковый
F — float
например: CV_8UC1 (8-битный, беззнакоый, 1-канальный)</s|u|f>

CVAPI(void)  cvReleaseMat( CvMat** mat );

— освобождает матрицу (данные и заголовок)

mat — двойной указатель на матрицу

CVAPI(void)  cvCreateData( CvArr* arr );

— резервирование данных массива
arr — указатель на заголовок массива

CVAPI(void)  cvReleaseData( CvArr* arr );

— освобождает данных массива
arr — указатель на заголовок массива

CVAPI(void)  cvSetData( CvArr* arr, void* data, int step );

— привязывает данные к заголовку массива (должен быть инициализирован ранее)
arr — указатель на заголовок массива
data — данные
step — полная длина строки в байтах

Рассмотрим 3 способа получения заданного элемента матрицы:
1 — с использованием макроса CV_MAT_ELEM
2 — с использованием функций cvGet2D(), cvGetReal2D()
3 — с использованием прямого доступа к элементам матрицы

// макросы доступа к элементу матрицы

#define CV_MAT_ELEM_PTR_FAST( mat, row, col, pix_size )  \
    (assert( (unsigned)(row) < (unsigned)(mat).rows &&   \
             (unsigned)(col) < (unsigned)(mat).cols ),   \
     (mat).data.ptr + (size_t)(mat).step*(row) + (pix_size)*(col))

#define CV_MAT_ELEM_PTR( mat, row, col )                 \
    CV_MAT_ELEM_PTR_FAST( mat, row, col, CV_ELEM_SIZE((mat).type) )

#define CV_MAT_ELEM( mat, elemtype, row, col )           \
    (*(elemtype*)CV_MAT_ELEM_PTR_FAST( mat, row, col, sizeof(elemtype)))

CV_MAT_ELEM — возвращает заданный элемент матрицы
mat — матрица
elemtype — тип элементов матрицы
row — номер строки
col — номер столбца

CVAPI(uchar*) cvPtr1D( const CvArr* arr, int idx0, int* type CV_DEFAULT(NULL));
CVAPI(uchar*) cvPtr2D( const CvArr* arr, int idx0, int idx1, int* type CV_DEFAULT(NULL) );
CVAPI(uchar*) cvPtr3D( const CvArr* arr, int idx0, int idx1, int idx2,
                      int* type CV_DEFAULT(NULL));
CVAPI(uchar*) cvPtrND( const CvArr* arr, const int* idx, int* type CV_DEFAULT(NULL),
                      int create_node CV_DEFAULT(1),
                      unsigned* precalc_hashval CV_DEFAULT(NULL));

— возвращает указатель на заданный элемент массива
arr — указатель на массив
idx0, idx1, idx2 — индекс элемента
idx — массив индексов
type — указатель для получения типа элемента

CVAPI(CvScalar) cvGet1D( const CvArr* arr, int idx0 );
CVAPI(CvScalar) cvGet2D( const CvArr* arr, int idx0, int idx1 );
CVAPI(CvScalar) cvGet3D( const CvArr* arr, int idx0, int idx1, int idx2 );
CVAPI(CvScalar) cvGetND( const CvArr* arr, const int* idx );

— возвращает заданный элемент массива
arr — указатель на массив
idx0, idx1, idx2 — индекс элемента
idx — массив индексов

CVAPI(double) cvGetReal1D( const CvArr* arr, int idx0 );
CVAPI(double) cvGetReal2D( const CvArr* arr, int idx0, int idx1 );
CVAPI(double) cvGetReal3D( const CvArr* arr, int idx0, int idx1, int idx2 );
CVAPI(double) cvGetRealND( const CvArr* arr, const int* idx );

— возвращает заданный элемент 1-канального массива

arr — указатель на 1-канальный массив
idx0, idx1, idx2 — индекс элемента
idx — массив индексов элемента

CVAPI(void) cvSet1D( CvArr* arr, int idx0, CvScalar value );
CVAPI(void) cvSet2D( CvArr* arr, int idx0, int idx1, CvScalar value );
CVAPI(void) cvSet3D( CvArr* arr, int idx0, int idx1, int idx2, CvScalar value );
CVAPI(void) cvSetND( CvArr* arr, const int* idx, CvScalar value );

— изменение заданного элемента массива
arr — указатель на массив
idx0, idx1, idx2 — индекс элемента
idx — массив индексов
value — новое значение

CVAPI(void) cvSetReal1D( CvArr* arr, int idx0, double value );
CVAPI(void) cvSetReal2D( CvArr* arr, int idx0, int idx1, double value );
CVAPI(void) cvSetReal3D( CvArr* arr, int idx0,
                        int idx1, int idx2, double value );
CVAPI(void) cvSetRealND( CvArr* arr, const int* idx, double value );

— изменение заданного элемента массива
arr — указатель на массив
idx0, idx1, idx2 — индекс элемента
idx — массив индексов
value — новое значение

double  cvmGet( const CvMat* mat, int row, int col )

— возвращает элемент массива (1-канальной матрицы типа float)
arr — указатель на массив
row — номер строки
col — номер столбца

void  cvmSet( CvMat* mat, int row, int col, double value );

— устанавливает элемент массива (1-канальной матрицы типа float)
arr — указатель на массив
row — номер строки
col — номер столбца
value — новое значение

3 способа получения заданного элемента матрицы:

    // покажем содержимое матрицы
//
int i=0, j=0;

// 1 вариант: с использованием макроса CV_MAT_ELEM
for(i=0; i<matrix->rows; i++){
    for(j=0; j<matrix->cols; j++){
        printf("%.0f ", CV_MAT_ELEM( *matrix, float, i, j));
    }
    printf("\n");
}
printf("-----\n");
// 2 вариант: с использованием cvGet2D(), cvGetReal2D()
for(i=0; i<matrix->rows; i++){
    for(j=0; j<matrix->cols; j++){
        printf("%.0f ", cvGet2D(matrix, i, j));//cvGetReal2D(matrix, i, j));
    }
    printf("\n");
}
printf("-----\n");
// 3 вариант: прямой доступ к элементам
for(i=0; i<matrix->rows; i++){
    float* ptr = (float*)(matrix->data.ptr + i*matrix->step);
    for(j=0; j<matrix->cols; j++){
        printf("%.0f ", ptr[j]);
    }
    printf("\n");
}

Код для обхода всех пикселей 3-канального изображения формата RGB (вернее BGR):

        // пробегаемся по всем пикселям изображения
	for( int y=0; yheight; y++ ) {
		uchar* ptr = (uchar*) (image->imageData + y * image->widthStep);
		for( int x=0; xwidth; x++ ) {
			// 3 канала 
			ptr[3*x] = 0;     // B - синий
			ptr[3*x+1] = 0;   // G - зелёный
			ptr[3*x+2] = 255; // R - красный
		}
	}

Читать далее: 15. OpenCV шаг за шагом. Сохранение данных в XML


16 комментариев на «“14. OpenCV шаг за шагом. Матрица”»

  1. А вот вопрос от чайника: как загрузить картинку из файла в IplImage пример есть. А как загрузить картинку из файла в cvMat?
    И можно ли конвертировать IplImage в cvMat и обратно?

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

      memcpy(cv_image->imageData, raw_buf, raw_buf_size);

      или так:

      // помещаем данные в матрицу
      CvMat image_mat =  cvMat(raw_height, raw_width, CV_8UC3, raw_buf);
      IplImage cv_image;
      // преобразуем матрицу в изображение
      cvGetImage(&image_mat, &cv_image);

      , при этом raw_buf — это массив ваших данных в правильной укладке порядка пикселей (BGR)

    • Спасибо за ответ!
      Тяжко на шестом десятке въезжать в С и пытаться его переложить на более привычный Basic 🙂
      Вообще то мне хотелось бы применить cvMinAreaRect для захваченного с web-камеры изображения (микросхемы) чтобы определить угол ее поворота.
      Захват, определение границ контура (по картинке из файла) работают, теперь надо это все вместе соединить и voila 🙂
      Добрался до 32 статьи цикла, похоже это там?

    • данные картики хранятся в массиве памяти (imageData), в котором они укладываются строчка за строчкой (при этом в widthStep хранится длина одной такой строчки).
      т.о. в коде

      uchar* ptr = (uchar*) (image->imageData + y * image->widthStep);

      мы получаем указатель на y-ю строчку пикселей изображения (IPL_DEPTH_8U — 8-битного беззнакового — стандартный тип для загруженных картинок)

  2. В последнем примере (Код для обхода всех пикселей 3-канального изображения) ошибка, правда при данных условиях (тип изображения 8UC) она не проявится, а проявится если, например создать картинку с данными типа float:
    cvCreateImage(cvSize(cols, rows), IPL_DEPTH_32F, 1);
    Короче вместо
    uchar* ptr = (uchar*) (image->imageData + y * image->widthStep);
    следует писать так
    uchar* ptr = (uchar*) (image->imageData) + y * image->widthStep;
    Т.е. сперва привести указатель на данные к нужному типу, а потом уже его использовать.
    За статью спасибо, в своё время здорово помогла, особенно последний пример! 🙂

  3. Сорри за повторение. Но хотелось бы внести и свою лепту. Как новичок в этом деле долго ( что-то около 15 минут 🙂 ) разбирался в последнем куске кода 🙂
    Чтобы было более понятно, приведу этот кусок кода с некоторыми комментариями.

    // пробегаемся по всем пикселям изображения
            for( int y=0; y<image->height; y++ ) {
                    int nChan = image->nChannels;  // Определить количество каналов, чтобы реализация не зависела от пользователя. 
    // Мало ли сколько там каналов :) Не самому же пользователю за всем следить :)
    
                    uchar* ptr = (uchar*) (image->imageData + y * image->widthStep);  
    // widthStep - расстояние между соседними по вертикали точками изображения (число байт в одной строчке картинки). 
    // Хотя это, скорее всего, число элементов (uchar) в одной строке массива данных. 
    // Соответственно, для данного случая (при image->imageData == начало массива данных) по арифметике указателей
    // ptr будет указывать на начало каждой строки массива данных картинки
    
                    for( int x=0; x<image->width; x++ ) {
                             // количество каналов вложили в nChan
                             // пробегаемся по всем каналам каждого пикселя
                             // конкретно здесь устанавливаются значения каналов каждого пикселя :)
                            ptr[nChan*x] = 0;     // B - синий
                            ptr[nChan*x+1] = 0;   // G - зелёный
                            ptr[nChan*x+2] = 255; // R - красный
                    }
            }
  4. Подскажите, будьте добры, если я загружаю изображение, как

    cv::Mat mat = cv::imread(filename, CV_LOAD_IMAGE_GRAYSCALE);

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

    • к отдельному элементу можно так:

      mat.at<unsigned char>(h, w);

      к отдельной строчке через

      mat.ptr(h);

      , а к массиву данных через

      mat.data

      см. cv::Mat

    • Я пробовал такой вариант, но возвращает какие-то нечленораздельные символы. Вот пример:

      Слева пример вывода в файл, справа — на консоль.

Добавить комментарий

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Android Arduino Bluetooth CraftDuino DIY IDE iRobot Kinect OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака телеприсутствие управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение