之前遇到个需求,就是在c#.net中,把100张图片拼接成一张长图;尝试用.net自带的bitmap拼接,但是自带的bitmap对象对图片大小有上限,超过上限以后会抛出异常GDI+ 中发生一般性错误,当时由于项目紧急就没有继续实现,后面发现Emgu.CV可以实现,但是感觉用Emgu.CV仅仅是用来拼接图片有点杀鸡用牛刀,暂且先记录一下,后续有时间再找找有没有更轻巧的方式实现海量长图拼接。

Emgu.CV是基于OpenCV封装的一个计算机视觉框架,以帮助开发人员更便捷地设计更复杂得计算机视觉相关应用程序。OpenCV包含的函数有500多个,覆盖了计算机视觉的许多应用领域,如工厂产品检测、医学成像、信息安全、用户界面、摄像机标定、立体视觉和机器人等。

Bitmap拼接图片

这种方式有局限性,对象对图片大小有上限,超过上限以后会抛出异常,如果超了这个上限,那么就会抛出GDI+ 中发生一般性错误

var src_img1 = new Bitmap("5.png");
 var src_img2 = new Bitmap("5.png");

 var dst_width = src_img2.Width;
 var dst_height = src_img2.Height + src_img1.Height;
 var dst_img = new Bitmap(dst_width, dst_height);
 using (Graphics graph = Graphics.FromImage(dst_img))
 {
     graph.DrawImage(src_img1, 0, 0, src_img1.Width, src_img1.Height);
     graph.DrawImage(src_img2, 0, src_img1.Height, src_img1.Width, src_img2.Height);
 }
 dst_img.Save("test.jpg", ImageFormat.Jpeg);
 Console.ReadLine();

写文件方式拼接图片

bitmap属于位图格式,了解图像格式后会发现,bitmap文件的第3-8位存储了文件大小信息,第19-22位存储了高度信息,第23-26位存储了宽度信息。文件头后面都是像素的argb字节,并无其它信息。于是我们可以把多张图像的像素argb循环拼接,并修改第一张的文件头信息,就可以实现文件合并了。缺点就是结果长图只支持保存为bmp格式,容量较大。

