前言:
之前写过一章关于Gdal批量拼接,剪切图片的博客,但是有一些局限性,如果相机拍摄的时机不一定,物体移动的时候,拍出来的图片起始高度不同,且没办法测量偏移量,就不适用了。
所以我想到一个办法。就是在要拍摄的物体上方加一条标识线,如下图所示:
假设物体在标识线下方,我们只需要按照标识线位置拼接就好。比如拍出来的图片如下面两张图所示:
假设图一和图二是完美情况拍出来的图片,高度分毫不差,这样我们不用考虑偏移,正常拼接就好,但是如果遇到图三这样的,突然某一张图片起始高度变了,而且不知道偏移了多少,就比较麻烦了。普通拼接就不适合了。
所以利用标识线的方式就能完美拼接图片了,只要保证相机能拍到标识线。
第一步:找标识线
/// <summary>
///寻找标识线坐标 ,保留标识线下方的部分,裁剪多余部分
/// </summary>
/// <param name="filePath">图片地址</param>
/// <param name="outFilePath">输出图片地址</param>
private void FindPosition(string filePath,string outFilePath)
{
Mat backImg = new Mat(filePath, ImreadModes.AnyColor);
Mat mat = new Mat();
backImg.CopyTo(mat);// 作为对比
int height = backImg.Rows;
int width = backImg.Cols;
for (int i = 0; i < height; i++)
{
bool colMate = true;
byte pixelValue = (byte)Math.Abs(backImg.Get<byte>(i, 0));//获取每行每列像素值
//像素值误差小于5
if (pixelValue <= 5)
{
bool rowMate = true;
for (int m = 0; m < width; m++)
{
byte col = (byte)Math.Abs(backImg.Get<byte>(i, m));
if (col >= 5)
{
rowMate = false;
break;
}
}
if (rowMate)
{
for (int j = 0; j < 2 && j + i < height; j++)
{
byte col = (byte)Math.Abs(backImg.Get<byte>(j + i, 0));
if (col >= 5)
{
colMate = false;
break;
}
}
if (colMate)
{
CutImage(filePath, outFilePath, i);
break;
}
}
}
}
}
/// <summary>
/// 裁剪图片
/// </summary>
/// <param name="filePath"></param>
/// <param name="outFileName"></param>
/// <param name="position_y">裁剪位置</param>
private void CutImage(string filePath, string outFileName, int position_y)
{
GdalConfiguration.ConfigureGdal();
GdalConfiguration.ConfigureOgr();
Gdal.AllRegister();
Gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "NO");
using (Dataset srcDs = Gdal.Open(filePath, Access.GA_ReadOnly))
{
DataType srcType = srcDs.GetRasterBand(1).DataType;
int bandCount = srcDs.RasterCount;
double[] adfGeoTransform = new double[6];
srcDs.GetGeoTransform(adfGeoTransform);
int width = srcDs.RasterXSize; // 图片的宽度
int height = srcDs.RasterYSize; // 图片的高度
int[] dataArray = new int[width * (height - position_y) * bandCount];
int[] bandArray = new int[bandCount];
for (int i = 0; i < bandCount; i++)
{
bandArray[i] = i + 1;
}
srcDs.ReadRaster(0, position_y, width, (height - position_y), dataArray, width, (height - position_y), bandCount, bandArray, 0, 0, 0);
Driver drv = Gdal.GetDriverByName("BMP");
if (File.Exists( outFileName))
{
File.Delete(outFileName);
}
using (Dataset dstDs = drv.Create(outFileName.Replace(" ", ""), width, (height - position_y), bandCount, srcType, null))
{
dstDs.WriteRaster(0, 0, width, (height - position_y), dataArray, width, (height - position_y), bandCount, bandArray, 0, 0, 0);
dstDs.FlushCache();
dstDs.Dispose();
}
}
}
第二步:创建拼接图片的工具类
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
using OSGeo.GDAL;
namespace OpenCVGDAL
{
public class JigsawPuzzle
{
private static string _outFileName = @"E:\t.bmp";
private static int _offset_x = 93;
private static int _offset_y = 26;
private static string[] _files;
public JigsawPuzzle(int offset_x, int offset_y,string [] files,string outFileName)
{
_offset_x = offset_x;
_offset_x = offset_y;
_files = iniArray(files);
_outFileName = outFileName;
}
//文件名排序
private string[] iniArray(string [] files)
{
Array.Sort(files);
return files;
}
public void Start()
{
CombineTiles(_files);
}
private static void SaveBitMapBuffer(Dataset srcDs, Dataset outDs, int index)
{
if (srcDs.RasterCount < 1)
{
return;
}
int width = srcDs.RasterXSize;
int height = srcDs.RasterYSize;
int bandCount = srcDs.RasterCount;
int[] dataArray = new int[width * width * bandCount];
int[] bandArray = new int[bandCount];
for (int i = 0; i < bandCount; i++)
{
bandArray[i] = i + 1;
}
//byte[] buf = new byte[width * height];
srcDs.ReadRaster(0, 0, width, height, dataArray, width, height, bandCount, bandArray, 0, 0, 0);
outDs.WriteRaster(index * width , 0, width, height, dataArray, width, height, bandCount, bandArray, 0, 0, 0);
}
private static void CombineTiles(string [] fileNames)
{
int fileCount = fileNames.Length;
if (fileCount < 1)
{
return;
}
GdalConfiguration.ConfigureGdal();
GdalConfiguration.ConfigureOgr();
Gdal.AllRegister();
//Gdal.SetConfigOption("GDAL_FILENAME_IS_UTF8", "NO");
int maxHeight = 0;
foreach (var item in fileNames)
{
using (Dataset fristDs = Gdal.Open(item, Access.GA_ReadOnly))
{
int height = fristDs.RasterYSize; // 图片的高度
maxHeight = maxHeight < height ? height : maxHeight;
}
}
using (Dataset fristDs = Gdal.Open(fileNames[0], Access.GA_ReadOnly))
{
DataType srcType = fristDs.GetRasterBand(1).DataType;
int width = fristDs.RasterXSize; // 图片的宽度
//合成图片的大小
int imageWidth = width * fileCount ;
int imageHeight = maxHeight;
Driver driver = Gdal.GetDriverByName("BMP");
if (File.Exists(_outFileName))
{
File.Delete(_outFileName);
}
using (Dataset outDs = driver.Create(_outFileName, imageWidth, imageHeight, 3, srcType, null))
{
for (int i = 0; i < fileNames.Length; i++)
{
using (Dataset srcDs = Gdal.Open(fileNames[i], Access.GA_ReadOnly))
SaveBitMapBuffer(srcDs, outDs, i);
}
}
}
}
}
}
第三步:依次调用方法
private void button2_Click(object sender, EventArgs e)
{
OpenFileDialog fileDialog = new OpenFileDialog();
fileDialog.Multiselect = true;//可以选择多个选项
if (!(fileDialog.ShowDialog() == DialogResult.OK))
{
return;
}
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "(*.bmp)|*.bmp";
if (!(saveFileDialog.ShowDialog() == DialogResult.OK))
{
return;
}
string[] files = new string[fileDialog.FileNames.Length];
for (int i = 0; i < fileDialog.FileNames.Length; i++)
{
files[i] = fileDialog.FileNames[i];
}
string[] outfile = new string[files.Length];
for (int i = 0; i < files.Length; i++)
{
string item = files[i];
string outFilePath = Path.GetDirectoryName(item)+"\\" + Path.GetFileNameWithoutExtension(item) + i + ".bmp";
outfile[i] = outFilePath;
}
for (int i = 0; i < files.Length; i++)
{
FindPosition(files[i], outfile[i]);
}
JigsawPuzzle jigsawPuzzle = new JigsawPuzzle(0,0, outfile, saveFileDialog.FileName);
jigsawPuzzle.Start();
}
大功告成!展示一下拼接结果:
总结
博主是刚学习opencv,许多地方也不懂。比如像素值的范围问题,我试了很多次,搞不懂标识线的一行都是黑色的,但是偏偏有的值是1,有的是2,3,4。相差有点离谱。但是大多数还是0,可能是因为绝对值的原因吧,或者分辨率....。