8

我有一个位图sourceImage.bmp

锁定它的位:

BitmapData dataOriginal = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

做分析,得到一个克隆

Bitmap originalClone = AForge.Imaging.Image.Clone(dataOriginal);

解锁位:

sourceImage.UnlockBits(dataOriginal);

是否可以指定要复制“dataOriginal”的哪一部分(x,y,w,h)?或从 dataOriginal 创建新数据,指定 X 和 Y 坐标以及 H 和 W?

目的是从该图像中复制一个小区域。这种方法可能比 DrawImage 快,这就是我不使用后者的原因。

编辑:

所以我拿了29 Mb位图并做了一些核心测试!全尺寸裁剪(基本上是副本)+ 100 次迭代。

http://i.minus.com/ibmcUsT1qUGw6f.png

代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using AForge;
using AForge.Imaging;
using System.Diagnostics;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;


namespace testCropClone
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private unsafe Bitmap Clone(Bitmap bmp, int startX, int startY, int width, int height)
        {
        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

        int origByteCount = rawOriginal.Stride * rawOriginal.Height;
        byte[] origBytes = new Byte[origByteCount];
        Marshal.Copy(rawOriginal.Scan0, origBytes, 0, origByteCount);

        int BPP = 4;        //4 Bpp = 32 bits, 3 = 24, etc.

        byte[] croppedBytes = new Byte[width * height * BPP];

        //Iterate the selected area of the original image, and the full area of the new image
        for (int i = 0; i < height; i++)
        {
            for (int j = 0; j < width * BPP; j += BPP)
            {
                int origIndex = (startX * rawOriginal.Stride) + (i * rawOriginal.Stride) + (startY * BPP) + (j);
                int croppedIndex = (i * width * BPP) + (j);

                //copy data: once for each channel
                for (int k = 0; k < BPP; k++)
                {
                    croppedBytes[croppedIndex + k] = origBytes[origIndex + k];
                }
            }
        }

        //copy new data into a bitmap
        Bitmap croppedBitmap = new Bitmap(width, height);
        BitmapData croppedData = croppedBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
        Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length);

        bmp.UnlockBits(rawOriginal);
        croppedBitmap.UnlockBits(croppedData);

        return croppedBitmap;
        }

        private Bitmap cloneBitmap(Bitmap bmp, int startX, int startY, int width, int height)
        {
            Rectangle srcRect = Rectangle.FromLTRB(startX, startY, width, height);
            Bitmap cloneBitmap = bmp.Clone(srcRect, bmp.PixelFormat);
            return cloneBitmap;
        }


        private Bitmap cloneRectangle(Bitmap bmp, int startX, int startY, int width, int height)
        {
            Rectangle srcRect = Rectangle.FromLTRB(startX, startY, width, height);
            Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height);
            Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height);
            using (Graphics graphics = Graphics.FromImage(dest))
            {
                graphics.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
            }
            return dest;
        }


        private Bitmap cloneAforge(Bitmap bmp, int startX, int startY, int width, int height)
        {
            BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
            Bitmap cloneBitmap = AForge.Imaging.Image.Clone(rawOriginal);
            bmp.UnlockBits(rawOriginal);
            return cloneBitmap;
        }



        private void button1_Click(object sender, EventArgs e)
        {
            Bitmap source = new Bitmap(@"C:\9\01.bmp");

            Stopwatch s1 = Stopwatch.StartNew();
            for (int i = 0; i < 100; i++)
            {
                Bitmap Clone1 = cloneAforge(source, 0, 0, source.Width, source.Height);
                Clone1.Dispose();

            }

            /*Bitmap Clone1 = cloneAforge(source, 0, 0, source.Width, source.Height);
            Clone1.Save(@"C:\9\01_aforge.bmp");
            Clone1.Dispose();*/

            s1.Stop();
            source.Dispose();
            textBox1.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Bitmap source = new Bitmap(@"C:\9\01.bmp");

            Stopwatch s1 = Stopwatch.StartNew();
            for (int i = 0; i < 100; i++)
            {
                Bitmap Clone1 = cloneBitmap(source, 0, 0, source.Width, source.Height);
                Clone1.Dispose();

            }

            /*Bitmap Clone1 = cloneBitmap(source, 0, 0, source.Width, source.Height);
            Clone1.Save(@"C:\9\01_bitmap.bmp");
            Clone1.Dispose();*/

            s1.Stop();


            source.Dispose();
            textBox2.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
        }

        private void button3_Click(object sender, EventArgs e)
        {
            Bitmap source = new Bitmap(@"C:\9\01.bmp");

            Stopwatch s1 = Stopwatch.StartNew();
            for (int i = 0; i < 100; i++)
            {
                Bitmap Clone1 = Clone(source, 0, 0, source.Width, source.Height);
                Clone1.Dispose();

            }

            /*Bitmap Clone1 = Clone(source, 0, 0, source.Width, source.Height);
            Clone1.Save(@"C:\9\01_bits.bmp");
            Clone1.Dispose();*/

            s1.Stop();
            source.Dispose();
            textBox3.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
        }

        private void button4_Click(object sender, EventArgs e)
        {
            Bitmap source = new Bitmap(@"C:\9\01.bmp");

            Stopwatch s1 = Stopwatch.StartNew();
            for (int i = 0; i < 100; i++)
            {
                Bitmap Clone1 = cloneRectangle(source, 0, 0, source.Width, source.Height);
                Clone1.Dispose();

            }


            /*Bitmap Clone1 = cloneRectangle(source, 0, 0, source.Width, source.Height);
            Clone1.Save(@"C:\9\01_rect.bmp");
            Clone1.Dispose();*/


            s1.Stop();
            source.Dispose();
            textBox4.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
        }
    }
}

