CraftDuino v2.0
  • - это CraftDuino - наш вариант полностью Arduino-совместимой платы.
  • CraftDuino - настоящий конструктор, для очень быстрого прототипирования и реализации идей.
  • Любая возможность автоматизировать что-то с лёгкостью реализуется с CraftDuino!
Просто добавьте CraftDuino!

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

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