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*.



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

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-канальный)

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; y<image->height; y++ ) {
		uchar* ptr = (uchar*) (image->imageData + y * image->widthStep);
		for( int x=0; x<image->width; x++ ) {
			// 3 канала 
			ptr[3*x] = 0;     // B - синий
			ptr[3*x+1] = 0;   // G - зелёный
			ptr[3*x+2] = 255; // R - красный
		}
	}


Читать далее: 15. OpenCV шаг за шагом. Сохранение данных в XML
  • 0
  • 27 августа 2010, 10:03
  • noonv

Комментарии (16)

RSS свернуть / развернуть
+
0
Скажи мне, а в последнем примере у тебя image задан как IplImage, CvMat или CvArr?
avatar

gous

  • 14 августа 2011, 07:04
+
0
IplImage
avatar

admin

  • 14 августа 2011, 07:08
+
+1
а, это я дибил. IplImage*.
avatar

gous

  • 14 августа 2011, 07:08
+
0
А вот вопрос от чайника: как загрузить картинку из файла в IplImage пример есть. А как загрузить картинку из файла в cvMat?
И можно ли конвертировать IplImage в cvMat и обратно?
avatar

atlab

  • 20 ноября 2011, 07:52
+
0
разумеется, можно.
можно и из обычного массива грузить данные:
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)
avatar

noonv

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

atlab

  • 20 ноября 2011, 09:43
+
0
Не совсем понятно, что происходит в строчке
uchar* ptr = (uchar*) (image->imageData + y * image->widthStep);
Не могли бы пояснить?
avatar

vargy

  • 2 января 2012, 03:07
+
+1
данные картики хранятся в массиве памяти (imageData), в котором они укладываются строчка за строчкой (при этом в widthStep хранится длина одной такой строчки).
т.о. в коде
uchar* ptr = (uchar*) (image->imageData + y * image->widthStep);
мы получаем указатель на y-ю строчку пикселей изображения (IPL_DEPTH_8U — 8-битного беззнакового — стандартный тип для загруженных картинок)
avatar

noonv

  • 2 января 2012, 07:21
+
+1
Сорри за повторение. Но хотелось бы внести и свою лепту. Как новичок в этом деле долго ( что-то около 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 - красный
                }
        }
avatar

DevOS

  • 25 марта 2012, 12:17
+
+1
В последнем примере (Код для обхода всех пикселей 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;
Т.е. сперва привести указатель на данные к нужному типу, а потом уже его использовать.
За статью спасибо, в своё время здорово помогла, особенно последний пример! :)
avatar

Grunelf

  • 4 июня 2012, 19:43
+
0
Подскажите, будьте добры, если я загружаю изображение, как
cv::Mat mat = cv::imread(filename, CV_LOAD_IMAGE_GRAYSCALE);

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

vcxsaz

  • 27 марта 2013, 23:13
+
0
к отдельному элементу можно так:
mat.at<unsigned char>(h, w);
к отдельной строчке через
mat.ptr(h);
, а к массиву данных через
mat.data
см. cv::Mat
avatar

noonv

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

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

vcxsaz

  • 29 марта 2013, 19:34
+
0
avatar

vcxsaz

  • 29 марта 2013, 19:36
+
0
Отпишитесь, пожалуйста, кто знает.
avatar

vcxsaz

  • 10 апреля 2013, 13:25
+
0
тогда пишите на форум — что хотите сделать, что делаете и т.д.
avatar

noonv

  • 18 апреля 2013, 08:36

Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.