C/C++ 旋转 BMP 图像

Posted

技术标签:

【中文标题】C/C++ 旋转 BMP 图像【英文标题】:C/C++ rotate an BMP image 【发布时间】:2016-11-15 22:55:39 【问题描述】:

我正在尝试使用 ,但它不起作用。

我已经制作了一些用于读取、写入和旋转的函数。读取和写入功能工作正常,但由于某种原因无法旋转。

编辑(正弦、余弦和旋转函数)

BMP 结构:

struct BMP 
    int width;
    int height;
    unsigned char header[54];
    unsigned char *pixels;
    int size;
;

写:

void writeBMP(string filename, BMP image) 
    string fileName = "Output Files/" + filename;
    FILE *out = fopen(fileName.c_str(), "wb");
    fwrite(image.header, sizeof(unsigned char), 54, out);
    int i;
    unsigned char tmp;
    for (i = 0; i < image.size; i += 3) 
        tmp = image.pixels[i];
        image.pixels[i] = image.pixels[i + 2];
        image.pixels[i + 2] = tmp;
    
    fwrite(image.pixels, sizeof(unsigned char), image.size, out); // read the rest of the data at once
    fclose(out);

阅读:

BMP readBMP(string filename) 
    BMP image;
    int i;
    string fileName = "Input Files/" + filename;
    FILE *f = fopen(fileName.c_str(), "rb");
    fread(image.header, sizeof(unsigned char), 54, f); // read the 54-byte header

    // extract image height and width from header
    image.width = *(int *) &image.header[18];
    image.height = *(int *) &image.header[22];

    image.size = 3 * image.width * image.height;
    image.pixels = new unsigned char[image.size]; // allocate 3 bytes per pixel
    fread(image.pixels, sizeof(unsigned char), image.size, f); // read the rest of the data at once
    fclose(f);

    for (i = 0; i < image.size; i += 3) 
        unsigned char tmp = image.pixels[i];
        image.pixels[i] = image.pixels[i + 2];
        image.pixels[i + 2] = tmp;
    
    return image;

旋转:

BMP rotate(BMP image, double degree) 
    BMP newImage = image;
    unsigned char *pixels = new unsigned char[image.size];

    double radians = (degree * M_PI) / 180;
    int sinf = (int) sin(radians);
    int cosf = (int) cos(radians);

    double x0 = 0.5 * (image.width - 1);     // point to rotate about
    double y0 = 0.5 * (image.height - 1);     // center of image

    // rotation
    for (int x = 0; x < image.width; x++) 
        for (int y = 0; y < image.height; y++) 
            long double a = x - x0;
            long double b = y - y0;
            int xx = (int) (+a * cosf - b * sinf + x0);
            int yy = (int) (+a * sinf + b * cosf + y0);

            if (xx >= 0 && xx < image.width && yy >= 0 && yy < image.height) 
                pixels[(y * image.height + x) * 3 + 0] = image.pixels[(yy * image.height + xx) * 3 + 0];
                pixels[(y * image.height + x) * 3 + 1] = image.pixels[(yy * image.height + xx) * 3 + 1];
                pixels[(y * image.height + x) * 3 + 2] = image.pixels[(yy * image.height + xx) * 3 + 2];
            
        
    
    newImage.pixels = pixels;
    return newImage;

主要:

int main() 
    BMP image = readBMP("InImage_2.bmp");
    image = rotate(image,180);
    writeBMP("Output-11.bmp", image);
    return 0;

sin=0.8939966636(弧度)和cos=-0.44807361612(弧度)表示这张图片应该旋转90度。

这是我的原图:

现在是我的结果:

有人可以帮我理解我在这里做错了什么吗?我真的需要这个功能。

我不能为此代码使用任何第三方库。

【问题讨论】:

在旋转中,newImage = image。因此,您在旋转时会覆盖图像像素。 PIxel 寻址应该是[y * image.width + x],假设是光栅顺序 @samgak 我认为应该对旋转方向产生一些影响。但无论如何它都不起作用...我已经编辑了我的 旋转函数 添加了新结果以及您所说的内容。而在这一刻……我有一个比以前更奇怪的结果。 我会说问题是因为你截断了 sin 和 cos: int sinf = (int) sin(radians); int cosf = (int) cos(弧度);通常的做法是将浮点数保留到最后一刻。 【参考方案1】:

它必须以 bmp 使用的相同像素格式处理旋转。您只为每个像素转换一个字节。像素看起来更宽。既然问题已经确定,这应该很容易解决。

如果您需要更快的速度,请注意您的不变量(x 和 y)在每次迭代时都会递增:

for (int y = 0; y < image.height; y++) 
        double a = x - x0;
        double b = y - y0;
        int xx = (int) (+a * cos - b * sin + x0);

将 b 移出循环并将乘法改为加法:

double b = -y0;
for (int y = 0; y < image.height; ++y) 
    int xx = (int) (a * cos - b + x0);
    b += sin;

注意到 a * cos 是整个 y 循环的常数吗?将其融合到 b。对 x0 执行相同操作。

double b = a * cos - y0 + x0;
for (int y = 0; y < image.height; ++y) 
    int xx = (int) (- b);
    b += sin;

注意 -b 也有成本?否定 b。

double b = -(a * cos - y0 + x0);
for (int y = 0; y < image.height; ++y) 
    int xx = (int) b;
    b -= sin;

看看我们在那里做了什么?下一步:摆脱双打。使用定点。浮点到整数的转换可能代价高昂。充其量,它们在这里毫无用处。

最后但同样重要的是,您正在垂直写入内存。这对写入组合非常非常不利,并且会大大降低性能。考虑更改循环顺序,使 x-loop 位于最里面。

额外:对图像使用平铺内存布局,以提高读取时的内存局部性。缓存将更有效地工作。在这里不是很重要,因为您只处理一次图像并且平铺会比加速更昂贵。但是,如果您想为旋转设置动画,那么平铺应该会让您受益。此外,通过平铺,性能不会因旋转角度而波动,因此动画将更加一致(并且更快)。

编辑:添加说明如何支持每像素更多字节:

pixels[(y * image.height + x) * 3 + 0] = image.pixels[(yy * image.height + xx) * 3 + 0];
pixels[(y * image.height + x) * 3 + 1] = image.pixels[(yy * image.height + xx) * 3 + 1];
pixels[(y * image.height + x) * 3 + 2] = image.pixels[(yy * image.height + xx) * 3 + 2];

这开始有点难以阅读,但你确实看到我们在做什么了吗?

【讨论】:

好吧……如果你谈到速度,是的,我将来会改变它,首先它们都是我需要让我的轮换工作的。 It must handle the rotation in the same pixel format the bmp uses.,好吧...我不知道该怎么做。我的意思是我认为我已经这样做了。你能帮我写这部分代码吗?因为我不知道如何做到这一点,而且我尝试了几个小时但我什么也没得到......如果你能为我做这将非常有帮助。 我添加了一些代码来演示如何处理更宽的像素。我注意到您很乐意在不释放内存的情况下覆盖旧图像指针。您可能还想修复该内存泄漏。如果您容易出现此类错误(std::vector、std::array、std::unique_ptr 或类似),我建议您使用一些对 RAII 友好的类型进行存储。 我会在完成这个问题后看看我将如何处理内存泄漏。在这一刻,让我们说……我的问题完成了 50%。我现在还有 2 个问题,1。我不知道为什么,但它没有在中心旋转的图像,它在中心旁边的某个地方(你可以看到我的新结果,因为我已经更新了我的问题)和第二个,2。我不知道为什么,但我失去了图像的颜色属性,由于某种原因我调用了旋转函数后,我的所有图像都是黑色和什么。你能帮我解决这两个问题吗? 将 y * image.height 固定为 y * image.width,与 yy 相同。您正在处理错误的图像。在读取和写入 bmp 时交换(红色,蓝色)的 for 循环也是不必要的,删除它。最后但并非最不重要的一点是,不要调用您的旋转函数 .. 只需读写 bmp 并查看读写是否会破坏颜色。您需要确信事情在一定程度上可以正常工作,这样您才能发现问题(现在问题可能出现在任何地方)。【参考方案2】:
BMP newImage = image;

