在大图中查找已知的子图像

26

有没有人知道一种算法(或搜索词/描述),可以在较大的图像中定位已知图像?

例如:

我有一个包含各种按钮和区域的单个桌面窗口的图像 (目标)。我还有代码来捕捉当前桌面的屏幕截图。我想要一种算法,能够帮助我在更大的桌面图像中找到目标图像 (查找窗口的精确 x 和 y 坐标)。 目标图像可能出现在较大的图像中的任何位置,并且可能不完全相同(由于操作系统显示差异,可能非常相似但不完全相同)。

有人知道这样的算法或算法类别吗?

我找到了各种图像分割和计算机视觉算法,但它们似乎是针对区域的“模糊”分类,而不是在另一个图像中定位特定的图像。

** 我的目标是创建一个框架,给定一些初始目标图像,可以查找桌面上的“外观”,找到目标区域并“监视”它是否发生变化。 **

6个回答

12

请看我写的论文:http://werner.yellowcouch.org/Papers/subimg/index.html。它非常详细,似乎是唯一一篇讨论如何将傅里叶变换应用于子图像查找问题的文章。

简而言之,如果想要使用傅里叶变换,可以应用以下公式:当图像A沿着dx、dy移动时,图像A和图像B之间的相关性在以下矩阵中给出:C=ifft(fft(A) x conjugate(fft(B))。因此,在图像C中具有最高值的位置具有最高的相关性,并且该位置反映了dx、dy。

这个结果对于相对较大的子图像效果很好。对于较小的图像,需要进行更多的工作,如文章所述。尽管如此,这样的傅里叶变换非常快速。它的运算量约为3*sxsylog_2(sx*sy)+3*sx*sy。


6
您说您的图像可能不完全相同,但又表示不想要“模糊”的算法。我不确定这两者是否兼容。总的来说,我认为您需要查看图像配准算法。有一个名为ITK的开源C++软件包,可能会提供一些提示。此外,ImageJ是一个流行的开源Java软件包。如果您仔细查看,这两个软件包都具有至少一些可用的配准功能。

6

以下是你需要使用的代码框架:

// look for all (x,y) positions where target appears in desktop
List<Loc> findMatches(Image desktop, Image target, float threshold) {
  List<Loc> locs;
  for (int y=0; y<desktop.height()-target.height(); y++) {
      for (int x=0; x<desktop.width()-target.width(); x++) {
          if (imageDistance(desktop, x, y, target) < threshold) {
              locs.append(Loc(x,y));
          }
      }
   }
   return locs;
}

// computes the root mean squared error between a rectangular window in 
// bigImg and target.
float imageDistance(Image bigImg, int bx, int by, Image target) {
    float sum_dist2 = 0.0;
    for (int y=0; y<target.height(); y++) {
        for (int x=0; x<target.width(); x++) {
            // assume RGB images...
            for (int colorChannel=0; colorChannel<3; colorChannel++) {
                float dist = target.getPixel(x,y) - bigImg.getPixel(bx+x,by+y);
                sum_dist2 += dist * dist;
            }
         }
    }
    return Math.sqrt(sum_dist2 / target.width() / target.height());
}

你可以考虑其他图像距离(请参见类似问题)。 对于您的应用程序,均方根误差可能是一个不错的选择。

可能有各种Java库可以高效地计算此距离。


第二个方法中的bx和by是用来做什么的? - T_01
1
@T_01谢谢,我忘记在bigImg中使用它们来移动比较位置了。代码已经修复。 - Mr Fooz
这里的Loc类是什么?我应该从哪里导入它? - Nodir Nasirov
我应该为阈值参数设置什么值? - user17389840
如果我在8位1通道灰度图像上使用它,距离似乎没有在[0,1]或[0,255]范围内归一化。我认为这是因为sqrt会根据图像大小改变比例尺。我想能够比较不同大小的子图的阈值/距离。您认为将sqrt(dist)更改为“dist /(MAX_PX_VALUE * MAX_PX_VALUE)”是否可行?其中,在我的情况下,MAX_PX_VALUE是255,因为使用了1通道8位图像。 - Nicolás Abram
显示剩余2条评论

2
您可以使用这个目标区域独特的视觉元素来确定它的位置。这些独特的视觉元素就像一种“签名”。例如:独特的图标、图片和符号。如果你在角落里有独特的元素,这种方法就不受窗口分辨率的影响。对于固定大小的窗口,只需要一个元素就足以找到所有的窗口坐标。
下面我将用一个简单的例子使用 Marvin Framework 来说明这个想法。
独特元素:

enter image description here
enter image description here

程序输出:

enter image description here

原图像:
window.png 源代码:
import static marvin.MarvinPluginCollection.*;

public class FindSubimageWindow {
    public FindSubimageWindow(){
        MarvinImage window = MarvinImageIO.loadImage("./res/window.png");
        MarvinImage eclipse = MarvinImageIO.loadImage("./res/eclipse_icon.png");
        MarvinImage progress = MarvinImageIO.loadImage("./res/progress_icon.png");

        MarvinSegment seg1, seg2;
        seg1 = findSubimage(eclipse, window, 0, 0);
        drawRect(window, seg1.x1, seg1.y1, seg1.x2-seg1.x1, seg1.y2-seg1.y1);

        seg2 = findSubimage(progress, window, 0, 0);
        drawRect(window, seg2.x1, seg2.y1, seg2.x2-seg2.x1, seg2.y2-seg2.y1);

        drawRect(window, seg1.x1-10, seg1.y1-10, (seg2.x2-seg1.x1)+25, (seg2.y2-seg1.y1)+20);

        MarvinImageIO.saveImage(window, "./res/window_out.png");
    }
    private void drawRect(MarvinImage image, int x, int y, int width, int height){
        x-=4; y-=4; width+=8; height+=8;
        image.drawRect(x, y, width, height, Color.red);
        image.drawRect(x+1, y+1, width-2, height-2, Color.red);
        image.drawRect(x+2, y+2, width-4, height-4, Color.red);
    }
    public static void main(String[] args) {
        new FindSubimageWindow();
    }   
}

1
我考虑了Werner Van Belle的解决方案(因为所有其他方法都非常缓慢 - 根本不可行):

自适应滤波器用于正确定位子图像:FFT 基于子图像的定位需要图像归一化才能正常工作

我用C#编写了代码,但结果非常不准确。与Van Belle的声明相反,它真的不能很好地工作吗?我使用https://github.com/tszalay/FFTWSharp作为FFTW的C#包装器。
这是我的翻译代码:(原始代码在C++中,位于http://werner.yellowcouch.org/Papers/subimg/index.html
using System.Diagnostics;
using System;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

using FFTWSharp;

using unsigned1 = System.Byte;
using signed2 = System.Int16;
using signed8 = System.Int64;

public class Subimage
{
    /**
     * This program finds a subimage in a larger image. It expects two arguments.
     * The first is the image in which it must look. The secon dimage is the
     * image that is to be found. The program relies on a number of different
     * steps to perform the calculation.
     *
     * It will first normalize the input images in order to improve the
     * crosscorrelation matching. Once the best crosscorrelation is found
     * a sad-matchers is applied in a grid over the larger image.
     *
     * The following two article explains the details:
     *
     *   Werner Van Belle; An Adaptive Filter for the Correct Localization
     *   of Subimages: FFT based Subimage Localization Requires Image
     *   Normalization to work properly; 11 pages; October 2007.
     *   http://werner.yellowcouch.org/Papers/subimg/
     *
     *   Werner Van Belle; Correlation between the inproduct and the sum
     *   of absolute differences is -0.8485 for uniform sampled signals on
     *   [-1:1]; November 2006
     */
    unsafe public static Point FindSubimage_fftw(string[] args)
    {
        if (args == null || args.Length != 2)
        {
            Console.Write("Usage: subimg\n" + "\n" + "  subimg is an image matcher. It requires two arguments. The first\n" + "  image should be the larger of the two. The program will search\n" + "  for the best position to superimpose the needle image over the\n" + "  haystack image. The output of the the program are the X and Y\n" + "  coordinates.\n\n" + "  http://werner.yellowouch.org/Papers/subimg/\n");
            return new Point();
        }

        /**
         * The larger image will be called A. The smaller image will be called B.
         *
         * The code below relies heavily upon fftw. The indices necessary for the
         * fast r2c and c2r transforms are at best confusing. Especially regarding
         * the number of rows and colums (watch our for Asx vs Asx2 !).
         *
         * After obtaining all the crosscorrelations we will scan through the image
         * to find the best sad match. As such we make a backup of the original data
         * in advance
         *
         */
        int Asx = 0, Asy = 0;
        signed2[] A = read_image(args[0], ref Asx, ref Asy);
        int Asx2 = Asx / 2 + 1;

        int Bsx = 0, Bsy = 0;
        signed2[] B = read_image(args[1], ref Bsx, ref Bsy);

        unsigned1[] Asad = new unsigned1[Asx * Asy];
        unsigned1[] Bsad = new unsigned1[Bsx * Bsy];

        for (int i = 0; i < Bsx * Bsy; i++)
        {
            Bsad[i] = (unsigned1)B[i];
            Asad[i] = (unsigned1)A[i];
        }
        for (int i = Bsx * Bsy; i < Asx * Asy; i++)
            Asad[i] = (unsigned1)A[i];

        /**
         * Normalization and windowing of the images.
         *
         * The window size (wx,wy) is half the size of the smaller subimage. This
         * is useful to have as much good information from the subimg.
         */
        int wx = Bsx / 2;
        int wy = Bsy / 2;
        normalize(ref B, Bsx, Bsy, wx, wy);
        normalize(ref A, Asx, Asy, wx, wy);

        /**
         * Preparation of the fourier transforms.
         * Aa is the amplitude of image A. Af is the frequence of image A
         * Similar for B. crosscors will hold the crosscorrelations.
         */
        IntPtr Aa = fftw.malloc(sizeof(double) * Asx * Asy);
        IntPtr Af = fftw.malloc(sizeof(double) * 2 * Asx2 * Asy);
        IntPtr Ba = fftw.malloc(sizeof(double) * Asx * Asy);
        IntPtr Bf = fftw.malloc(sizeof(double) * 2 * Asx2 * Asy);

        /**
         * The forward transform of A goes from Aa to Af
         * The forward tansform of B goes from Ba to Bf
         * In Bf we will also calculate the inproduct of Af and Bf
         * The backward transform then goes from Bf to Aa again. That
         * variable is aliased as crosscors;
         */
        //#original: fftw_plan_dft_r2c_2d
        //IntPtr forwardA = fftwf.dft(2, new int[] { Asy, Asx }, Aa, Af, fftw_direction.Forward, fftw_flags.Estimate);//equal results
        IntPtr forwardA = fftwf.dft_r2c_2d(Asy, Asx, Aa, Af, fftw_flags.Estimate);
        //#original: fftw_plan_dft_r2c_2d
        //IntPtr forwardB = fftwf.dft(2, new int[] { Asy, Asx }, Ba, Bf, fftw_direction.Forward, fftw_flags.Estimate);//equal results
        IntPtr forwardB = fftwf.dft_r2c_2d(Asy, Asx, Ba, Bf, fftw_flags.Estimate);
        double* crosscorrs = (double*)Aa;
        //#original: fftw_plan_dft_c2r_2d
        //IntPtr backward = fftwf.dft(2, new int[] { Asy, Asx }, Bf, Aa, fftw_direction.Backward, fftw_flags.Estimate);//equal results
        IntPtr backward = fftwf.dft_c2r_2d(Asy, Asx, Bf, Aa, fftw_flags.Estimate);

        /**
         * The two forward transforms of A and B. Before we do so we copy the normalized
         * data into the double array. For B we also pad the data with 0
         */
        for (int row = 0; row < Asy; row++)
            for (int col = 0; col < Asx; col++)
                ((double*)Aa)[col + Asx * row] = A[col + Asx * row];
        fftw.execute(forwardA);

        for (int j = 0; j < Asx * Asy; j++)
            ((double*)Ba)[j] = 0;
        for (int row = 0; row < Bsy; row++)
            for (int col = 0; col < Bsx; col++)
                ((double*)Ba)[col + Asx * row] = B[col + Bsx * row];
        fftw.execute(forwardB);

        /**
         * The inproduct of the two frequency domains and calculation
         * of the crosscorrelations
         */
        double norm = Asx * Asy;
        for (int j = 0; j < Asx2 * Asy; j++)
        {
            double a = ((double*)Af)[j * 2];//#Af[j][0];
            double b = ((double*)Af)[j * 2 + 1];//#Af[j][1];
            double c = ((double*)Bf)[j * 2];//#Bf[j][0];
            double d = ((double*)Bf)[j * 2 + 1];//#-Bf[j][1];
            double e = a * c - b * d;
            double f = a * d + b * c;
            ((double*)Bf)[j * 2] = (double)(e / norm);//#Bf[j][0] = (fftw_real)(e / norm);
            ((double*)Bf)[j * 2 + 1] = (double)(f / norm);//Bf[j][1] = (fftw_real)(f / norm);
        }
        fftw.execute(backward);

        /**
         * We now have a correlation map. We can spent one more pass
         * over the entire image to actually find the best matching images
         * as defined by the SAD.
         * We calculate this by gridding the entire image according to the
         * size of the subimage. In each cel we want to know what the best
         * match is.
         */
        int sa = 1 + Asx / Bsx;
        int sb = 1 + Asy / Bsy;
        int sadx = 0;
        int sady = 0;
        signed8 minsad = Bsx * Bsy * 256L;
        for (int a = 0; a < sa; a++)
        {
            int xl = a * Bsx;
            int xr = xl + Bsx;
            if (xr > Asx) continue;
            for (int b = 0; b < sb; b++)
            {
                int yl = b * Bsy;
                int yr = yl + Bsy;
                if (yr > Asy) continue;

                // find the maximum correlation in this cell
                int cormxat = xl + yl * Asx;
                double cormx = crosscorrs[cormxat];
                for (int x = xl; x < xr; x++)
                    for (int y = yl; y < yr; y++)
                    {
                        int j = x + y * Asx;
                        if (crosscorrs[j] > cormx)
                            cormx = crosscorrs[cormxat = j];
                    }
                int corx = cormxat % Asx;
                int cory = cormxat / Asx;

                // We dont want subimages that fall of the larger image
                if (corx + Bsx > Asx) continue;
                if (cory + Bsy > Asy) continue;

                signed8 sad = 0;
                for (int x = 0; sad < minsad && x < Bsx; x++)
                    for (int y = 0; y < Bsy; y++)
                    {
                        int j = (x + corx) + (y + cory) * Asx;
                        int i = x + y * Bsx;
                        sad += Math.Abs((int)Bsad[i] - (int)Asad[j]);
                    }

                if (sad < minsad)
                {
                    minsad = sad;
                    sadx = corx;
                    sady = cory;
                    // printf("* ");
                }
                // printf("Grid (%d,%d) (%d,%d) Sip=%g Sad=%lld\n",a,b,corx,cory,cormx,sad);
            }
        }
        //Console.Write("{0:D}\t{1:D}\n", sadx, sady);
        /**
         * Aa, Ba, Af and Bf were allocated in this function
         * crosscorrs was an alias for Aa and does not require deletion.
         */
        fftw.free(Aa);
        fftw.free(Ba);
        fftw.free(Af);
        fftw.free(Bf);
        return new Point(sadx, sady);
    }

    private static void normalize(ref signed2[] img, int sx, int sy, int wx, int wy)
    {
        /**
         * Calculate the mean background. We will subtract this
         * from img to make sure that it has a mean of zero
         * over a window size of wx x wy. Afterwards we calculate
         * the square of the difference, which will then be used
         * to normalize the local variance of img.
         */
        signed2[] mean = boxaverage(img, sx, sy, wx, wy);
        signed2[] sqr = new signed2[sx * sy];
        for (int j = 0; j < sx * sy; j++)
        {
            img[j] -= mean[j];
            signed2 v = img[j];
            sqr[j] = (signed2)(v * v);
        }
        signed2[] var = boxaverage(sqr, sx, sy, wx, wy);
        /**
         * The normalization process. Currenlty still
         * calculated as doubles. Could probably be fixed
         * to integers too. The only problem is the sqrt
         */
        for (int j = 0; j < sx * sy; j++)
        {
            double v = Math.Sqrt(Math.Abs((double)var[j]));//#double v = sqrt(fabs(var[j])); <- ambigous
            Debug.Assert(!double.IsInfinity(v) && v >= 0);
            if (v < 0.0001) v = 0.0001;
            img[j] = (signed2)(img[j] * (32 / v));
            if (img[j] > 127) img[j] = 127;
            if (img[j] < -127) img[j] = -127;
        }
        /**
         * As a last step in the normalization we
         * window the sub image around the borders
         * to become 0
         */
        window(ref img, sx, sy, wx, wy);
    }

    private static signed2[] boxaverage(signed2[] input, int sx, int sy, int wx, int wy)
    {
        signed2[] horizontalmean = new signed2[sx * sy];

        Debug.Assert(horizontalmean != null);
        int wx2 = wx / 2;
        int wy2 = wy / 2;
        int from = (sy - 1) * sx;
        int to = (sy - 1) * sx;
        int initcount = wx - wx2;
        if (sx < initcount) initcount = sx;
        int xli = -wx2;
        int xri = wx - wx2;
        for (; from >= 0; from -= sx, to -= sx)
        {
            signed8 sum = 0;
            int count = initcount;
            for (int c = 0; c < count; c++)
                sum += (signed8)input[c + from];
            horizontalmean[to] = (signed2)(sum / count);
            int xl = xli, x = 1, xr = xri;
            /**
             * The area where the window is slightly outside the
             * left boundary. Beware: the right bnoundary could be
             * outside on the other side already
             */
            for (; x < sx; x++, xl++, xr++)
            {
                if (xl >= 0) break;
                if (xr < sx)
                {
                    sum += (signed8)input[xr + from];
                    count++;
                }
                horizontalmean[x + to] = (signed2)(sum / count);
            }
            /**
             * both bounds of the sliding window
             * are fully inside the images
             */
            for (; xr < sx; x++, xl++, xr++)
            {
                sum -= (signed8)input[xl + from];
                sum += (signed8)input[xr + from];
                horizontalmean[x + to] = (signed2)(sum / count);
            }
            /**
             * the right bound is falling of the page
             */
            for (; x < sx; x++, xl++)
            {
                sum -= (signed8)input[xl + from];
                count--;
                horizontalmean[x + to] = (signed2)(sum / count);
            }
        }

        /**
         * The same process as aboe but for the vertical dimension now
         */
        int ssy = (sy - 1) * sx + 1;
        from = sx - 1;
        signed2[] verticalmean = new signed2[sx * sy];
        Debug.Assert(verticalmean != null);
        to = sx - 1;
        initcount = wy - wy2;
        if (sy < initcount) initcount = sy;
        int initstopat = initcount * sx;
        int yli = -wy2 * sx;
        int yri = (wy - wy2) * sx;
        for (; from >= 0; from--, to--)
        {
            signed8 sum = 0;
            int count = initcount;
            for (int d = 0; d < initstopat; d += sx)
                sum += (signed8)horizontalmean[d + from];
            verticalmean[to] = (signed2)(sum / count);
            int yl = yli, y = 1, yr = yri;
            for (; y < ssy; y += sx, yl += sx, yr += sx)
            {
                if (yl >= 0) break;
                if (yr < ssy)
                {
                    sum += (signed8)horizontalmean[yr + from];
                    count++;
                }
                verticalmean[y + to] = (signed2)(sum / count);
            }
            for (; yr < ssy; y += sx, yl += sx, yr += sx)
            {
                sum -= (signed8)horizontalmean[yl + from];
                sum += (signed8)horizontalmean[yr + from];
                verticalmean[y + to] = (signed2)(sum / count);
            }
            for (; y < ssy; y += sx, yl += sx)
            {
                sum -= (signed8)horizontalmean[yl + from];
                count--;
                verticalmean[y + to] = (signed2)(sum / count);
            }
        }
        return verticalmean;
    }

    private static void window(ref signed2[] img, int sx, int sy, int wx, int wy)
    {
        int wx2 = wx / 2;
        int sxsy = sx * sy;
        int sx1 = sx - 1;
        for (int x = 0; x < wx2; x++)
            for (int y = 0; y < sxsy; y += sx)
            {
                img[x + y] = (signed2)(img[x + y] * x / wx2);
                img[sx1 - x + y] = (signed2)(img[sx1 - x + y] * x / wx2);
            }

        int wy2 = wy / 2;
        int syb = (sy - 1) * sx;
        int syt = 0;
        for (int y = 0; y < wy2; y++)
        {
            for (int x = 0; x < sx; x++)
            {
                /**
                 * here we need to recalculate the stuff (*y/wy2)
                 * to preserve the discrete nature of integers.
                 */
                img[x + syt] = (signed2)(img[x + syt] * y / wy2);
                img[x + syb] = (signed2)(img[x + syb] * y / wy2);
            }
            /**
             * The next row for the top rows
             * The previous row for the bottom rows
             */
            syt += sx;
            syb -= sx;
        }
    }

    private static signed2[] read_image(string filename, ref int sx, ref int sy)
    {
        Bitmap image = new Bitmap(filename);
        sx = image.Width;
        sy = image.Height;
        signed2[] GreyImage = new signed2[sx * sy];
        BitmapData bitmapData1 = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
        unsafe
        {
            byte* imagePointer = (byte*)bitmapData1.Scan0;

            for (int y = 0; y < bitmapData1.Height; y++)
            {
                for (int x = 0; x < bitmapData1.Width; x++)
                {
                    GreyImage[x + y * sx] = (signed2)((imagePointer[0] + imagePointer[1] + imagePointer[2]) / 3.0);
                    //4 bytes per pixel
                    imagePointer += 4;
                }//end for x
                //4 bytes per pixel
                imagePointer += bitmapData1.Stride - (bitmapData1.Width * 4);
            }//end for y
        }//end unsafe
        image.UnlockBits(bitmapData1);
        return GreyImage;
    }
}

举个例子来说明不准确性,当我在一张黑色的1004x1080图片中搜索一个白色的246x144矩形(位于302,206位置),结果却是{X=246,Y=144}。当我尝试使用更复杂的图片时,结果甚至更糟糕。 - xamid
结果与图像的宽度完全相同吗?我有一种感觉,你在这种情况下弄错了一些变量名 :) - user458577
顺便问一下,这种语句是什么意思:“IntPtr Aa = fftw.malloc(sizeof(double) * Asx * Asy);”如果你要分配double类型的内存,那么不应该使用DoublePtr吗?或者我的C#有问题? - user458577
IntPtr在C#中代表指针,其中“Int”表示指针本身存储为整数(在x84系统中为32位,在x64系统中为64位)。您认为为什么需要类似DoublePtr的东西?您可以在不安全区域将IntPtr强制转换为double*,它会像双重指针一样正常工作(sizeof(double)的距离)。您发现了任何真正的错误吗? - xamid

0

你不需要像“神经网络”一样使用模糊,因为(据我所知)你没有旋转、倾斜或类似的问题。如果操作系统显示差异是唯一的修改,那么差异应该很小。 因此,WernerVanBelle的论文很好,但并不是必须的,而MrFooz的代码虽然可行,但效率很低(O(width * height * patter_width * pattern_height)!)。

我能想到的最好的算法是Boyer-Moore字符串搜索算法,经过修改可以进行二维搜索。 http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string_search_algorithm

你需要为每种颜色存储一对位移量dxdy。当检查一个像素时,只在x方向上移动x = x + dx,并且只存储dy的最小值DY = min(DY, dy),以设置在整个线路被测试后的新的y值(即x > width)。

由于可能存在大量的颜色,为所有可能的颜色创建一个表格可能是不可行的,因此可以使用映射来存储规则(如果颜色不在映射中,则默认为模式尺寸),或者分别为每种颜色创建表格,并设置dx = max(dx(red), dx(green), dx(blue)) - 这只是一个近似值,但可以消除映射的开销。

在坏字符规则的预处理中,您可以通过将所有颜色的规则传播到它们的“相邻”颜色(无论您如何定义相邻)来考虑颜色的小偏差。


我不确定dy部分。始终将y=y+pattern_height设置为更有效,然后在x方向扫描模式的每一行。只对x方向应用Bayer-Moore算法,并使用几个搜索模式。我想在这里问同样的问题(看看是否有人知道比Boyer-Moore更好的算法),只是发现我会创建一个重复... - example
我认为你严重低估了这个问题,因为如果涉及到一些模糊性,子字符串匹配是无法帮助你的。如果你有一些比我提供的解决方案更快的工作代码可以贡献出来,那么我当然非常有兴趣看看。含糊不清的“你可以考虑这个”解决方案并不真正起作用。 - user458577
@WernerVanBelle,该操作者表示,他不想要您所描述的任何模糊性(但它们似乎是针对区域的“模糊”分类,而不是在另一个图像中定位特定图像。)。据我理解,他想要找到具有完全相同缩放和旋转的图片(例如,创建类似于自动点击宏),在这种情况下,我认为使用比您提出的更专业的算法更好。例如,考虑他想要找到红色按钮的一部分-那么他很可能会跳过屏幕上98%的与“红色”无关的部分。 - example
原帖中提到:“目标图像可能位于较大图像的任何位置,而且可能不完全相同”,这基本上就是“模糊性”。 - user458577
@WernerVanBelle 我想问题并不完全清楚。我猜根据问题的解释(或者也许是任何其他人的意图),你的或我的答案可能更合适。 - example

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接