using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestPro
{
    public class ImgJoinAdapter
    {
        public static void SetBitmapFileSizeInfo(string filePath)
        {
            FileInfo fileInfo = new FileInfo(filePath);
            long le = fileInfo.Length;
            string hexSize = le.ToString("X").PadLeft(8, '0');
            int size1 = Convert.ToInt32(hexSize.Substring(0, 2), 16);
            int size2 = Convert.ToInt32(hexSize.Substring(2, 2), 16);
            int size3 = Convert.ToInt32(hexSize.Substring(4, 2), 16);
            int size4 = Convert.ToInt32(hexSize.Substring(6, 2), 16);
            byte[] sizeBytes = new byte[] { (byte)size4, (byte)size3, (byte)size2, (byte)size1 };
            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Write))
            {
                using (BinaryWriter r = new BinaryWriter(fs))
                {
                    r.Seek(2, 0);
                    r.Write(sizeBytes, 0, sizeBytes.Length);
                }
            }
        }

        public static void SetBitmapSizeInfo(string filePath, int width = 0, int height = 0)
        {
            if (height != 0)
            {
                string hexHeight = height.ToString("X").PadLeft(8, '0');
                int h1 = Convert.ToInt32(hexHeight.Substring(0, 2), 16);
                int h2 = Convert.ToInt32(hexHeight.Substring(2, 2), 16);
                int h3 = Convert.ToInt32(hexHeight.Substring(4, 2), 16);
                int h4 = Convert.ToInt32(hexHeight.Substring(6, 2), 16);
                byte[] sizeHeight = new byte[] { (byte)h4, (byte)h3, (byte)h2, (byte)h1 };
                using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite))
                {
                    using (BinaryWriter r = new BinaryWriter(fs))
                    {
                        r.Seek(22, 0);//高度保存位置
                        r.Write(sizeHeight, 0, sizeHeight.Length);
                    }
                }
            }
            if (width != 0)
            {
                string hexWidth = height.ToString("X").PadLeft(8, '0');
                int w1 = Convert.ToInt32(hexWidth.Substring(0, 2), 16);
                int w2 = Convert.ToInt32(hexWidth.Substring(2, 2), 16);
                int w3 = Convert.ToInt32(hexWidth.Substring(4, 2), 16);
                int w4 = Convert.ToInt32(hexWidth.Substring(6, 2), 16);
                byte[] sizeWidth = new byte[] { (byte)w4, (byte)w3, (byte)w2, (byte)w1 };
                using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite))
                {
                    using (BinaryWriter r = new BinaryWriter(fs))
                    {
                        r.Seek(18, 0);//高度保存位置
                        r.Write(sizeWidth, 0, sizeWidth.Length);
                    }
                }
            }
        }

        public static byte[] GetImageRasterBytes(Bitmap bmp, PixelFormat format)
        {
            Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
            byte[] bits = null;
            try
            {
                // Lock the managed memory
                BitmapData bmpdata = bmp.LockBits(rect, ImageLockMode.ReadWrite, format);
                // Declare an array to hold the bytes of the bitmap.
                bits = new byte[bmpdata.Stride * bmpdata.Height];
                // Copy the values into the array.
                System.Runtime.InteropServices.Marshal.Copy(bmpdata.Scan0, bits, 0, bits.Length);
                // Release managed memory
                bmp.UnlockBits(bmpdata);
            }
            catch
            {
                return null;
            }
            return bits;
        }
    }
}
var save_path = "output.bmp";
Bitmap bmp;
int height = 0;
for (int i = 200; i > 0; i--)
{
    string fileName = "5.png";
    bmp = new Bitmap(fileName);
    if (i == 200)
    {
        bmp.Save(save_path, ImageFormat.Bmp);
        height += bmp.Height;
        bmp.Dispose();
        continue;
    }
    else
    {
        byte[] bytes = ImgJoinAdapter.GetImageRasterBytes(bmp, PixelFormat.Format32bppRgb);
        using (FileStream fs = new FileStream(save_path, FileMode.Open, FileAccess.Write))
        {
            fs.Seek(fs.Length, 0);
            fs.Write(bytes, 0, bytes.Length);
        }
        height += bmp.Height;
        bmp.Dispose();
    }
}
ImgJoinAdapter.SetBitmapFileSizeInfo(save_path);
ImgJoinAdapter.SetBitmapSizeInfo(save_path, height: height);
Console.WriteLine();

Emgu.Cv拼接图片

Emgu.Cv拼接性能和效果算是最佳的,但是上面也说了,有点杀鸡用牛刀的感觉。首先我们打开nuget包管理工具,搜索Emgu.Cv,安装Emgu.CvEmgu.CV.runtime.windows,我这边用的版本是4.2.0.3636;高版本Emgu.Cv需要的.net框架依赖版本也比较高,大家自行选择。

暂时没找到Emgu.Cv保存自定义图片格式的方法,目前测试过程中传入的是png格式的图片,那么输出保存的格式也需要是png,不然会报错。如果有朋友研究过这个请留言。

c#安装emgu.cv库

using Emgu.CV;
using Emgu.CV.Structure;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Emgu.CV.ML.KNearest;

namespace TestPro
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            List<Emgu.CV.Image<Bgr, byte>> images = new List<Emgu.CV.Image<Bgr, byte>>();
            for (int i = 0; i < 200; i++)
            {
                images.Add(new Emgu.CV.Image<Bgr, byte>("5.png"));
            }
            VCombine(images);
            Console.WriteLine();
        }

        /// <summary>
        /// 纵向拼接图片
        /// </summary>
        /// <param name="images"></param>
        private static void VCombine(List<Emgu.CV.Image<Bgr, byte>> images)
        {
            int maxCols = images.Max(x => x.Cols);
            int totalRows = images.Sum(x => x.Rows);
            var output = new Emgu.CV.Image<Bgr, byte>(maxCols, totalRows, new Bgr(255, 255, 255));
            var count = 0;
            for (int i = 0; i < images.Count; i++)
            {
                output.ROI = new Rectangle(0, count, images[i].Width, images[i].Height);
                images[i].CopyTo(output);
                output.ROI = Rectangle.Empty;
                count += images[i].Height;
            }
            output.Save("output.png");
        }
    }
}
最后修改:2022 年 09 月 30 日
如果觉得我的文章对你有用,请随意赞赏