这会将newImage 设置为image。 BMP结构是:

     unsigned char *pixels;

所以,newImageimage 都有相同的 pixels 指针。

不幸的是,轮换代码中的逻辑假定 newImageimage 是不同的独立缓冲区。

您需要做更多的工作来创建您的newImage 对象。您将需要分配自己的图像缓冲区,将内存清除为空图像,并将其pixels 设置为指向它。

要正确执行此操作,您的 BMP 类应设为 Rule Of Three compliant。

【讨论】:

好吧,如果你说的是真的,那么我只需要制作一组新的像素,如unsigned char *pixels = new unsigned char[image.size]; 并使用它。最后我需要做一些类似`newImage.pixels = pixel;`的事情。所以我这样做了,我得到了一个更奇怪的结果...我已经通过添加详细信息和我的新结果来编辑我的 旋转函数,你能再检查一次吗? “我得到一个更奇怪的结果”不是有效的问题描述。 嗯,我的意思是,我没有得到我期望的结果。我已经更新了我的问题,因此您可以检查我尝试过的方法,但它不起作用,您会看到新的结果。 你的读写函数清楚地显示pixels[]数组中每个像素占用三个字节,每行有width*3字节(RGB分量)。您的旋转代码假定每个像素占用一个字节,并且每行有 width 字节。失败。在每次通过旋转循环时,您应该一次移动三个字节,包括一个像素。 pixels[]数组中每个像素的计算必须相应调整。 ...在分配新的像素数组等时需要做同样的调整...【参考方案3】:

您可以将您的像素(缓冲区)复制到一个新的、单独且相同类型/大小的像素(如前所述),然后使用以下方式旋转它:

http://www.sourcetricks.com/2012/07/rotate-matrix-by-90-degrees.html

然后您可以将新缓冲区重新复制回原始像素缓冲区,清理(释放)它(编辑:这意味着副本),最后重绘所有。

编辑:

I was thinking in pseudo code: 
    rotate image 
     
    imagecopy = duplicate (image) 
    (do the rotation on imagecopy)  
    copypixels (imagecopy (to), image) 
    free imagecopy 
     

copy 和 dup 是 for 循环,但 dups 添加了一个 malloc

和“newImage.pixels = 像素;”将不起作用,您必须遍历两个数组并像“writeBMP”中一样一一复制值

哦,通过

它必须以 bmp 使用的相同像素格式处理旋转。

我认为他的意思是使用整数,例如 intlong 而不是 floatdouble没有好处的代码

【讨论】:

好吧,如果你再次检查我的旋转功能.. 你会看到我已经为像素添加了一个新的缓冲区,这里是unsigned char *pixels = new unsigned char[image.size];,在这个函数的最后我会做newImage.pixels = pixels;所以我没有这个问题了 旋转图像 imagecopy = duplicate (image) (在 imagecopy 上进行旋转) image = copy (imagecopy) free imagecopy 对不起,我所有的 cmets 都很烂,我是新手 :) copy 和 dup 是 for 循环,但 dups 添加了一个 malloc 和“newImage.pixels = 像素;”没那么容易,你必须遍历两个数组并像“writeBMP”一样一一复制值 好的,但我不明白...我的意思是,我用这个BMP newImage = image; unsigned char *pixels = new unsigned char[image.size]; 制作了一个新的像素数组,之后我只使用这个新的像素数组.在这个函数的最后,我正在做 image.pixels = 像素。不好吗?我的意思是它就像一个重复......?没有?

以上是关于C/C++ 旋转 BMP 图像的主要内容,如果未能解决你的问题,请参考以下文章

C++ bmp图像添加水印及旋转水印

查表方式的图像旋转

图像旋转任意角度+python

如何将部分纹理复制到opengl中的图像

在 C/C++ 中将 1 位 bmp 文件转换为数组 [关闭]

使用PIL在python中旋转并将expand参数设置为true时指定图像填充颜色