【问题标题】:Image downscaling algorithm图像缩小算法
【发布时间】:2012-03-23 04:12:52
【问题描述】:

您能帮我找到正确的图像大小调整算法吗?我有一个数字的图像。最大尺寸为 200x200,我需要获得尺寸为 15x15 甚至更小的图像。图像是单色的(黑白),结果应该是一样的。这是关于我的任务的信息。

我已经尝试过一种算法,这里是

// xscale, yscale - decrease/increase rate
for (int f = 0; f<=49; f++)
            {
                    for (int g = 0; g<=49; g++)//49+1 - final size
                    {
                            xpos = (int)f * xscale;
                            ypos = (int)g * yscale;
                            picture3[f][g]=picture4[xpos][ypos];
                    }
            }

但它不适用于缩小图像,这是我之前的目标。 你能帮我找到一个可以解决这个问题的算法吗(质量不一定是完美的,速度甚至不重要)。考虑到我是新手,有关它的一些信息也将是完美的。当然,一小段 c/c++ 代码(或库)也将是完美的。

编辑: 我找到了一个算法。会不会适合从200压缩到20?

【问题讨论】:

  • 缩小到该尺寸并生成单色图像,无论如何都会导致令人失望的结果。
  • picture3 应该是新的缩小图像?如果是这样,您将进行反向逆转。它应该是picture3[xpos][ypos] = picture4[f][g]。如果图片 4 是缩小版,那么您的分配是向后的 - 图片 4 应该在左侧。
  • @Mark Ransom,不幸的是我的神经网络很慢。难道真的没有机会变得不优秀,但至少可以识别的图片?

标签: c++ c algorithm image image-recognition


【解决方案1】:

一般的做法是对输入进行过滤以生成更小的尺寸,并将阈值转换为单色。最容易实现的过滤器是一个简单的平均值,它通常会产生好的结果。 Sinc filter 理论上是最好的,但实施起来不切实际,并且具有通常不受欢迎的振铃伪影。还有许多其他过滤器可用,例如 Lanczos 或 Tent(双线性的广义形式)。

这是一个结合阈值的平均过滤器版本。假设picture4是输入,像素值为0或1,输出为picture3,格式相同。我还假设x 是与通常的数学符号相反的最不重要的维度,并且与您问题中的坐标相反。

int thumbwidth = 15;
int thumbheight = 15;
double xscale = (thumbwidth+0.0) / width;
double yscale = (thumbheight+0.0) / height;
double threshold = 0.5 / (xscale * yscale);
double yend = 0.0;
for (int f = 0; f < thumbheight; f++) // y on output
{
    double ystart = yend;
    yend = (f + 1) / yscale;
    if (yend >= height) yend = height - 0.000001;
    double xend = 0.0;
    for (int g = 0; g < thumbwidth; g++) // x on output
    {
        double xstart = xend;
        xend = (g + 1) / xscale;
        if (xend >= width) xend = width - 0.000001;
        double sum = 0.0;
        for (int y = (int)ystart; y <= (int)yend; ++y)
        {
            double yportion = 1.0;
            if (y == (int)ystart) yportion -= ystart - y;
            if (y == (int)yend) yportion -= y+1 - yend;
            for (int x = (int)xstart; x <= (int)xend; ++x)
            {
                double xportion = 1.0;
                if (x == (int)xstart) xportion -= xstart - x;
                if (x == (int)xend) xportion -= x+1 - xend;
                sum += picture4[y][x] * yportion * xportion;
            }
        }
        picture3[f][g] = (sum > threshold) ? 1 : 0;
    }
}

我现在已经测试了这段代码。这是输入的 200x200 图像,然后是最近邻缩小到 15x15(在 Paint Shop Pro 中完成),然后是此代码的结果。我会让你决定哪个更忠实于原作;如果原版有一些细节,差异会更加明显。

【讨论】:

  • 我在插值之前做阈值。所以不对吗?也许我应该更好地描述输入图片以便您决定哪个过滤器会更好?我将查找有关这些过滤器的一些信息,但希望您能帮助实现平均过滤器。
  • @user1131662,您的问题将输入图像描述为单色,这让我相信在调整大小之前进行阈值处理是不可能的。
  • 抱歉,我似乎需要编辑我的问题。整个故事就是我抓一张照片。然后我使用中等阈值算法对其进行阈值。现在我有一个带有数字 0(用于白色背景点)和 1(用于黑色 - 数字本身)的二维数组。然后我想调整图片大小,然后 - 识别。就是这样。
  • 谢谢,我会尽快测试这段代码。乍一看,我看到了一些必须理解的问题。尽管如此,还是非常感谢你。
【解决方案2】:

我找到了双线性插值器的实现。 C 代码。

假设:

a - 一个主数组(我们需要拉伸/压缩)指针。

oldw - 主要宽度

oldh - 主要高度

b - 一个二级数​​组(我们在压缩/拉伸后得到)指针

neww - 次要宽度

newh - 次日高度


#include <stdio.h>
#include <math.h>
#include <sys/types.h>

