『OpenCV』从char* 数组中读取Mat,解决恢复出的Mat花屏的问题。

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+jstep[1]
当然我们可以直接用OpenCV的 M.at<float>(i,j)来得到。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值