Edit2: (Aforge full-size Crop..) 方法编号。2

        for (int i = 0; i < 100; i++)
        {
            Crop crop = new Crop(new Rectangle(0, 0, source.Width, source.Height));
            var source2 = crop.Apply(source);
            source2.Dispose();

        }

平均 = 62 毫秒(比第一次 Aforge 方法少 40 毫秒)

结果:

  1. 位图克隆 (0 毫秒) ?? (作弊,不是吗?)
  2. Aforge #2(65 毫秒)
  3. Aforge #1(105 毫秒)
  4. 矩形(170 毫秒)
  5. 锁定位(803 毫秒)(等待修复/新测试结果..)
4

5 回答 5

8

我提出了一个快速(并且公认是粗略的)手动解决方案,演示如何使用锁定的位图来执行此操作。它应该比替代方法快得多,但确实涉及更多代码。

        Bitmap bmp = new Bitmap(@"C:\original.jpg");
        Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
        BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

        int origByteCount = rawOriginal.Stride * rawOriginal.Height;
        byte[] origBytes = new Byte[origByteCount];
        Marshal.Copy(rawOriginal.Scan0, origBytes, 0, origByteCount);

        //I want to crop a 100x100 section starting at 15, 15.
        int startX = 15;
        int startY = 15;
        int width = 100;
        int height = 100;
        int BPP = 4;        //4 Bpp = 32 bits, 3 = 24, etc.

        byte[] croppedBytes = new Byte[width * height * BPP];

        //Iterate the selected area of the original image, and the full area of the new image
        for (int i = 0; i < height; i++)
        {
            for (int j = 0; j < width * BPP; j += BPP)
            {
                int origIndex = (startX * rawOriginal.Stride) + (i * rawOriginal.Stride) + (startY * BPP) + (j);
                int croppedIndex = (i * width * BPP) + (j);

                //copy data: once for each channel
                for (int k = 0; k < BPP; k++)
                {
                    croppedBytes[croppedIndex + k] = origBytes[origIndex + k];
                }
            }
        }

        //copy new data into a bitmap
        Bitmap croppedBitmap = new Bitmap(width, height);
        BitmapData croppedData = croppedBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
        Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length);

        bmp.UnlockBits(rawOriginal);
        croppedBitmap.UnlockBits(croppedData);

        croppedBitmap.Save(@"C:\test.bmp");

我使用了这个原始图像:

原来的

要输出此图像,裁剪为 100x100 @ 15,15:

裁剪

显然,如果您使用此代码,您将需要对其进行一些清理并添加错误处理。如果我正确理解您的问题,那么以这种方式做事应该完全不需要使用 AForge。

于 2012-03-13T20:15:12.540 回答
3

当我们将 Marshal.copy 替换为 memcpy 时,Fopedush 的答案会受益匪浅,因为这样我们就不必通过 byte[] 数组复制它。这样内存只会被复制一次,而不是三次!

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static unsafe extern int memcpy(byte* dest, byte* src, long count);