void resample(void *a, void *b, int oldw, int oldh, int neww,  int newh)
{
int i;
int j;
int l;
int c;
float t;
float u;
float tmp;
float d1, d2, d3, d4;
u_int p1, p2, p3, p4; /* nearby pixels */
u_char red, green, blue;

for (i = 0; i < newh; i++) {
    for (j = 0; j < neww; j++) {

        tmp = (float) (i) / (float) (newh - 1) * (oldh - 1);
        l = (int) floor(tmp);
        if (l < 0) {
            l = 0;
        } else {
            if (l >= oldh - 1) {
                l = oldh - 2;
            }
        }

        u = tmp - l;
        tmp = (float) (j) / (float) (neww - 1) * (oldw - 1);
        c = (int) floor(tmp);
        if (c < 0) {
            c = 0;
        } else {
            if (c >= oldw - 1) {
                c = oldw - 2;
            }
        }
        t = tmp - c;

        /* coefficients */
        d1 = (1 - t) * (1 - u);
        d2 = t * (1 - u);
        d3 = t * u;
        d4 = (1 - t) * u;

        /* nearby pixels: a[i][j] */
        p1 = *((u_int*)a + (l * oldw) + c);
        p2 = *((u_int*)a + (l * oldw) + c + 1);
        p3 = *((u_int*)a + ((l + 1)* oldw) + c + 1);
        p4 = *((u_int*)a + ((l + 1)* oldw) + c);

        /* color components */
        blue = (u_char)p1 * d1 + (u_char)p2 * d2 + (u_char)p3 * d3 + (u_char)p4 * d4;
        green = (u_char)(p1 >> 8) * d1 + (u_char)(p2 >> 8) * d2 + (u_char)(p3 >> 8) * d3 + (u_char)(p4 >> 8) * d4;
        red = (u_char)(p1 >> 16) * d1 + (u_char)(p2 >> 16) * d2 + (u_char)(p3 >> 16) * d3 + (u_char)(p4 >> 16) * d4;

        /* new pixel R G B  */
        *((u_int*)b + (i * neww) + j) = (red << 16) | (green << 8) | (blue);       
    }
}
}

希望它对其他用户有用。但尽管如此,我仍然怀疑它是否适用于我的情况(当不是分层时,而是压缩数组时)。有什么想法吗?

【讨论】:

  • 插值(双线性或任何其他)是错误的答案。它可能适用于较小的尺寸调整,但在您所说的 200:15 的比例下,它不会比仅仅扔掉额外的像素更好。
  • 我同意这一点。我一直在处理图像,我可以告诉你——没有什么比图像上的线性插值更糟糕的了。不要。
【解决方案3】:

由于您可以使用库,您可以查看imagemagick C++ bindings

你也可以像pbm这样的简单格式输出图片,然后调用imagemagick命令调整大小:

system("convert input.pbm -resize 10x10 -compress none output.pbm");

示例输出文件(注意:您不需要为每一行使用新行):

P1
20 20
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0
0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0
0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 0 0 0 0 0
0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 0 0 0
0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 0 0 0
0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 0 0 0
0 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 0 0 0 0
0 0 0 0 0 0 0 1 1 0 0 0 0 1 1 1 0 0 0 0
0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0
0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

输出文件:

P1
10 10
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 1 1 0 1 1 0 
0 0 0 0 1 0 0 1 1 0 0 0 0 0 1 0 0 1 1 0 0 0 0 0 1 1 0 1 1 0 0 0 0 0 0 1 1 1 1 
1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 

【讨论】:

  • 你能给我一些源代码中命令行实用程序的例子吗?
  • 很好,您显示的结果对我来说没问题。希望从 200 的减少不会有很大的不同。所以我需要下载这个库。然后我将它包含在我的项目中。然后我做你展示的事情,对吧?你能告诉我更多关于这种奇怪的文件格式的信息吗?
  • netpbm format。手动处理非常简单易行。如果您使用 imagemagick API,那么您可以在没有中间图像格式的情况下使用 C++ 完成所有操作(不过我之前没有使用过)。
  • 好的,这就是敌人的解释。稍后会尝试。
【解决方案4】:

如果你使用 Win32,那么 StretchBlt 函数可能会有所帮助。

StretchBlt 函数将位图从源矩形复制到目标矩形,必要时拉伸或压缩位图以适应目标矩形的尺寸。系统根据目标设备上下文中当前设置的拉伸模式对位图进行拉伸或压缩。

【讨论】:

    【解决方案5】:

    要正确缩小图像,您应该将图像分成正方形像素块,然后使用Bilinear Interpolation 之类的东西来找到应该替换您正在做的 NxN 像素块的像素的正确颜色插值。

    由于我不太擅长所涉及的数学,因此我不会尝试为您提供代码的示例。对不起:(

    【讨论】:

    • 哦,又是这个 Wiki 链接。不幸的是,我的数学也不是很好。所以我会非常感谢“非数学”的解释。
    • @user1131662 忽略插值;只需用正方形像素块做第一部分。
    • 那么它适合压缩,不适合拉伸吗?
    【解决方案6】:

    我想,你需要Interpolation。算法很多,比如可以用Bilinear interpolation

    【讨论】:

    • 插值将在增加图像大小时使用,因为您需要在已知颜色的像素之间创建额外的像素。为了获得更小的图像,该过程称为“过滤”。一种方法是计算原始图像中的哪些像素被新图像中的像素覆盖,并将新像素的值设置为覆盖像素的平均值。
    【解决方案7】:

    200x200 图像缩小为100x100 的一种方法是沿每行和每列获取每第二个像素。我会让你滚动你自己的代码来缩小到一个不是原始大小除数的大小。对于这种方法是否适合您的问题,我不提供任何保证。

    【讨论】:

    • 这种方法会导致明显的混叠失真。也相当于问题中的代码。
    • 你可以删除它。
    • 线性插值其实更好。这是最快的路线,并不像大多数人想象的那么糟糕。是的,你最好使用像 Lanczos 这样的花哨算法。但是简单的二次采样是可以的。
    猜你喜欢
    • 2011-03-06
    • 1970-01-01
    • 2010-12-16
    • 2017-03-09
    • 2012-01-27
    • 1970-01-01
    • 2013-08-25
    • 1970-01-01
    相关资源
    最近更新 更多