关于源代码
已经上传,可以通过文章下载。
创作背景
在当前项目中,需要使用opencv来处理从线扫相机保存下来的灰度bmp图像,一张bmp图像的像素大小为16384x84822,文件大小为1.29G,当使用“cv::imread”从本地读取bmp图像时,将会抛出以下错误:
项目环境
操作系统:Windows10
VS版本:VS 2022 + VS Code
OpenCV版本:4.8.0
问题分析和解决方案
问题分析
使用vs code打开opencv的源代码目录(我这里时D:\opencv\opencv4.8.0\sources),根据报错的信息找到“loadsave.cpp”并打开:
static const size_t CV_IO_MAX_IMAGE_PIXELS = utils::getConfigurationParameterSizeT("OPENCV_IO_MAX_IMAGE_PIXELS", 1 << 30);
代码解释:尝试从环境变量“OPENCV_IO_MAX_IMAGE_PIXELS”中读取允许的最大像素大小,如果找不到,则设置为 2的30次方。
看来“imread”读取图像时会验证图像的像素大小,当读取超过1G像素大小的bmp图像时,会报错。
尝试设置通过cmake命令行设置一下项目的环境变量:
# 设置cmake项目的环境变量为2的32次方,也就是4G像素大小
set(ENV{OPENCV_IO_MAX_IMAGE_PIXELS} 4294967296)
message(STATUS "OPENCV_IO_MAX_IMAGE_PIXELS: $ENV{OPENCV_IO_MAX_IMAGE_PIXELS}")
通过cmake重新构建,
日志显示环境变量已经在cmake项目中生效,再次运行测试程序,但还是报同样的错误。
尝试给操作系统添加“OPENCV_IO_MAX_IMAGE_PIXELS”环境变量,添加完成,点击“确定”保存,并退出环境变量窗口:
注释掉 “CMakeLists.txt”中的 set环境变量语句,直接从操作系统的环境变量来获取:
# set(ENV{OPENCV_IO_MAX_IMAGE_PIXELS} 4294967296)
message(STATUS "OPENCV_IO_MAX_IMAGE_PIXELS: $ENV{OPENCV_IO_MAX_IMAGE_PIXELS}")
日志显示无法获取到刚才设置的操作系统的环境变量。原因是,vc code只会在启动的时候,从操作系统加载一次环境变量,后续不会进行自动更新。
关闭vs code,从dos命令行窗口“code .”, 重新打开:
(备注:亲测,我的电脑必须从命令行窗口来打开 vs code,才能读取到操作系统设置的环境变量。通过快捷图标打开vs code,然后从vs code中打开项目目录,是无法获取到系统的环境变量的):
重新构建,并运行测试程序,第一个问题解决了,又来一个,看来bmp图像的编解码也有1G像素大小的限制:
同样打开出错的“grfmt_bmp.cpp”,
这里无法通过设置环境变量进行解决,看来要解决“读取大于1G像素大小的bmp图像”的问题只能通过修改源代码,然后从源代码重新构建库opencv库了。
解决方案
修改并保存源代码
要修改的源代码都在模块“imgcodecs”中,修改后如下图所示:
修改“loadsave.cpp”:
注意:这里不能简单的把“1 << 30”, 修改为 “1 << 32", 因为1默认是32位为有符号整数,符号位占用了1个bit,只剩下31个字节来表示整数大小,进行32位的左移之后,会发生算术溢出。
这里修改为 “size_t(1) << 32”, 和“CV_IO_MAX_IMAGE_PIXELS”保持一致,“size_t”类型不管是64位还是32位的操作系统均定义为无符号整数,如果只是进行32位左移,应该都不是问题。
在vs studio打开源代码给出错误提示:
修改“grfmt_bmp.cpp”:
通过“CMake-GUI”进行项目配置和生成解决方案
关于从源代码构建opencv4.8.0动态库的详细步骤可以参考另一篇文章:
[C++] 详细教程 - opencv4.8.0安装和验证测试 (Windows + Linux)
方式一 - 构建“opencv_world”
先把“BUILD_opencv_world”选中,构建opencv_world动态库,然后CMake项目在依赖于opencv_world库:
打开生成的vs opencv 2022解决方案:
这里发现一个小问题,为什么opencv_world中没有看到修改了源代码的模块“imgcodecs”,为啥呢?会不会导致构建出来的库,修改的代码并不生效?
经过验证,如果采用opencv_world的方式来构建动态库文件,虽然生成的opencv_world vc++项目中看不到“imgcodecs”,“imgcodecs”也模块也会被自动构建,然后添加到opencv_world中去。
opencv的cmake构建过程比较复杂,至于怎么样把“opencv_imgcodecs” 模块添加到opencv_world vc++项目中去,后续有时间再研究一下。
方式二:每个模块单独构建动态库
先把“BUILD_opencv_world”不选中,构建每个模块动态库,然后CMake项目在依赖于代码用的模块(比如opencv_core、opencv_imgcodecs模块)或者依赖所有构建出来的独立模块:
构建生成新的opencv库之后,把“CMakeLists.txt”中的“set(OpenCV_DIR D:/opencv/opencv4.8.0/build)”, 修改为“set(OpenCV_DIR D:/opencv/opencv4.8.0/sources/build/install”), 重新构建和运行测试程序即可。
测试代码:
#include <stdio.h>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main()
{
// 大于1G的灰度bmp图像
Mat img = imread("D:/my_tutorials/SourceCode/big_size_images/scan_camera_img.bmp", cv::IMREAD_COLOR);
if (img.empty())
{
printf("img is not found.");
return -1;
}else
{
// 打印channels、cols、rows、dims、size等相关属性
cout << "\nchannels: " << img.channels() << endl;
cout << "cols: " << img.cols << endl;
cout << "rows: " << img.rows << endl;
cout << "depth: " << img.depth() << endl;
cout << "dims: ";
for (int i = 0; i < img.dims; i++)
{
cout << img.size[i] << " ";
}
cout << endl;
cout << "size: " << img.total() << endl;
return 0;
}
}
下面是测试程序的运行结果: