1. 从char* 构建Mat
1.1 直接读取文件的字符数组中读取
此时传入的 char* data 为直接读取图片文件的字符数组,可以使用以下代码进行Mat构建
/**
* @brief Constructing Mat directly from file byte array.
*
* @param data image file byte array
* @param size The size of image file byte array
* @param out dst Mat
* @param gray Set to true if it is a grayscale image, otherwise false.
*/
void charArrayToMat(const uchar* data, size_t size, cv::Mat& out, bool gray=false);
void charArrayToMat(const uchar* data, size_t size,cv::Mat& out,bool gray) {
if (data == nullptr || size == 0) {
throw std::invalid_argument("Invalid input data");
}
std::vector<uchar> image_bytes(data, data + size);
if (gray) {
cv::imdecode(image_bytes, cv::IMREAD_GRAYSCALE).copyTo(out);
} else {
cv::imdecode(image_bytes, cv::IMREAD_COLOR).copyTo(out );
}
}
2.2 从Mat 的 data数组 恢复Mat
/**
* @brief Constructing Mat directly from other Mat data.
*
* @param data Mat.data
* @param w The width of image
* @param h The Height of image
* @param out dst Mat
* @param gray Set to true if it is a grayscale image,otherwise keep false.
*/
void charArrayToMat(const uchar* data, int w, int h, cv::Mat& out, bool gray=false);
void charArrayToMat(const uchar* data, int w, int h, cv::Mat& out, bool gray) {
if (data == nullptr || w == 0 || h == 0) {
throw std::invalid_argument("Invalid input data");
}
if (gray) {
cv::Mat(h, w, CV_8UC1, (void*)data).copyTo(out);
} else {
cv::Mat(h, w, CV_8UC3, (void*)data).copyTo(out);
}
}
参考内容:
2. 解析后花屏
2.1 问题描述
将Mat 进行裁剪后,传入data后恢复Mat. 代码如下:
cv::Mat img = cv::imread(imgP);
REQUIRE_FALSE(img.empty());
cv::Mat subImg = img(cv::Rect(10, 10, img.cols - 10, img.rows-10));
cv::Mat rstImg;
CVUtils::charArrayToMat((uchar*)subImg.data, subImg.cols, subImg.rows, rstImg);
cv::imshow("TestCropMatParse", rstImg);
cv::waitKey(-1);
2.2 解决方式
将裁剪下的子图,clone 后传入。修改代码:
// 使用clone 进行Mat 拷贝
// cv::Mat subImg = img(cv::Rect(10, 10, img.cols - 10, img.rows-10));
cv::Mat subImg = img(cv::Rect(10, 10, img.cols - 10, img.rows-10)).clone();
结果图:
2.3 原因分析
2.3.1 截取ROI的操作:
cv::Mat subImg = img(cv::Rect(10, 10, img.cols - 10, img.rows - 10));
OpenCV源码:
// core\src\matrix.cpp
Mat::Mat(const Mat& m, const Rect& roi)
: flags(m.flags), dims(2), rows(roi.height), cols(roi.width),
data(m.data + roi.y*m.step[0]),
datastart(m.datastart), dataend(m.dataend), datalimit(m.datalimit),
allocator(m.allocator), u(m.u), size(&rows)
{
CV_Assert( m.dims <= 2 );
size_t esz = CV_ELEM_SIZE(flags);
data += roi.x*esz; // 其实位置指定为 y行的x列
CV_Assert( 0 <= roi.x && 0 <= roi.width && roi.x + roi.width <= m.cols &&
0 <= roi.y && 0 <= roi.height && roi.y + roi.height <= m.rows );
if( roi.width < m.cols || roi.height < m.rows )
flags |= SUBMATRIX_FLAG;
// step并没有变,还是原矩阵的step
step[0] = m.step[0]; step[1] = esz;
updateContinuityFlag();
addref();
if( rows <= 0 || cols <= 0 )
{
rows = cols = 0;
release();
}
}
总结一下:
- 取roi操作,首先是把data 移到子矩阵的开始位置
- 子矩阵的 step仍然是原来矩阵的step
- 子矩阵会被表示为子矩阵
- 如果直接用data 去构造矩阵,这是并不知道原来矩阵的 step, 然后默认当原始矩阵,给了个错的step,这样就造成了花屏。
- 传一个对的 step可以吗?个人觉得没有必要,之所以要传数据,要么是需要网络传输,要么就是要把数据给其他库,所以直接clone,把对应的数据拷贝出来,组成一个连续的数据,更加直接。
3. 关于 Mat 取索引和 cv::Mat::step
一个二维的像素数据,在底层是用一维的数组存储。所以如何组织数据就比较重要了,比如像素属于哪一行哪一列哪一个通道。
这个就要看 Mat 中的step
这个参数了。这里可以查看OpenCV文档的解释
The next important thing to learn about the matrix class is element access. Here is how the matrix is stored. The elements are stored in row-major order (row by row). The cv::Mat::data member points to the first element of the first row, cv::Mat::rows contains the number of matrix rows and cv::Mat::cols - the number of matrix columns. There is yet another member, cv::Mat::step that is used to actually compute address of a matrix element. cv::Mat::step is needed because the matrix can be a part of another matrix or because there can some padding space in the end of each row for a proper alignment.
从这段话里我们可以知道:
- cv::Mat::data是指向第一行第一个元素,也就是矩阵的开始位置
- cv::Mat::rows 和cv::Mat::cols分别存储矩阵的行数和列数
- 矩阵是按行存储的,也就是一行一行的存。
- cv::Mat::step成员用于计算矩阵元素的地址。这是因为cv::Mat 可能是其他Mat的一部分,或者每行末尾适当填充对齐,cv::Mat::step是一个数组,它的每个元素表示对应维度的步长(以字节为单位)。例如 step[0]表示当前行的下一行的步长,step[1]表示同一行下一个元素的步长。
所以第 i
行 第 j
列 的像素数据首地址是:
addr
(
i
,
j
)
=
M
.
d
a
t
a
+
M
.
s
t
e
p
[
0
]
∗
i
+
j
∗
s
t
e
p
[
1
]
\text{addr}(i, j) = M.data + M.step[0] * i + j * step[1]
addr(i,j)=M.data+M.step[0]∗i+j∗step[1]
当然我们可以直接用OpenCV的 M.at<float>(i,j)
来得到。