OpenCV

OpencvForUnity 插件的文档  :https://enoxsoftware.github.io/OpenCVForUnity/3.0.0/doc/html/annotated.html

OpenCVSharp :    https://github.com/shimat/opencvsharp

OpenCVSharp 文档: https://shimat.github.io/opencvsharp_docs/html/47b728ac-aff3-f998-4902-42d117710f4d.htm#!

如果没使用Nuget则获取DLL文件的方式,https://github.com/shimat/opencvsharp/releases

找dll库网:https://www.zhaodll.com/dll/softdown.asp?iz2=8aeff38ef164ad5eda73195ddb24ff96&softid=340814

OpenCV  EmguCV :  https://www.jianshu.com/p/8a24b593c83a 

EmguCV在unity上的部署:    https://forum.unity.com/threads/opencv-emgucv-wrapper-integration-in-unity.182600/

Dll库: https://www.pconlife.com/viewfileinfo/opencv-core249-dll/

opencv: 下载链接 https://opencv.org/releases/page/4/   https://www.cnblogs.com/icyhoo/p/6474053.html

opencv2.4文档:   https://docs.opencv.org/2.4/modules/core/doc/old_basic_structures.html?highlight=iplimage#IplImage

查看.net库的.dll是多少位:

1. 下载Reflector

2. 打开

3. 输入指令

4. 会打开Reflector

 

Unity 导入.net架构高于Unity的dll时,则会引发编译错误:

 

 

Unity Native Plugins:

导入或者卸载后需要重新启动Unity.

 

Unity使用OpencvSharp :

方式一: 

https://blog.csdn.net/vincentff7zg/article/details/54691783  //创建流程

https://www.bbsmax.com/A/Gkz10WEgdR/  //可能存在的问题解决方案

https://www.bbsmax.com/A/mo5kl3AKdw/    //可能存在的问题解决方案

<1.需要下载对应版本的opencv;

   链接 https://opencv.org/releases/page/4/ 

   下载安装后,把里面c#需要封装的opencv dll库全部拷到unity plugins中。

    

<2.需要下载对应版本的OpencvSharp;

     --1. 需要在vs中把下载后的OpencvSharp. 因为下载高版本.net框架太高,在unity里可能不能用。

           

    ---2. 打开源码: 提示是否更新,则选择更新,相当于你用vs2015打开了低版本的工程。会把环境定向到低版本环境中。

            

   ---3.  重新生成这三个的解决方案:

    https://www.youtube.com/watch?v=tUWoKxH3zxw   //youtobe老外生成的教程,他是32位的哦

    由于OpenCvSharpExtern是c++写的,所以我这里重新生成时会出错,因为虽然定向到了低版本,但是重新生成时,需要的低版本的库找不到。所以我只生成了前两个,如果你的可以那就更好。把前两个生成的Debug中dll文件拷贝到Unity plugins中。

         

---4.第三个生成方式:新建一个vs工程,使用NuGet包管理器来获取,注意一定要把版本下对。

      

   

<4. 把生成的dll文件和opencv中对应的dll文件,拷贝到plugins中。应该可以直接从此处把dll文件导入,你们可以试一试

     

 <5. 大功告成:再不报错了

       

 

方式二:

   <1. 新建一个vs工程,使用NuGet包管理器来获取,注意一定要把版本下对。

      

   

   <2. 应该可以直接从此处把dll文件导入,你们可以试一试

     

<3. 测试后可以使用,但是有个粗无,找不到一个库. 可以用这种方式比较简单,不容易出错。

      

 

对使用指针的操作:

 

 

 

一、没有模板的情况——根据一定规则进行识别

对于背景不是纯色的图片:

    /// <summary>
    /// 分水岭算法
    /// MatchShapes Example
    /// https://baike.baidu.com/item/%E5%9B%BE%E5%83%8F%E4%BA%8C%E5%80%BC%E5%8C%96/1748870?fr=aladdin
    /// http://docs.opencv.org/3.1.0/d5/d45/tutorial_py_contours_more_functions.html
    /// https://docs.opencv.org/3.1.0/df/d4e/group__imgproc__c.html#ga5c79f8ca95dc7a93f7142b218bea61f3  //文档
    /// https://docs.opencv.org/3.1.0/d7/d1b/group__imgproc__misc.html#gae8a4a146d1ca78c626a53577199e9c57  //文档  threshold
    /// 
    ///  会把背景上的颜色分割出来,对图像轮廓点进行多边形拟合,对比顶点个数 【分水岭算法】  优先使用二值法,不行了再考虑这种方法,这种方法误差较大

 

using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
using System.Collections;

#if UNITY_5_3 || UNITY_5_3_OR_NEWER
using UnityEngine.SceneManagement;
#endif
using OpenCVForUnity;

namespace OpenCVForUnityExample
{
    /// <summary>
    /// 分水岭算法
    /// MatchShapes Example
    /// https://baike.baidu.com/item/%E5%9B%BE%E5%83%8F%E4%BA%8C%E5%80%BC%E5%8C%96/1748870?fr=aladdin
    /// http://docs.opencv.org/3.1.0/d5/d45/tutorial_py_contours_more_functions.html
    /// https://docs.opencv.org/3.1.0/df/d4e/group__imgproc__c.html#ga5c79f8ca95dc7a93f7142b218bea61f3  //文档
    /// https://docs.opencv.org/3.1.0/d7/d1b/group__imgproc__misc.html#gae8a4a146d1ca78c626a53577199e9c57  //文档  threshold
    /// https://enoxsoftware.github.io/OpenCVForUnity/3.0.0/doc/html/annotated.html  //这个插件的文档
    ///  会把背景上的颜色分割出来,对图像轮廓点进行多边形拟合,对比顶点个数 【分水岭算法】  优先使用二值法,不行了再考虑这种方法,这种方法误差较大
    /// 
    /// </summary>
    public class test_MatchShapesExample : MonoBehaviour
    {
        public float threshValue = 127;  //阀值
        public float periMin = 200;
        public float periMax = 1000;
        void Update()
        {
            if (Input.GetMouseButtonDown(1))
            {

                //分水岭算法
                //srcMat
                Texture2D srcTexture = Resources.Load("matchshapes_Creat_Src") as Texture2D;
                Mat srcMat = new Mat(srcTexture.height, srcTexture.width, CvType.CV_8UC1);
                Utils.texture2DToMat(srcTexture, srcMat);
                Debug.Log("srcMat.ToString() " + srcMat.ToString());
                Imgproc.threshold(srcMat, srcMat, threshValue, 255, Imgproc.THRESH_OTSU);   

                //dstMat
                Texture2D dstTexture = Resources.Load("matchshapes_Creat_Src") as Texture2D;
                Mat dstMat = new Mat(dstTexture.height, dstTexture.width, CvType.CV_8UC3);
                Utils.texture2DToMat(dstTexture, dstMat);
                Debug.Log("dstMat.ToString() " + dstMat.ToString());


                List<MatOfPoint> srcContours = new List<MatOfPoint>();
                Mat srcHierarchy = new Mat();

                /// Find srcContours
                Imgproc.findContours(srcMat, srcContours, srcHierarchy, Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_NONE);

                Debug.Log("srcContours.Count " + srcContours.Count);

                for (int i = 0; i < srcContours.Count; i++)
                {
                    Imgproc.drawContours(dstMat, srcContours, i, new Scalar(255, 0, 0), 2, 8, srcHierarchy, 0, new Point());
                }

                for (int i = 0; i < srcContours.Count; i++)
                {
                    double returnVal = Imgproc.matchShapes(srcContours[1], srcContours[i], Imgproc.CV_CONTOURS_MATCH_I1, 0);
                    //Debug.Log("returnVal " + i + " " + returnVal);

                    MatOfPoint2f newMatOfPoint2f = new MatOfPoint2f(srcContours[i].toArray());
                    //主要是计算图像轮廓的周长
                    double peri = Imgproc.arcLength(newMatOfPoint2f, true);
                    if (peri < periMin || peri > periMax)
                    {
                        continue;
                    }
                    else
                    {
                        //对图像轮廓点进行多边形拟合
                        MatOfPoint2f polyShape = new MatOfPoint2f();
                        Imgproc.approxPolyDP(newMatOfPoint2f, polyShape, 0.04f * peri, true);
                        int shapeLen = polyShape.toArray().Length;

                        if (shapeLen >= 3 && shapeLen <= 20)
                        {
                            Debug.LogWarning(shapeLen + "        " + returnVal);
                            OpenCVForUnity.Rect rect = Imgproc.boundingRect(srcContours[i]);
                            float width = rect.width;
                            float height = rect.height;
                            float ar = width / height;
                            //计算宽高比,判断是矩形还是正方形
                            string name = null;
                            if (shapeLen == 3) { name = "triangle"; }
                            else if (shapeLen == 4)
                            {
                                //计算宽高比,判断是矩形还是正方形
                                if (ar >= 0.95 && ar <= 1.05) { name = "square"; } else { name = "rectangle"; }
                            }
                            else { name = shapeLen.ToString(); }

                            Point point = new Point();
                            float[] radius = new float[1];
                            Imgproc.minEnclosingCircle(new MatOfPoint2f(srcContours[i].toArray()), point, radius);
                            //Debug.Log("point.ToString() " + point.ToString());
                            //Debug.Log("radius.ToString() " + radius[0]);

                            Imgproc.circle(dstMat, point, 5, new Scalar(0, 0, 255), -1);
                            Imgproc.putText(dstMat, " " + name, point, Core.FONT_HERSHEY_SIMPLEX, 0.4, new Scalar(0, 255, 0), 1, Imgproc.LINE_AA, false);
                        }
                    }
                }
                Texture2D texture = new Texture2D(dstMat.cols(), dstMat.rows(), TextureFormat.RGBA32, false);
                Utils.matToTexture2D(dstMat, texture);
                GameObject.Find("pic_RawImage").GetComponent<RawImage>().texture = texture;
            }
        }

    }
}

对于背景是纯色的图片识别图片中的形状:

            //此图同背景的差别表现不在灰度值上(比如纹理不同),所以将这个差别特征转换为灰度的差别,然后利用阈值选取技术来分割该图像,动态调节阈值实现图像的二值化可动态观察其分割图像的具体结果。
            //一般用在背景是同一种颜色时,如果背景含有多种颜色,则不行  
using OpenCVForUnity;
using OpenCVForUnityExample;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// https://blog.csdn.net/u013293580/article/details/84710933#commentsedit
/// </summary>
public class shapeDectedScript : MonoBehaviour {

    public RawImage color_Pic; //UI
    private Mat srcMat; //Mat格式存放处理的图片

    public float size=5;
    public float threshValue = 80;  //阀值
    private Mat dstMat; //Mat格式存放处理的图片
    private Texture2D flipTexture;
    public List<ShapeInfo> listShape=new List<ShapeInfo>(); 

    private void Update()
    {
            if (Input.GetMouseButtonDown(1))
        {


            System.Diagnostics.Stopwatch oTime = new System.Diagnostics.Stopwatch();   //定义一个计时对象  
            oTime.Start();


            listShape.Clear();
            Texture2D tex = (Texture2D)GameObject.Find("Quad").GetComponent<MeshRenderer>().materials[0].mainTexture;
            Texture2D flipTexture = new Texture2D(tex.width, tex.height);
            for (int i = 0; i < tex.height; i++)
            {
                flipTexture.SetPixels(0, i, tex.width, 1, tex.GetPixels(0, tex.height - i - 1, tex.width, 1));
            }
            flipTexture.Apply();



            //此图同背景的差别表现不在灰度值上(比如纹理不同),所以将这个差别特征转换为灰度的差别,然后利用阈值选取技术来分割该图像,动态调节阈值实现图像的二值化可动态观察其分割图像的具体结果。
            //一般用在背景是同一种颜色时,如果背景含有多种颜色,则不行  

            srcMat = new Mat(flipTexture.height, flipTexture.width, CvType.CV_8UC1);
            Utils.texture2DToMat(flipTexture, srcMat);

            //读取图片   参照图   白色线
           // srcMat = Imgcodecs.imread(Application.dataPath + "/Resources/matchshapes_Creat_Test.png", 1);
            //图片颜色模式转换,转为灰度图
            Imgproc.cvtColor(srcMat, srcMat, Imgproc.COLOR_BGR2GRAY);
            //图片高斯模糊处理   
            Imgproc.GaussianBlur(srcMat, srcMat, new Size(size, size), 0);   // 使得内部有均匀一致的灰度值,而且此图片的背景是在其他等级灰度值的均匀背景
            //图片二值化处理   特征转换为灰度的差别,再利用阀值进行分离背景
            Imgproc.threshold(srcMat, srcMat, threshValue, 255, Imgproc.THRESH_BINARY);  //图像的集合性质只与像素值为0或255的点的位置有关,不再涉及像素的多级值.






            dstMat = new Mat(flipTexture.height, flipTexture.width, CvType.CV_8UC1);
            Utils.texture2DToMat(flipTexture, dstMat);
            //读取新图片
            //dstMat =Imgcodecs.imread(Application.dataPath + "/Resources/matchshapes_Creat_Test.png", 1);
            //Imgproc.COLOR_BGR2RGB的颜色模式可以让图片保持原色
            Imgproc.cvtColor(dstMat, dstMat, Imgproc.COLOR_BGR2RGB);
            //对新图片何原图片进行对比
            List<MatOfPoint> srcContours = new List<MatOfPoint>();
            Mat srcHierarchy = new Mat();
            //寻找轮廓
            Imgproc.findContours(srcMat, srcContours, srcHierarchy, Imgproc.RETR_CCOMP, Imgproc.CHAIN_APPROX_NONE);
            for (int i = 0; i < srcContours.Count; i++)
            {
                //轮廓描边
                //Imgproc.drawContours(dstMat, srcContours, i, new Scalar(255, 0, 0), 2, 8, srcHierarchy, 0, new Point());
                Point point = new Point();
                float[] radius = new float[1];
                //获取点集最小外接圆点
                Imgproc.minEnclosingCircle(new MatOfPoint2f(srcContours[i].toArray()), point, radius);
                //在圆点位置绘制圆形
                Imgproc.circle(dstMat, point, 7, new Scalar(255, 255, 0), -1);
                MatOfPoint2f newMatOfPoint2f = new MatOfPoint2f(srcContours[i].toArray());
                enDetectShape enShape = detect(srcContours[i], newMatOfPoint2f);
                //在图形圆心的(20,20)的右上方会绘制该轮廓的名字
                Imgproc.putText(dstMat, enShape.ToString(), new Point(point.x - 20, point.y - 20), Core.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(255, 0, 0), 2, Imgproc.LINE_AA, false);

                if (enShape != enDetectShape.Null)
                {
                    ShapeInfo shapeInfo = new ShapeInfo();
                    shapeInfo.point = new Vector2((float)point.x, tex.height - (float)point.y);
                    shapeInfo.enShape = enShape;
                    listShape.Add(shapeInfo);
                }
                
            }


            //定义Texture2D设置其宽高随scrMat材质颜色模式为RGBA32
             texture = new Texture2D(dstMat.cols(), dstMat.rows(), TextureFormat.RGBA32, false);
            //把Mat格式转换成texture格式
            Utils.matToTexture2D(dstMat, texture);
            //把texture贴在UI RawImage上

            color_Pic.texture = texture;
            color_Pic.material.mainTexture= texture;

            oTime.Stop();  
            Debug.LogError("程序的运行时间:{0} 秒" + "   " + oTime.Elapsed.TotalSeconds);
            Debug.LogError("程序的运行时间:{0} 毫秒" + "    " + oTime.Elapsed.TotalMilliseconds);
        }


    }  
   //圆和星星的判断   https://blog.51cto.com/xvjunjie/2416635?source=dra
    //在检测时发现有时候圆和星星有可能被误识别,通过获取轮廓上第一个点和图形的中心点。计算两点的距离,使用圆的面积公式求面积。
    //在使用OpenCV3.3中自带的轮廓区域面积函数获取面积。通过比较来判断是圆形还是星星,因为同等距离的星星的面积比圆小。
    public Texture2D test;
    public Texture2D texture;
    public enDetectShape detect(MatOfPoint mp, MatOfPoint2f mp2f)
    {
        enDetectShape enShape = enDetectShape.Null;
        //主要是计算图像轮廓的周长
        double peri = Imgproc.arcLength(mp2f, true);
      
        //对图像轮廓点进行多边形拟合
        MatOfPoint2f polyShape = new MatOfPoint2f();
        Imgproc.approxPolyDP(mp2f, polyShape, 0.04f * peri, true);
        int shapeLen = polyShape.toArray().Length;
        Debug.Log(shapeLen);
        //根据轮廓凸点拟合结果,判断属于那个形状
        switch (shapeLen)
        {
            case 3:
                enShape = enDetectShape.Null;
                break;
            case 4:
                OpenCVForUnity.Rect rect = Imgproc.boundingRect(mp);
                float width = rect.width;
                float height = rect.height;
                float ar = width / height;
                //计算宽高比,判断是矩形还是正方形
                if (ar >= 0.95 && ar <= 1.05)
                {
                    enShape = enDetectShape.Square;
                }
                else
                {
                    enShape = enDetectShape.Rectangle;  
                }
                break;
            case 5:
                enShape = enDetectShape.Pentagon;
                break;
            default:
                enShape = enDetectShape.Circle; 
                break;
        }
        return enShape;
    }
}

public class ShapeInfo
{
    public Vector3 point;
    public enDetectShape enShape;
}

public enum enDetectShape
{
    Null,
    Triangle,
    Square,
    Rectangle,
    Pentagon,
    Circle,
}

注意:该图片翻转是 由于direct是左上和opengl是左下坐标的问题。

 

二、有模板图片——比较图片中形状的相似度

https://blog.csdn.net/zhuason/article/details/78933250

1.输入图像

<1. Mat存在各种类型,maketype()  函数可以返回该Mat的类型。类型表示了材质中元素的类型以及材质的通道个数.

CvType:
    CvType.CV_8UC4是8位无符号四通道带透明色RGB图像,每一个像素在内存空间占的空间大小是8位。
    CvType.CV_8UC1是 1通道(单通道) 

    CV_CN_SHIFT = 3;
    CV_DEPTH_MAX = (1 << CV_CN_SHIFT);   //= 1*2^3=8
    //前半句 :  把深度编号和(8-1)进行与运算  +    后半句:  (channels - 1)*2^3       
    //与运算目的:取某数中指定位   即:类型 = 数据类型的编号 + (通道数 - 1) * 8
    公式:  (depth & (CV_DEPTH_MAX - 1)) + ((channels - 1) << CV_CN_SHIFT);  

    Mat M(3,2,CV_8SC3,Scalar(0,0,4));
    cout<<"M="<<M<<endl;


    创造了一个行数(高度)是3,列数(宽度)是2的图像,图像的type是CV_8SC3,表示图像的元素是8位无符        号整数,3个通道,图像所有的元素都被初始化位(0,0,255),由于opencv颜色的顺序是BGR,所以这张图片是全红色。
    常用的构造函数经常涉及到类型 type。type 可以是 CV_8UC1,CV_16SC1,…, CV_64FC4 等。里面的 8U 表示 8 位无符号整数,16S 表示 16 位有符号整数,64F 表示 64 位浮点数(即 double 类型);C 后面的数表示通道数,例如 C1 表示一个 通道的图像,C4 表示 4 个通道的图像,以此类推。如果为表明通道数,则为如果你需要更多的通道数,需要用宏 CV_8UC(n),例如:Mat M(3,2, CV_8UC(5))
Mat中重定义了<<操作符,可以直接输出所有像素值

Imgproc.cs: 图片处理类

 Imgproc.GaussianBlur;

c#和c++之间的问题:

.net3.0   intptr和int类型不能相加;  再.net4.0才可以相加。所以要把intptr类型转换为int32()类型。

 

一:感知哈希算法

<根据图像的明暗信息,对每张图片生成一个"指纹"(fingerprint)字符串,然后比较不同图片的指纹。结果越接近,就说明图片越相似。

第一步,缩小尺寸。

将图片缩小到8x8的尺寸,总共64个像素。这一步的作用是去除图片的细节,只保留结构、明暗等基本信息,摒弃不同尺寸、比例带来的图片差异。

 

第二步,简化色彩。

将缩小后的图片,转为64级灰度。也就是说,所有像素点总共只有64种颜色。

第三步,计算平均值。

计算所有64个像素的灰度平均值。

第四步,比较像素的灰度。

将每个像素的灰度,与平均值进行比较。大于或等于平均值,记为1;小于平均值,记为0。因为图片相似度和颜色关系不大

第五步,计算哈希值。

方差: 在概率论和统计方差衡量随机变量或一组数据时离散程度的度量。概率论中方差用来度量随机变量和其数学期望(即均值)之间的偏离程度

将上一步的比较结果,组合在一起,就构成了一个64位的整数,这就是这张图片的指纹。组合的次序并不重要,只要保证所有图片都采用同样次序就行了。

汉明距离: (Hamming distance,在信息论中,两个等长字符串之间的汉明距离是两个字符串对应位置的不同字符的个数)

 =  = 8f373714acfcf4d0

//链接  https://blog.csdn.net/wangyaninglm/article/details/43853435#_935

//链接 https://blog.csdn.net/haofan_/article/details/78806143


    //图像大小(会压缩)   颜色区域分布在同一 块的比例  
    int size2 = 512;
    //截取所画区域的图
    //根据灰度直方图来比较
    //hash 参考链接  https://blog.csdn.net/wangyaninglm/article/details/43853435
    //opencv doc https://docs.opencv.org/master/da/d54/group__imgproc__transform.html#gga5bb5a1fea74ea38e1a5445ca803ff121aa5521d8e080972c762467c45f3b70e6c
    //opencv doc of old version https://docs.opencv.org/2.4/modules/gpu/doc/image_processing.html?highlight=inter_nearest
    unsafe void Fun3()
    {
        Mat matSrc=new Mat(), matSrc1=new Mat(), matSrc2=new Mat(),  matSrc4 = new Mat();
        matSrc = Cv2.ImRead(Application.dataPath + "/Resources/YaoM2.png", LoadMode.Color);
        matSrc4 = Cv2.ImRead(Application.dataPath + "/Resources/YaoM3.png", LoadMode.Color);

        if (matSrc.Channels() != 3) { Debug.LogError("error  " + matSrc.Channels()); return; }
        //Cv2.Resize(matSrc, matSrc1, Cv.Size(size2, size2), 0, 0, Interpolation.Cubic);
        if (matSrc4.Channels() != 3) { Debug.LogError("error  " + matSrc4.Channels()); return; }
        //Cv2.Resize(matSrc4, matSrc2, Cv.Size(size2, size2), 0, 0, Interpolation.Cubic);
   


        Mat matDst1 =new Mat(), matDst2=new Mat();
        //Cv2.Resize(matSrc1, matDst1, Cv.Size(8, 8), 0, 0, Interpolation.Cubic);  
        //Cv2.Resize(matSrc2, matDst2, Cv.Size(8, 8), 0, 0, Interpolation.Cubic);
        Cv2.Resize(matSrc, matDst1, Cv.Size(16, 16), 0, 0, Interpolation.Cubic);
        Cv2.Resize(matSrc4, matDst2, Cv.Size(16, 16), 0, 0, Interpolation.Cubic);


        //灰度,因为图片相似度和颜色关系不大
        //Mat temp1 = matDst1;
        //Mat temp2 = matDst2;
        Cv2.CvtColor(matDst1, matDst1, ColorConversion.BgrToGray);
        Cv2.CvtColor(matDst2, matDst2, ColorConversion.BgrToGray);

        //与传统的哈希算法不同:分别依次计算图像每行像素点的平均值,记录每行像素点的平均值。每一个平均值对应着一行的特征。
        //方差是在概率论和统计方差衡量随机变量或一组数据时离散程度的度量。概率论中方差用来度量随机变量和其数学期望(即均值)之间的偏离程度
        int iAvg1 = 0, iAvg2 = 0;
        int[] arr1=new int[size2], arr2=new int[size2];
        for (int i = 0; i < 8; i++)
        {
            char* data1 =(char*) matDst1.Ptr(i);
            char* data2 = (char*)matDst2.Ptr(i);
            int tmp = i * 8;

            for (int j = 0; j < 8; j++)
            {
                int tmp1 = tmp + j;

                arr1[tmp1] =   data1[j] / 4 * 4;
                arr2[tmp1] = data2[j] / 4 * 4;

                iAvg1 += arr1[tmp1];
                iAvg2 += arr2[tmp1];
            }
        }

        iAvg1 /= size2;
        iAvg2 /= size2;

        for (int i = 0; i < size2; i++)
        {
            arr1[i] = (arr1[i] >= iAvg1) ? 1 : 0;
            arr2[i] = (arr2[i] >= iAvg2) ? 1 : 0;
        }
        Compare(arr1, arr2);
    }

    void Compare(int[] arr1, int[] arr2)
    {
        char[] resStr = new char[size2];
        int iDiffNum = 0;

        for (int i = 0; i < size2; i++)
        {
            if (arr1[i] != arr2[i])
            {
                resStr[i] = '0';
            }
            else
            {
                ++iDiffNum;
                resStr[i] = '1';
            }
        }

        StringBuilder strBuild = new StringBuilder();
        strBuild.Append(resStr);

        //模糊逻辑
        Debug.Log("iDiffNum = " + iDiffNum);
        double value =(double)iDiffNum/ (double)size2;
        Debug.LogWarning(strBuild + "   " + value*100+ "%");


        //if (iDiffNum <= 5)
        //    Debug.Log(" two images are very similar!");
        //else if((iDiffNum <= 10)&& iDiffNum>5)
        //    Debug.Log("two image are somewhat similar!");
        //else if (iDiffNum > 10)
        //    Debug.Log("they are two different images!");
    }

二、直方图颜色法;

http://www.ruanyifeng.com/blog/2013/03/similar_image_search_part_ii.html

根据颜色生成直方图,如果颜色很接近,就说明是同一张图。

有点: 计算量适中。比较适合描述难以自动分割的图像。

缺点:直方图反应的是图像灰度值/ 颜色值得概率分布,并没有图像的空间位置信息在里面,因此,常常出现误判;从信息论来讲,通过直方图转换,信息丢失量较大,因此单一的通过直方图进行匹配显得有点力不从心。

三、图像模板匹配   

如果源图像与模板图像尺寸不一样,通常需要进行滑动模板去匹配窗口,扫面个整幅图像获得最好的匹配patch。

在OpenCV中对应的函数为:matchTemplate():函数功能是在输入图像中滑动窗口寻找各个位置与模板图像patch的相似度。一般适合在同一张图像上抠出来的部分与这个图像匹配。

下图:

<1. 模板必须 宽和高都小于源图像,模板缩小后会对图像压缩,框出来的图像就是压缩后的框的位置,如果图片有压缩了,则会框错位置.  绿色框是把的尺寸是使用模板的大小在输入源图中框出来。所以要把模板的尺寸设置的刚刚好。

<2.所以模板必须是单个图像,否则没法区分。左侧是模板的图片,里面有两个图形。右侧是输入源图。

<3. 绿色框是把的尺寸是使用模板的大小在输入源图中框出来。所以要把模板的尺寸设置的刚刚好,然后在源图中框出来。

    //模板必须不大于源映像,模板缩小后会对图像压缩,框出来的图像就是压缩后的框的位置,如果图片有压缩了,则会框错位置。

 void setUpTemplate()
    {
        Mat img, templ, result=new Mat();
        //img = imread("nba.jpg");
        //templ = imread("76.png");


        img = Cv2.ImRead(Application.dataPath + "/Resources/999.png");
        templ = Cv2.ImRead(Application.dataPath + "/Resources/san_2.png");

        int result_cols = img.Cols - templ.Cols + 1;
        int result_rows = img.Rows - templ.Rows + 1;
        result.Create(result_cols, result_rows, MatType.CV_32FC1);
        Cv2.MatchTemplate(img, templ, result, MatchTemplateMethod.SqDiffNormed);
        //matchTemplate(img, templ, result, CV_TM_SQDIFF_NORMED);//这里我们使用的匹配算法是标准平方差匹配 method=CV_TM_SQDIFF_NORMED,数值越小匹配度越好   匹配结果存储在result单通道矩阵中
        Cv2.Normalize(result, result, 0, 1,NormType.MinMax, -1,new Mat());
            //normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());

        double minVal = -1;
        double maxVal;
        Point minLoc;
        Point maxLoc;
        CvPoint matchLoc;
        Debug.Log ( "匹配度:" +minVal +"   ");
        Cv2.MinMaxLoc(result,out minVal, out maxVal, out minLoc, out maxLoc,new  Mat());
        //minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());


        Debug.Log("匹配度:" + minVal);

        matchLoc = minLoc;
        CvScalar color = new CvScalar(0, 255, 0);
        //(CvArr img, CvPoint pt1, CvPoint pt2, CvScalar color, int thickness, LineType lineType, int shift);
        CvPoint cvPoint = Cv.Point(matchLoc.X + templ.Cols, matchLoc.Y + templ.Rows);
        LineType LINE = LineType.Link8;
        Cv2.Rectangle(img, matchLoc, cvPoint, color, 2 ,LineType.Link8, 0);

        //Cv.Rectangle(img, matchLoc, cvPoint, color, 2, LineType.Link8,0);
        //rectangle (img, matchLoc, Point(matchLoc.x + templ.cols, matchLoc.y + templ.rows), Scalar(0, 255, 0), 2, 8, 0);

        Cv2.ImShow("img", img);
        Cv2.WaitKey(0);
    }

检测动物:

模板法:

把犀牛检测成了河马:

特征法:根据独有的特征检测。可以检测出