static public Bitmap cropBitmap(Bitmap sourceImage, Rectangle rectangle)
{
    const int BPP = 4; //4 Bpp = 32 bits; argb
    var sourceBitmapdata = sourceImage.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    var croppedImage = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppArgb);
    var croppedBitmapData = croppedImage.LockBits(new Rectangle(0, 0, rectangle.Width, rectangle.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
    unsafe
    {
        croppedBitmapData.Stride = sourceBitmapdata.Stride;
        byte* sourceImagePointer = (byte*)sourceBitmapdata.Scan0.ToPointer();
        byte* croppedImagePointer = (byte*)croppedBitmapData.Scan0.ToPointer();
        memcpy(croppedImagePointer, sourceImagePointer,
               Math.Abs(croppedBitmapData.Stride) * rectangle.Height);
    }
    sourceImage.UnlockBits(sourceBitmapdata);
    croppedImage.UnlockBits(croppedBitmapData);
    return croppedImage;
}

我的结果是:

BitmapClone: 1823 ms
LockBits: 4857 ms
Rectangle: 1479 ms
My method: 559 ms
My method with LockBits on source image done only once (before loop): 160 ms

我没有 AForge,所以我没有包含它,但是通过查看 op 的结果,它会比这慢。我正在测试将图像裁剪成两半。

请注意,如果我们将 memcpy 交换为:

for (int k = 0; k < Math.Abs(croppedBitmapData.Stride) * rectangle.Height; k++)
     *(croppedImagePointer++) = *(sourceImagePointer++);

它变慢了 10 倍!

于 2013-10-03T00:58:11.897 回答
2

你可以尝试这样的事情:

public static Bitmap CropBitmap(Bitmap bitmap, int x, int y, int w, int h)
{
   Rectangle rect = new Rectangle(x, y, w, h);
   Bitmap cropped = bitmap.Clone(rect, bitmap.PixelFormat);
   return cropped;
}

并在你的代码中做这样的事情(示例):

var croppedImagem = CropBitmap(dataOriginal, 0, 0, 100, 100); 

我希望它有帮助!

于 2012-03-13T17:12:25.610 回答
2

此类获取您的位图 obj 。然后锁定位。在 ctor。当您调用crop 方法时,它使用memcpy将所需区域复制到新的bmp。

lockbits:告诉垃圾收集器不要将我的位移动到任何地方,因为我会通过指针(scan0)修改它。

memcpy:最快的复制。可以复制内存块。由一些专家优化。

为什么 memcpy 快?

而不是逐字节复制,(宽度高度)乘以内存访问。memcpy 逐块进行,比 w h 次要少得多。

internal unsafe sealed class FastImageCroper : IDisposable
{
    private readonly Bitmap _srcImg;
    private readonly BitmapData _srcImgBitmapData;
    private readonly int _bpp;
    private readonly byte* _srtPrt;

    public FastImageCroper(Bitmap srcImg)
    {
        _srcImg = srcImg;
        _srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat);
        _bpp = _srcImgBitmapData.Stride / _srcImgBitmapData.Width; // == 4
        _srtPrt = (byte*)_srcImgBitmapData.Scan0.ToPointer();
    }

    public Bitmap Crop(Rectangle rectangle)
    {
        Bitmap dstImg = new Bitmap(rectangle.Width, rectangle.Height, _srcImg.PixelFormat);
        BitmapData dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat);
        byte* dstPrt = (byte*)dstImgBitmapData.Scan0.ToPointer();
        byte* srcPrt = _srtPrt + rectangle.Y*_srcImgBitmapData.Stride + rectangle.X*_bpp;

        for (int y = 0; y < rectangle.Height; y++)
        {
            int srcIndex =  y * _srcImgBitmapData.Stride;
            int croppedIndex = y * dstImgBitmapData.Stride;
            memcpy(dstPrt + croppedIndex, srcPrt + srcIndex, dstImgBitmapData.Stride);
        }

        dstImg.UnlockBits(dstImgBitmapData);
        return dstImg;
    }


    public void Dispose()
    {
        _srcImg.UnlockBits(_srcImgBitmapData);
    }


    [DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
    private static extern int memcpy(byte* dest, byte* src, long count);
}
于 2015-02-20T19:30:12.973 回答
2

我是新用户,还不能投票,否则我会赞成Korwin80的答案,因为它提供了最有效的工作解决方案,在我看来。trakos的解决方案可能执行得更快,但会产生混乱的图像,至少对我来说是这样。以下是我在自己的代码中应用Korwin80的解决方案的方式,并进行了一些小的改进:

[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
private unsafe static extern int memcpy(byte* dest, byte* src, long count);

private unsafe Bitmap Crop(Bitmap srcImg, Rectangle rectangle)
{
    if ((srcImg.Width == rectangle.Width) && (srcImg.Height == rectangle.Height))
        return srcImg;

    var srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat);
    var bpp = srcImgBitmapData.Stride / srcImgBitmapData.Width; // 3 or 4
    var srcPtr = (byte*)srcImgBitmapData.Scan0.ToPointer() + rectangle.Y * srcImgBitmapData.Stride + rectangle.X * bpp;
    var srcStride = srcImgBitmapData.Stride;

    var dstImg = new Bitmap(rectangle.Width, rectangle.Height, srcImg.PixelFormat);
    var dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat);
    var dstPtr = (byte*)dstImgBitmapData.Scan0.ToPointer();
    var dstStride = dstImgBitmapData.Stride;

    for (int y = 0; y < rectangle.Height; y++)
    {
        memcpy(dstPtr, srcPtr, dstStride);
        srcPtr += srcStride;
        dstPtr += dstStride;
    }

    srcImg.UnlockBits(srcImgBitmapData);
    dstImg.UnlockBits(dstImgBitmapData);
    return dstImg;
}
于 2015-05-31T19:21:37.533 回答