读入批量图像+切割视频为规定格式的帧图像

读入批量图像

读入两帧图像直接使用imread即可,但是读入文件夹内的图像文件夹有点复杂。

一、十四讲第八章使用LK光流法

在视觉SLAM十四讲第八章使用LK光流法时,第一帧图像提取FAST特征点,之后的帧使用LK跟踪特征点,需要批量导入图像。

这里提供的方式如下:

string path_to_dataset="../TUM";
string associate_file=path_to_dataset+"/associate.txt";// string+"" 复合
ifstream fin(associate_file);  //string转流

string rgb_file, depth_file,time_rgb,time_depth;
for (int index=0;index<100;index++)
{
 fin>>rgb_file>>depth_file>>time_rgb>>time_depth;// 流的功能>>
 color=cv::imread(path_to_dataset+"/"+rgb_file);// imread参数为string类型
 depth=cv::imread(path_to_dataset+"/"+depth_file,-1);
}

1、其中,associate.txt对depth和rgb图像的采集时间进行配对,时间上对齐。

运行:python associate.py rgb.txt depth.txt > associate.txt

2、imread(flag); 彩色图像为8位3通道,深度图为16位单通道图像。

flags = 1:imread按三通道方式读入图像,即彩色图像

flags = 0:imread按单通道的方式读入图像,即灰白图像

flags = -1:imread按解码得到的方式读入图像

3、groundtruth.txt为标准轨迹,外部运动捕捉系统采集到的相机位姿。(time,tx,ty,tz,qx,qy,qz,qw)


二、VINS-mono读取图像

void PubImuData()
{
	string sImu_data_file = sConfig_path + "MH_05_imu0.txt";// string+""复合

	ifstream fsImu;// 流形式fsImu
	fsImu.open(sImu_data_file.c_str());//string转char*
	if (!fsImu.is_open())
	{
		cerr << "Failed to open imu file! " << sImu_data_file << endl;
		return;
	}

	std::string sImu_line;// 每一行imu数据string类型
	double dStampNSec = 0.0;
	Vector3d vAcc;
	Vector3d vGyr;
	while (std::getline(fsImu, sImu_line) && !sImu_line.empty()) // read imu data,getline出来的是string形式
	{
		std::istringstream ssImuData(sImu_line);// string转流形式
		ssImuData >> dStampNSec >> vGyr.x() >> vGyr.y() >> vGyr.z() >> vAcc.x() >> vAcc.y() >> vAcc.z();
		// cout << "Imu t: " << fixed << dStampNSec << " gyr: " << vGyr.transpose() << " acc: " << vAcc.transpose() << endl;
		pSystem->PubImuData(dStampNSec / 1e9, vGyr, vAcc);
		usleep(5000*nDelayTimes);
	}
	fsImu.close();
}


void PubImageData()
{
	string sImage_file = sConfig_path + "MH_05_cam0.txt";// string+""复合

	cout << "1 PubImageData start sImage_file: " << sImage_file << endl;

	ifstream fsImage;// 流形式
	fsImage.open(sImage_file.c_str());//string转char*
	if (!fsImage.is_open())
	{
		cerr << "Failed to open image file! " << sImage_file << endl;
		return;
	}

	std::string sImage_line;
	double dStampNSec;
	string sImgFileName;

    // 循环读取每一行getline出来为string类型
	while (std::getline(fsImage, sImage_line) && !sImage_line.empty())
	{
		std::istringstream ssImuData(sImage_line);//流文件
		ssImuData >> dStampNSec >> sImgFileName;
		// cout << "Image t : " << fixed << dStampNSec << " Name: " << sImgFileName << endl;
		string imagePath = sData_path + "cam0/data/" + sImgFileName;

		Mat img = imread(imagePath.c_str(), 0);
		if (img.empty())
		{
			cerr << "image is empty! path: " << imagePath << endl;
			return;
		}
		pSystem->PubImageData(dStampNSec / 1e9, img);
		// cv::imshow("SOURCE IMAGE", img);
		// cv::waitKey(0);
		usleep(50000*nDelayTimes);
	}
	fsImage.close();
}

三、简单的视觉里程计

在实现里,需要读入KITTI的图像。

要求:图像为顺序排列1、2、3....