//unityforopencv   
 public RawImage image1;
        public RawImage image2;
        public RawImage image3;
        void Start ()
        {
            //from mat1 match to mat2
            Texture2D imgTexture = Resources.Load ("Animator3") as Texture2D;
            
            Mat img1Mat = new Mat (imgTexture.height, imgTexture.width, CvType.CV_8UC3);
            Utils.texture2DToMat (imgTexture, img1Mat);
          

            Texture2D imgTexture2 = Resources.Load("Animator") as Texture2D;
            Mat img2Mat = new Mat (imgTexture2.height, imgTexture2.width, CvType.CV_8UC3);
            Utils.texture2DToMat (imgTexture2, img2Mat);
         

            //float angle = UnityEngine.Random.Range (0, 360), scale = 1.0f;
            //angle = 0;
            //Point center = new Point (img2Mat.cols () * 0.5f, img2Mat.rows () * 0.5f);
            //Mat affine_matrix = Imgproc.getRotationMatrix2D (center, angle, scale);
            //Imgproc.warpAffine (img1Mat, img2Mat, affine_matrix, img2Mat.size ());


            ORB detector = ORB.create ();
            ORB extractor = ORB.create ();

            MatOfKeyPoint keypoints1 = new MatOfKeyPoint ();
            Mat descriptors1 = new Mat ();
            detector.detect (img1Mat, keypoints1);
            extractor.compute (img1Mat, keypoints1, descriptors1);
            Texture2D texture1 = new Texture2D(img1Mat.cols(), img1Mat.rows(), TextureFormat.RGBA32, false);
            Utils.matToTexture2D(img1Mat, texture1);
            //image1.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, (float)img1Mat.width());
            //image1.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, (float)img1Mat.height());
            image1.texture = texture1;

            MatOfKeyPoint keypoints2 = new MatOfKeyPoint ();
            Mat descriptors2 = new Mat ();
            detector.detect (img2Mat, keypoints2);
            extractor.compute (img2Mat, keypoints2, descriptors2);
            Texture2D texture2 = new Texture2D(img2Mat.cols(), img2Mat.rows(), TextureFormat.RGBA32, false);
            Utils.matToTexture2D(img2Mat, texture2);
            image2.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, (float)img2Mat.width());
            image2.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, (float)img2Mat.height());
            image2.texture = texture2;



            DescriptorMatcher matcher = DescriptorMatcher.create (DescriptorMatcher.BRUTEFORCE_HAMMINGLUT);//BRUTEFORCE_HAMMINGLUT
            MatOfDMatch matches = new MatOfDMatch ();
            matcher.match (descriptors1, descriptors2, matches);

            
            Mat resultImg = new Mat ();
            Features2d.drawMatches (img1Mat, keypoints1, img2Mat, keypoints2, matches, resultImg);
            Texture2D texture = new Texture2D (resultImg.cols (), resultImg.rows (), TextureFormat.RGBA32, false);
            Utils.matToTexture2D (resultImg, texture);

            image2.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, (float)img2Mat.width());
            image2.GetComponent<RectTransform>().SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, (float)img2Mat.height());
            image2.texture = texture2;

            gameObject.GetComponent<Renderer> ().material.mainTexture = texture;
        }

https://www.youtube.com/watch?v=bUrjFGMfwas

总结:hash和模板匹配都只对自身截图的匹配比较好,对自己手动画的匹配效果不好。

 

 

网友提出的一种新方法:

二值化 --->找出角点-->旋转--->寻找特征点

一、角点(corner)

        https://blog.csdn.net/zhu_hongji/article/details/81235643
       角点通常被定义为两条边的交点,或者说,角点的局部邻域应该具有两个不同区域的不同方向的边界。角点检测(Corner Detection)是计算机视觉系统中获取图像特征的一种方法,广泛应用于运动检测、图像匹配、视频跟踪、三维重建和目标识别等,也可称为特征点检测。

1、角点的类型

  使用一个固定窗口在图像上进行任意方向上的滑动,比较滑动前与滑动后两种情况,窗口中的像素灰度变化程度,如果存在任意方向上的滑动,都有着较大灰度变化,那么我们可以认为该窗口中存在角点。

  目前,角点检测算法还不是十分完善,许多算法需要依赖大量的训练集和冗余数据来防止和减少错误的特征的出现。对于角点检测算法的重要评价标准是:其对多幅图像中相同或者相似特征的检测能力,并且能够应对光照变化、或者图像旋转等影响。

  • 一阶导数(即灰度的梯度)的局部最大所对应的像素点;
  • 两条及两条以上边缘的交点;
  • 图像中梯度值和梯度方向的变化速率都很高的点; 
  • 角点处的一阶导数最大,二阶导数为零,指示物体边缘变化不连续的方向。   
  • 三类角点检测算法:

    基于二值图像的角点检测;
    基于轮廓曲线的角点检测;
    基于灰度图像的角点检测:基于梯度、基于模板和基于模板和梯度组合三类方法;常见的基于模板的角点检测算法有:Kitchen-Rosenfeld角点检测算法,Harris角点检测算法,KLT角点检测算法及SUSAN角点检测算法。基于模板的方法主要是考虑像素领域点灰度的变化,即亮度的变化。

    1、Harris角点检测 
    1.1 原理分析

    1.1.1 算法思想

            角点原理来源于人对角点的感性判断,即图像在各个方向灰度有明显变化。算法的核心是利用局部窗口在图像上进行移动判断灰度发生较大的变化,所以此窗口用于计算图像的灰度变化为(3*3的窗口,这和计算梯度变化算子一样):[-1,0,1;-1,0,1;-1,0,1][-1,-1,-1;0,0,0;1,1,1]。根据下面三幅图可以清晰理解角点检测的过程:当一个窗口在图像上移动,如图(a),窗口在各个方向上都没有变化,则认为窗口区域为平滑区域。如图(b),窗口在某个方向上没有变化,另一个方向上有明显变化,那么,这块区域可能存在边缘。如图(c),窗口在各个方向上灰度发生了较大的变化,那么,这块区域可能存在角点。Harris角点检测正是利用了这个直观的物理现象,通过窗口在各个方向上的变化程度,决定是否为角点。
     

      

1.1.2 数学表示

根据算法思想,构建数学模型,计算移动窗口的的灰度差值。

        

OpenCV里面的API:

C++:void cornerHarris( InputArray src, //输入8bit单通道灰度Mat矩阵
                  OutputArray dst, //用于保存Harris角点检测结果,32位单通道,大小与src相同
                  int blockSize,   //滑块窗口的尺寸
                  int ksize,       //Sobel边缘检测滤波器大小
                  double k,        //Harris中间参数,经验值0.04~0.06
                  int borderType=BORDER_DEFAULT  //插值类型

二、Shi-Tomasi角点检测
        Shi-Tomasi 算法是Harris 算法的改进。Harris 算法最原始的定义是将矩阵 M 的行列式值与 M 的迹相减,再将差值同预先给定的阈值进行比较。后来Shi 和Tomasi 提出改进的方法,若两个特征值中较小的一个大于最小阈值,则会得到强角点。
Shi 和Tomasi 的方法比较充分,并且在很多情况下可以得到比使用Harris 算法更好的结果。

OpenCV里面的API:

C++:void goodFeaturesToTrack( InputArray image, OutputArray corners,
                                     int maxCorners, double qualityLevel, double minDistance,
                                     InputArray mask=noArray(), int blockSize=3,

参数详解:

第一个参数image:8位或32位单通道灰度图像;

第二个参数corners:位置点向量,保存的是检测到的角点的坐标;

第三个参数maxCorners:定义可以检测到的角点的数量的最大值;

第四个参数qualityLevel:检测到的角点的质量等级,角点特征值小于qualityLevel*最大特征值的点将被舍弃;

第五个参数minDistance:两个角点间最小间距,以像素为单位;

第六个参数mask:指定检测区域,若检测整幅图像,mask置为空Mat();

第七个参数blockSize:计算协方差矩阵时窗口大小;

第八个参数useHarrisDetector:是否使用Harris角点检测,为false,则使用Shi-Tomasi算子;

第九个参数k:留给Harris角点检测算子用的中间参数,一般取经验值0.04~0.06。第八个参数为false时,该参数不起作用;
 

三、亚像素级角点检测
       当我们想要进行几何测量或者标定的时候势必要比目标识别需要更高的精度的特征点。而上面的goodFeaturesToTrack()只能得到整数的坐标值,这时候我们就需要亚像素级的角点检测来得到实数坐标值来满足精度需求。

       亚像素级角点检测的位置摄像机标定,跟踪并重建摄像机的轨迹或者重建被跟踪目标的三维结构时,是一个基本的测量值。下面是将角点位置精确到亚像素精度的过程:

                                     

        一个向量与其正交的向量的点积为0,角点满足上图所示情况。其中(a)点p附近的图像是均匀的,其梯度为0;(b)边缘的梯度与沿边缘方向的q-p向量正交。在图中两种情况下,p点梯度与q-p向量的点积均为0。

      上图中,我们假设起始角点q在实际亚像素角点的附近。检测所有的q-p向量。若点p位于一个均匀区域,则点p的梯度为0。若q-p向量的方向与边缘的方向一致,则此边缘上p点处的梯度与q-p向量正交,在这两种情况下,p点处的梯度与q-p向量的点积为0.我们可以在p点周围找到很多组梯度以及相关的向量q-p,令其点集为0,然后可以通过求解方程组,方程组的解即为角点q的亚像素级精度的位置,即精确的角点位置。
 

 

Unity for Opencv

  void Start ()
        {
            Texture2D imgTexture = Resources.Load ("lena") as Texture2D;
            
            Mat img1Mat = new Mat (imgTexture.height, imgTexture.width, CvType.CV_8UC3);
            Utils.texture2DToMat (imgTexture, img1Mat);
            Debug.Log ("img1Mat.ToString() " + img1Mat.ToString ());

            Texture2D imgTexture2 = Resources.Load("lena") as Texture2D;
            Mat img2Mat = new Mat (imgTexture2.height, imgTexture2.width, CvType.CV_8UC3);
            Utils.texture2DToMat (imgTexture2, img2Mat);
            Debug.Log ("img2Mat.ToString() " + img2Mat.ToString ());


            float angle = UnityEngine.Random.Range (0, 360), scale = 1.0f;

            Point center = new Point (img2Mat.cols () * 0.5f, img2Mat.rows () * 0.5f);

            Mat affine_matrix = Imgproc.getRotationMatrix2D (center, angle, scale);

            Imgproc.warpAffine (img1Mat, img2Mat, affine_matrix, img2Mat.size ());


            ORB detector = ORB.create ();
            ORB extractor = ORB.create ();

            MatOfKeyPoint keypoints1 = new MatOfKeyPoint ();
            Mat descriptors1 = new Mat ();

            detector.detect (img1Mat, keypoints1);
            extractor.compute (img1Mat, keypoints1, descriptors1);

            MatOfKeyPoint keypoints2 = new MatOfKeyPoint ();
            Mat descriptors2 = new Mat ();
        
            detector.detect (img2Mat, keypoints2);
            extractor.compute (img2Mat, keypoints2, descriptors2);


            DescriptorMatcher matcher = DescriptorMatcher.create (DescriptorMatcher.BRUTEFORCE_HAMMINGLUT);
            MatOfDMatch matches = new MatOfDMatch ();

            matcher.match (descriptors1, descriptors2, matches);


            Mat resultImg = new Mat ();

            Features2d.drawMatches (img1Mat, keypoints1, img2Mat, keypoints2, matches, resultImg);


            Texture2D texture = new Texture2D (resultImg.cols (), resultImg.rows (), TextureFormat.RGBA32, false);
        
            Utils.matToTexture2D (resultImg, texture);

            gameObject.GetComponent<Renderer> ().material.mainTexture = texture;
        }
    

如果没有明显且独立的特征点,则没法匹配。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Debug_horizon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值