char filename[100];
for(int index=2;index<100;index++)
{
 sprintf(filename,"/home/avisingh/Datasets/KITTI_VO/00/image_2/%06d.png",index);
 Mat currImage_c=imread(filename);
 cvtColor(currImage_c,currImage,COLOR_BGR2GRAY);
}

1、sprintf()把格式化的数据写入某个字符串中

2、照片格式为000011.png,所以这里用sprintf(filename,"../%06d.png",index)形式,字长6位,不够的前面补零

     相同效果的是:

     stringstream ss;

     ss << "../"  << setw(6) << setfill('0') << index<< ".png";

    Mat img(imread(ss.str(),0));


切割视频

#include <iostream>  
#include <vector>  
#include "opencv2\highgui\highgui.hpp"  
#include <string>
#include <iomanip>//setw和setfill要用

using namespace std;
using namespace cv;

void video2image(string video, string path)
{
	VideoCapture capture(video);
	if (!capture.isOpened())
	{
		cerr << "Failed to open a video" << endl;
		return;
	}

	//设置开始帧、结束帧、当前帧初始化为初始帧
	long frameToStart = 40;
	int frameToStop = 200;
	long currentFrame = frameToStart;

	Mat frame;
	int num = 1;
	string filename;
	
	bool stop = false;//标志位
	while (!stop)
	{
		capture >> frame;//视频流读入帧中
		if (frame.empty())//读完,直接break
			break;

		stringstream str;//以流的形式
		str << path << setw(5) << setfill('0') << num << ".jpg";//命名从1开始计数

		cout << "now is reading" << currentFrame << "." << endl;
		imshow("Extractedframe", frame);

		cout << "now is writing" << currentFrame << "." << endl;
		imwrite(str.str(), frame);

		//按下ESC或者到达指定的结束帧后退出读取视频  
		if ( currentFrame > frameToStop)
		{
			stop = true;
		}

		num++;//命名从1开始计数
		currentFrame++;//当前帧计数
	}
	capture.release();//关闭视频文件
	waitKey(0);
}

int main(int argc, char** argv)
{
	string videoFromfile = "./2.avi";  //读取视频  
	string Imagespath = "./2/";    // 保存图片的文件夹路径一定要有,因为OpenCV不会自动创建文件夹  
	video2image(videoFromfile, Imagespath);
	return 0;

}

 

from osgeo import gdal, osr import numpy as np import os import tempfile import shutil def merge_tiff_files_with_color(input_files, output_file, resample_alg='nearest', compress='LZW'): """ 合并多个TIFF文件(具有不同分辨率和波段数)为一个TIFF文件,保留原始颜色并生成12级金字塔 参数: input_files: 输入文件路径列表 output_file: 输出文件路径 resample_alg: 重采样方法,默认为'nearest'(最邻近) compress: 压缩方式,默认为'LZW' """ if not input_files: raise ValueError("输入文件列表为空") # 创建临时目录处理中间文件 temp_dir = tempfile.mkdtemp() try: # 步骤1: 分析输入文件并处理颜色表 rgb_input_files = [] color_table = None same_color_table = True first_file = True for i, file in enumerate(input_files): ds = gdal.Open(file) if ds is None: raise Exception(f"无法打开文件:{file}") # 检查波段数和颜色表 band = ds.GetRasterBand(1) ct = band.GetColorTable() num_bands = ds.RasterCount # 如果是单波段且有颜色表 if num_bands == 1 and ct is not None: if first_file: color_table = ct.Clone() # 保存第一个文件的颜色表 first_file = False else: # 检查颜色表是否一致 if not same_color_table or not ct.IsSame(color_table): same_color_table = False # 转换为RGB保留颜色 rgb_file = os.path.join(temp_dir, f"rgb_{i}.tif") gdal.Translate(rgb_file, ds, bandList=[1, 1, 1], outputType=gdal.GDT_Byte, creationOptions=["PHOTOMETRIC=RGB"]) rgb_input_files.append(rgb_file) else: # 多波段文件直接使用(假设已经是RGB) rgb_input_files.append(file) ds = None # 步骤2: 计算所有输入文件中的最高分辨率(最小像元大小)和目标范围(并集) min_pixel_size_x = None min_pixel_size_y = None all_extents = [] # 每个元素为(minX, maxX, minY, maxY) for file in rgb_input_files: ds = gdal.Open(file) geotransform = ds.GetGeoTransform() if geotransform is None: ds = None raise Exception(f"文件{file}没有地理变换信息。") # 计算分辨率(绝对值) pixel_size_x = abs(geotransform[1]) pixel_size_y = abs(geotransform[5]) # 更新最小分辨率 if min_pixel_size_x is None or pixel_size_x < min_pixel_size_x: min_pixel_size_x = pixel_size_x if min_pixel_size_y is None or pixel_size_y < min_pixel_size_y: min_pixel_size_y = pixel_size_y # 计算当前影像的范围 minX = geotransform[0] maxX = minX + geotransform[1] * ds.RasterXSize minY = geotransform[3] + geotransform[5] * ds.RasterYSize maxY = geotransform[3] if geotransform[5] > 0: # 处理罕见情况 minY, maxY = maxY, minY all_extents.append((minX, maxX, minY, maxY)) ds = None # 计算所有范围的最小外接矩形(并集) all_minX = min([extent[0] for extent in all_extents]) all_maxX = max([extent[1] for extent in all_extents]) all_minY = min([extent[2] for extent in all_extents]) all_maxY = max([extent[3] for extent in all_extents]) # 步骤3: 设置Warp选项 creationOptions = ['TILED=YES', 'BIGTIFF=YES'] if compress is not None: creationOptions.append(f'COMPRESS={compress}') warp_options = gdal.WarpOptions( format='GTiff', outputBounds=(all_minX, all_minY, all_maxX, all_maxY), xRes=min_pixel_size_x, yRes=min_pixel_size_y, resampleAlg=resample_alg, creationOptions=creationOptions, dstSRS=None, multithread=True, targetAlignedPixels=True, ) # 执行Warp合并 ds = gdal.Warp(output_file, rgb_input_files, options=warp_options) # 如果所有输入文件有相同的颜色表,应用到输出文件 if same_color_table and color_table is not None: output_band = ds.GetRasterBand(1) output_band.SetColorTable(color_table) ds = None # 关闭文件,写入磁盘 # 步骤4: 为合并后的TIFF生成12级金字塔 print("开始生成12级金字塔...") ds = gdal.Open(output_file, gdal.GA_Update) if ds is None: raise Exception(f"无法打开输出文件以生成金字塔: {output_file}") # 设置金字塔配置选项 gdal.SetConfigOption('BIGTIFF_OVERVIEW', 'IF_NEEDED') # 支持大文件 if compress: gdal.SetConfigOption('COMPRESS_OVERVIEW', compress) # 应用压缩 # 定义12级金字塔层级 (2^1 到 2^12) overview_levels = [2 ** i for i in range(1, 13)] # 生成金字塔(使用最近邻采样法) ds.BuildOverviews("NEAREST", overview_levels) # 关闭数据集 ds = None print("金字塔生成完成!") return output_file finally: # 清理临时目录 shutil.rmtree(temp_dir, ignore_errors=True) # 使用示例 if __name__ == "__main__": input_files = [ r"F:\曹妃甸水域正射\二维\冯晨2\image_pyramid.tif", r"F:\曹妃甸水域正射\25-08-07-大学城-水域2\8-7-管委会-image_pyramid.tif", r"F:\曹妃甸水域正射\二维\B河\B河_image_pyramid.tif", r"F:\曹妃甸水域正射\25-08-01-大学城-水域1\image_pyramid.tif", r"F:\曹妃甸水域正射\二维\冯晨3\image_pyramid.tif", r"F:\曹妃甸水域正射\二维\主河道\主河道_image_pyramid.tif" ] output_file = r"E:\游戏\merged_output_with_color.tif" merge_tiff_files_with_color(input_files, output_file) print(f"合并完成,结果保存至: {output_file}") 保持基本功能不变,添加一个为合并后的Tif文件去黑边的功能,给我添加后的完整代码
最新发布
08-20
<think>我们现有的脚本是合并多个TIFF文件,但合并后的图像有黑边(可能是无效值区域)。我们需要在合并后添加去除黑边的功能。 去除黑边通常是指裁剪掉图像周围的无效值区域(例如,值为0或NODATA的区域)。我们可以通过以下步骤实现: 1. 读取合并后的TIFF文件。 2. 找到有效数据的边界(即非黑边区域)。 3. 根据边界裁剪图像。 4. 保存裁剪后的图像。 我们可以使用GDAL来完成这些操作。具体步骤包括: - 打开合并后的TIFF文件,读取第一个波段(假设黑边在该波段中表现为0或特定NODATA值)。 - 扫描图像,找到包含非零(或非NODATA)数据的最小矩形区域。 - 使用gdal_translate命令或编程方式裁剪该矩形区域。 注意:如果图像有多个波段,且每个波段都有相同的黑边,那么我们可以只根据第一个波段确定边界,然后对所有波段进行裁剪。 我们将修改脚本,在合并步骤之后添加一个裁剪步骤。 现有合并脚本(参考引用[2])可能是将多个TIFF文件叠加(镶嵌)为一个文件,但未处理黑边。现在我们要在合并后立即进行裁剪。 步骤: 1. 完成多个TIFF文件的合并(使用gdal.Warp或gdal_merge.py的方式)。 2. 对合并后的文件进行裁剪(去除黑边)。 裁剪去除黑边的具体方法: 假设黑边为0(如果不是0,则可能是其他NODATA值,需要根据实际情况调整): - 读取图像数组,找到非零(或有效值)的区域。 - 计算这个区域的最小外接矩形(即边界框)。 - 使用gdal.Translate函数,根据边界框进行裁剪。 下面是一个去除黑边的函数示例(假设黑边值为0,且没有NODATA值设置): def remove_black_border(input_path, output_path): # 打开数据集 ds = gdal.Open(input_path, gdal.GA_ReadOnly) if ds is None: raise Exception("无法打开文件: " + input_path) # 获取第一个波段 band = ds.GetRasterBand(1) # 读取整个波段为数组 arr = band.ReadAsArray() # 找到非零元素的位置 rows, cols = np.where(arr != 0) # 注意:如果黑边是0,非0则是有效区域;如果黑边是其他值,则调整条件 if len(rows) == 0 or len(cols) == 0: # 整个图像都是黑边,无法裁剪 print("警告:整个图像都是黑边,跳过裁剪") ds = None return # 计算边界框 min_row, max_row = np.min(rows), np.max(rows) min_col, max_col = np.min(cols), np.max(cols) # 计算边界框的地理坐标(从像素坐标转换) # 注意:图像左上角是原点 transform = ds.GetGeoTransform() x_min = transform[0] + min_col * transform[1] + min_row * transform[2] y_min = transform[3] + min_col * transform[4] + min_row * transform[5] x_max = transform[0] + (max_col+1) * transform[1] + (max_row+1) * transform[2] y_max = transform[3] + (max_col+1) * transform[4] + (max_row+1) * transform[5] # 注意:由于旋转和倾斜,上述简单计算可能不准确,但通常我们假设没有旋转(即transform[2]和transform[4]为0),所以可以简化 # 如果图像有旋转,则需要使用四个角点计算。但通常裁剪时我们使用像素窗口,所以使用像素坐标裁剪更简单(避免地理坐标计算错误)。 # 因此,我们也可以直接使用像素窗口进行裁剪(即指定偏移量和大小) # 使用gdal.Translate,通过srcWin参数指定窗口([左上角x, 左上角y, 宽度, 高度]) # 左上角x: min_col, 左上角y: min_row, 宽度: max_col - min_col + 1, 高度: max_row - min_row + 1 # 使用像素窗口裁剪 options = gdal.TranslateOptions(srcWin=[min_col, min_row, max_col-min_col+1, max_row-min_row+1]) gdal.Translate(output_path, ds, options=options) ds = None 但是,请注意: - 如果合并后的图像非常大,将整个波段读入内存可能造成负担。我们可以考虑逐块扫描,但为了简单,假设图像可以读入内存。 - 如果黑边不是0,而是其他值(如NODATA值),那么我们需要先获取波段的NODATA值,然后使用该值判断。例如: nodata = band.GetNoDataValue() if nodata is None: # 没有设置NODATA,则使用0作为黑边值 nodata = 0 rows, cols = np.where(arr != nodata) 另外,如果合并后的图像已经设置了NODATA值,那么裁剪时我们希望保留这个NODATA值,并且裁剪后仍然设置。 修改计划: 1. 在合并多个TIFF文件后,将合并结果保存为一个临时文件(或直接覆盖)。 2. 调用上述函数去除黑边,得到最终结果。 注意:如果合并后的图像没有黑边,则裁剪操作不会改变图像。 因此,我们可以在现有脚本的合并步骤后面添加这个裁剪步骤。 但是,请注意引用[2]中的合并操作可能是使用gdal.Warp,它本身有一个裁剪选项(比如根据有效数据自动裁剪),我们可以尝试在合并时设置裁剪参数,避免合并后再次处理。但这里我们按照用户要求,在合并后添加去除黑边的功能。 在合并时使用gdal.Warp的自动裁剪:实际上,gdal.Warp有一个参数`cropToCutline`,但那是针对剪切线的。对于镶嵌后的黑边,我们可以使用`-crop_to_cutline`选项,但需要指定一个切割线,而我们没有切割线。因此,我们选择在合并后单独进行裁剪。 另一种方法:在合并时设置`-srcnodata`和`-dstnodata`,然后使用`-cutline`选项,但这里没有现成的切割线。 因此,我们采用合并后裁剪的方法。 步骤整合: 1. 合并多个TIFF文件(使用gdal.Warp或gdal_merge)到一个临时文件(或直接指定输出文件)。 2. 调用去除黑边函数,读取这个临时文件,然后裁剪并保存为最终文件。 注意:如果合并后的文件很大,我们可以考虑在内存中处理,但为了降低内存消耗,我们使用临时文件。 修改后的脚本流程: 输入:多个TIFF文件(或一个目录下的多个文件) 输出:合并并去除黑边后的TIFF文件 步骤: a. 合并所有输入TIFF文件,生成一个合并后的文件(例如:merged_temp.tif)。 b. 调用remove_black_border函数,输入为merged_temp.tif,输出为最终文件(例如:merged_final.tif)。 c. 删除临时文件(可选)。 注意:如果合并后的文件已经是最终输出,那么我们可以将去除黑边后的文件覆盖原文件,或者直接输出到最终文件。 下面我们修改脚本,在合并步骤后添加去除黑边的功能。 由于引用[2]中并没有给出完整的合并脚本,我们假设合并脚本如下(参考GDAL的典型用法): import gdal import os from osgeo import gdal, osr import numpy as np 然后,我们定义合并函数和去除黑边函数。 注意:为了使用numpy,需要安装numpy。 我们重新组织脚本,添加去除黑边功能。 以下是修改后的脚本框架: 步骤: 1. 读取输入文件列表(可以是多个文件或一个目录下的所有tif文件)。 2. 合并这些文件(使用gdal.Warp)。 3. 去除黑边。 具体代码实现: 首先,我们编写一个函数来合并多个TIFF文件(使用gdal.Warp进行镶嵌): def merge_tiff_files(input_files, output_path): # 设置合并选项 # 这里我们假设所有输入文件具有相同的投影和波段数 # 使用gdal.Warp将多个文件合并到输出路径 if len(input_files) == 0: return # 构建虚拟数据集(VRT)也可以,但直接使用Warp合并 opts = gdal.WarpOptions(format='GTiff') gdal.Warp(output_path, input_files, options=opts) 然后,编写去除黑边函数(如上所述,但需要导入numpy)。 注意:去除黑边函数中,我们假设黑边为0,且没有设置NODATA。如果实际情况不同,可能需要调整。 最后,主流程: input_files = [...] # 获取输入文件列表 temp_output = "merged_temp.tif" final_output = "merged_final.tif" # 最终输出文件 # 合并 merge_tiff_files(input_files, temp_output) # 去除黑边 remove_black_border(temp_output, final_output) # 删除临时文件(可选) os.remove(temp_output) 但是,如果合并后的文件已经很大,且磁盘空间有限,我们可以考虑在内存中进行裁剪?但gdal.Translate不支持直接从一个数据集到另一个数据集而不经过磁盘,所以我们可以使用内存映射(如/vsimem/),但这里为了简单,我们使用临时文件。 另外,注意:如果合并后的图像没有黑边,那么裁剪操作相当于复制整个图像,这样会多一次复制操作。为了效率,我们可以先判断是否有黑边,再决定是否裁剪。但为了代码简单,我们每次都进行裁剪。 现在,我们考虑如何将去除黑边功能整合到用户现有的脚本中。用户现有的脚本可能是引用[4]中的脚本(用于金字塔切片),但用户要求的是在合并后去除黑边。 因此,我们假设用户已经有一个合并脚本,现在需要添加去除黑边的步骤。 修改建议: 在合并操作完成后,立即调用去除黑边的函数。 注意:如果用户需要保留地理坐标信息,那么裁剪后的图像仍然需要保留地理坐标信息。我们的去除黑边函数中,使用gdal.Translate时会自动保留裁剪后的地理坐标(因为使用了原图像的地理变换和裁剪窗口,所以会计算新的地理变换)。 但是,如果原图像有旋转或倾斜(即地理变换的旋转项不为0),那么使用srcWin裁剪可能不准确?因为srcWin是在像素坐标系下,而旋转后的图像像素坐标和地理坐标不是简单的线性关系(但gdal.Translate会处理)。所以,这种方法适用于无旋转的图像(大多数情况)。如果图像有旋转,则应该使用地理坐标边界框进行裁剪(使用projWin参数)。但计算地理坐标边界框比较复杂。 因此,我们改进去除黑边函数,使其支持旋转图像: 方法一:使用像素窗口(srcWin)裁剪,因为旋转图像在数据数组中也是旋转的,所以像素窗口裁剪会直接裁剪数据数组,同时gdal会调整地理变换。这种方法在旋转图像中也能正确裁剪,因为裁剪的是像素数组,然后根据新的左上角位置和旋转角度重新计算地理变换。所以,我们使用srcWin是安全的。 方法二:使用地理坐标边界框(projWin)。但我们需要根据像素坐标计算四个角点的地理坐标,然后取最小外接矩形(但这样计算复杂,且可能不是轴对齐的矩形)。 我们使用方法一,因为gdal.Translate的srcWin参数会自动处理旋转和仿射变换。 因此,上面的去除黑边函数(使用srcWin)是通用的。 但是,注意:在计算边界框时,我们使用的是第一个波段的数组,并假设所有波段具有相同的有效区域。如果波段间有效区域不同,那么这种方法可能不适用。但通常,合并后的图像各波段的黑边是一致的。 综上所述,我们提供以下完整代码示例: 注意:需要安装gdal和numpy。 代码示例: """ import os import numpy as np from osgeo import gdal def remove_black_border(input_path, output_path, nodata_value=None): """ 去除黑边(无效值区域) :param input_path: 输入TIFF文件路径 :param output_path: 输出TIFF文件路径 :param nodata_value: 如果指定,则使用此值作为无效值;如果不指定,尝试从波段中读取;如果波段没有设置,则使用0。 """ ds = gdal.Open(input_path, gdal.GA_ReadOnly) if ds is None: raise Exception("无法打开文件: " + input_path) band = ds.GetRasterBand(1) if nodata_value is None: nodata_value = band.GetNoDataValue() if nodata_value is None: nodata_value = 0 # 默认将0视为无效值(黑边) # 读取整个波段 arr = band.ReadAsArray() # 找到非无效值的位置 if nodata_value is not None: valid_mask = (arr != nodata_value) else: valid_mask = (arr != 0) rows, cols = np.where(valid_mask) if len(rows) == 0: print("警告:整个图像都是无效值,跳过裁剪") ds = None # 复制原文件到输出路径 if input_path != output_path: ds = gdal.Open(input_path) driver = gdal.GetDriverByName('GTiff') driver.CreateCopy(output_path, ds) ds = None return min_row, max_row = np.min(rows), np.max(rows) min_col, max_col = np.min(cols), np.max(cols) width = max_col - min_col + 1 height = max_row - min_row + 1 # 使用像素窗口裁剪 opts = gdal.TranslateOptions(srcWin=[min_col, min_row, width, height]) gdal.Translate(output_path, ds, options=opts) ds = None def merge_tiff_files(input_files, output_path): """ 合并多个TIFF文件 :param input_files: 输入文件列表 :param output_path: 输出文件路径 """ if len(input_files) == 0: return # 使用gdal.Warp进行合并 # 设置选项,例如格式为GTiff warp_options = gdal.WarpOptions(format='GTiff') gdal.Warp(output_path, input_files, options=warp_options) def main(input_path, output_path): # 如果input_path是目录,则获取目录下所有tif文件;如果是文件,则直接使用 if os.path.isdir(input_path): input_files = [os.path.join(input_path, f) for f in os.listdir(input_path) if f.endswith('.tif')] else: input_files = [input_path] # 单个文件 if len(input_files) == 0: print("没有找到TIFF文件") return # 创建临时文件路径 temp_output = "temp_merged.tif" # 合并 merge_tiff_files(input_files, temp_output) # 去除黑边 remove_black_border(temp_output, output_path) # 删除临时文件 if os.path.exists(temp_output): os.remove(temp_output) if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description='合并多个TIFF文件并去除黑边') parser.add_argument('--input', type=str, required=True, help='输入TIFF文件或目录') parser.add_argument('--output', type=str, required=True, help='输出TIFF文件路径') args = parser.parse_args() main(args.input, args.output) 注意:这个脚本假设黑边是无效值(0或NODATA值)。如果实际情况中黑边不是0,也没有设置NODATA,那么需要用户指定无效值。我们可以修改remove_black_border函数,增加一个参数让用户指定,或者在主程序中增加选项。 另外,如果图像非常大,读取整个波段到内存可能会导致内存不足。对于大图像,我们可以考虑分块读取,逐步确定边界。这里提供一个分块读取的方法: def remove_black_border_large(input_path, output_path, nodata_value=None): ds = gdal.Open(input_path, gdal.GA_ReadOnly) band = ds.GetRasterBand(1) if nodata_value is None: nodata_value = band.GetNoDataValue() if nodata_value is None: nodata_value = 0 # 获取图像大小 width = band.XSize height = band.YSize # 初始化边界 min_col = width max_col = 0 min_row = height max_row = 0 # 分块读取,块大小可以设置 block_size = 4096 # 每次读取的块大小 for i in range(0, height, block_size): # 计算当前块的高度 h = min(block_size, height - i) for j in range(0, width, block_size): w = min(block_size, width - j) # 读取一个块 arr = band.ReadAsArray(j, i, w, h) if arr is None: continue # 在当前块中查找有效值 if nodata_value is not None: valid_mask = (arr != nodata_value) else: valid_mask = (arr != 0) if np.any(valid_mask): # 找到有效值的位置 rows, cols = np.where(valid_mask) if len(rows) > 0: # 注意:这些坐标是相对于当前块的 # 转换为全局坐标 rows += i cols += j min_row = min(min_row, np.min(rows)) max_row = max(max_row, np.max(rows)) min_col = min(min_col, np.min(cols)) max_col = max(max_col, np.max(cols)) # 如果整个图像都没有有效值 if min_row == height: print("警告:整个图像都是无效值,跳过裁剪") # ... 复制原图像 ... return # 裁剪 opts = gdal.TranslateOptions(srcWin=[min_col, min_row, max_col-min_col+1, max_row-min_row+1]) gdal.Translate(output_path, ds, options=opts) 这样,可以避免一次性读取整个大图像。 根据用户需求,可以选择使用分块读取的函数。 因此,我们可以在脚本中根据图像大小决定是否分块,但为了简单,我们让用户自己选择。或者,我们默认使用分块读取的方法,以避免内存问题。 修改remove_black_border函数为分块版本(如上),并替换原来的函数。 注意:分块读取可能会慢一些,但可以处理大图像。 综上所述,我们提供了一个完整的脚本,用于合并多个TIFF文件并去除黑边。 用户需要将这段代码整合到自己的脚本中,或者直接使用。 注意:如果用户希望保留地理坐标信息,我们的裁剪操作会保留(因为gdal.Translate会自动调整地理变换)。 如果用户不需要保留地理坐标信息,那么可以在去除黑边后转换为其他格式(如jpg),但用户要求的是合并后去除黑边,所以输出仍然是TIFF。 最后,我们生成相关问题。</think>### 在GDAL Python脚本中添加去除黑边功能 在合并多个TIFF文件后去除黑边(无效值区域)的核心思路是:**识别有效数据区域 → 计算最小外接矩形 → 裁剪图像**。以下是完整的解决方案: ```python from osgeo import gdal, ogr, osr import numpy as np import os def remove_black_borders(input_tif, output_tif, nodata_value=0): """ 去除TIFF文件的黑边(无效值区域) :param input_tif: 输入TIFF文件路径 :param output_tif: 输出TIFF文件路径 :param nodata_value: 被视为黑边的值(通常为0) """ # 打开源文件 src_ds = gdal.Open(input_tif, gdal.GA_ReadOnly) if src_ds is None: raise RuntimeError("无法打开文件: " + input_tif) # 获取第一个波段(假设所有波段有相同的无效区域) band = src_ds.GetRasterBand(1) nodata = band.GetNoDataValue() target_value = nodata if nodata is not None else nodata_value # 读取为NumPy数组 arr = band.ReadAsArray() # 查找有效数据区域 valid_pixels = np.where(arr != target_value) if len(valid_pixels[0]) == 0: print("警告:未找到有效数据,跳过裁剪") return # 计算有效数据边界 min_row, max_row = np.min(valid_pixels[0]), np.max(valid_pixels[0]) min_col, max_col = np.min(valid_pixels[1]), np.max(valid_pixels[1]) width = max_col - min_col + 1 height = max_row - min_row + 1 # 设置裁剪选项 translate_options = gdal.TranslateOptions( srcWin=[min_col, min_row, width, height], # [x偏移, y偏移, 宽度, 高度] format='GTiff', noData=target_value ) # 执行裁剪 gdal.Translate(output_tif, src_ds, options=translate_options) print(f"已成功去除黑边并保存到: {output_tif}") # 示例使用 if __name__ == "__main__": # 合并后的文件路径 merged_file = "merged.tif" # 最终输出路径 output_file = "merged_cropped.tif" # 调用去除黑边函数 remove_black_borders(merged_file, output_file) ``` ### 整合到现有工作流的步骤 1. **在合并操作后调用此函数**: ```python # 现有合并代码(示例) input_files = ["file1.tif", "file2.tif"] merged_output = "merged.tif" gdal.Warp(merged_output, input_files, format="GTiff") # 添加去除黑边操作 final_output = "final_merged.tif" remove_black_borders(merged_output, final_output) ``` 2. **关键参数说明**: - `nodata_value`:根据实际情况调整(通常为0) - `srcWin`:裁剪窗口参数`[x偏移, y偏移, 宽度, 高度]` - 支持多波段图像(自动处理所有波段) 3. **处理大文件优化**(可选): ```python def find_valid_bbox(ds, nodata_value=0): """分块处理大文件,避免内存溢出""" width = ds.RasterXSize height = ds.RasterYSize band = ds.GetRasterBand(1) # 初始化边界值 min_col, max_col = width, 0 min_row, max_row = height, 0 block_size = 4096 # 分块大小 for y in range(0, height, block_size): h = min(block_size, height - y) for x in range(0, width, block_size): w = min(block_size, width - x) block = band.ReadAsArray(x, y, w, h) # 查找有效像素 valid = (block != nodata_value) if np.any(valid): rows, cols = np.where(valid) min_col = min(min_col, x + np.min(cols)) max_col = max(max_col, x + np.max(cols)) min_row = min(min_row, y + np.min(rows)) max_row = max(max_row, y + np.max(rows)) return min_col, min_row, max_col-min_col+1, max_row-min_row+1 ``` ### 注意事项 1. **无效值处理**: - 如果原始文件已设置NODATA值(通过`band.GetNoDataValue()`获取) - 未设置时默认使用0作为黑边标识值 2. **地理坐标保留**: - 裁剪后自动更新地理变换参数 - 坐标系信息保持不变 3. **性能考虑**: - 小文件直接使用内存处理 - 大文件使用分块处理(`find_valid_bbox`函数) 4. **多波段支持**: - 假设所有波段有相同的无效区域 - 特殊情况下需单独处理每个波段 ### 相关问题 1. 如何批量处理目录中的所有合并后TIFF文件? 2. 当黑边不是0时,如何自动检测无效值? 3. GDAL裁剪操作如何影响图像的地理坐标精度? 4. 除了矩形裁剪,如何实现不规则有效区域的精确裁剪? 5. 如何处理包含透明通道的TIFF文件去黑边? > 引用说明:本文解决方案基于GDAL的栅格处理原理[^2]和NumPy数组操作技术[^2],结合了TIFF文件处理的最佳实践[^1][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值