SURF特征提取与匹配实践

上回说到,最近看了点算法,也实践了FAST关键点的提取,近来事多了些,所以今天才又有点成果。

依然是C#实现,在Unity3d里面做的,先上J,哦不,上图。。。

这是两张素材,第二个是用PS透视变换了一下,进行匹配的就是它们俩(艹泥马它们俩明明是一只嘛。。。)


下面这个图是代码生成的,先用上文的FAST的代码生成了关键点图,然后合并了两图,最后用匹配得到的pair(点的映射对)在图上画了线,目测大部分是匹配上了的。

当然,也有可能我这野路子哪里没理解到位。。。然而算法有点变异不是大问题,主要是还要优化,现在出结果图要4秒,根本不能用于实时应用,当然,这里面包含了生成三张结果图和画线的时间,这一块也比较占时间的,


听人说现在代码是求人看也没人看,所以我最后上点代码,原理就是提取时算关键点周围的梯度(好像不是梯度,我是算的梯度),匹配时计算向量距离与阈值比较

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

/// <summary>
/// 加速鲁棒_特征
/// </summary>
public class SURF : ADiscript<Color> {

    float Threshold = 0.6f;

    /// <summary>
    /// 关键点的4个8x8块描述符,块内梯度归到8方向
    /// </summary>
    public float[] LT_TR_LB_BR = new float[32];

    public override void Init(params object[] p) {
        //
    }
    public override IList<CVTools.IntV2> GetKeyPoint(Texture2D src) {
        //未实现SURF关键点
        return null;
    }
    //得到所有关键点的特征描述符
    public override ADiscript<Color>[] GetDiscriptor(IList<CVTools.IntV2> keyPoint, Color[] mat, int w, int h) {
        SURF[] allDis = new SURF[keyPoint.Count];
        for (int i = 0; i < keyPoint.Count;i++ ) {
            allDis[i] = GetDiscript(keyPoint[i], mat, w, h) as SURF;
        }
        return allDis;
    }
    /// <summary>
    /// 计算一个点的特征描述
    /// </summary>
    public override ADiscript<Color> GetDiscript(CVTools.IntV2 keyPoint, Color[] mat, int w, int h) {
        CVTools.IntV2[] LT = CVTools.GetNP(keyPoint, 8, 8, -5, -5);

        CVTools.IntV2[] TR = CVTools.GetNP(keyPoint, 8, 8, 5, -5);

        CVTools.IntV2[] RB = CVTools.GetNP(keyPoint, 8, 8, 5, 5);

        CVTools.IntV2[] BL = CVTools.GetNP(keyPoint, 8, 8, -5, 5);

        CVTools.IntV2[][] enumBlock = new CVTools.IntV2[][] { LT, TR, RB, BL };
        SURF pDis = new SURF();
        pDis.x = keyPoint.x;
        pDis.y = keyPoint.y;
        for (int i = 0; i < enumBlock.Length;i++ ) {
            CVTools.IntV2[] curBlock = enumBlock[i];
            for (int j = 0; j < curBlock.Length;j++ ) {
                CVTools.Grad8Dir d8 = CVTools.NearGrad(CVTools.GetGrad(mat,w,h,curBlock[j]));
                pDis.LT_TR_LB_BR[i * 8 + d8.dir] += d8.len;
            }
            CVTools.NormalizationRotateArray(pDis.LT_TR_LB_BR, i * 8, (i + 1) * 8);
        }
        return pDis;
    }
    public override IList<CVTools.Pair<AKeyPoint>> Match(IList<AKeyPoint> kp1, IList<AKeyPoint> kp2) {
        List<CVTools.Pair<AKeyPoint>> pPair = new List<CVTools.Pair<AKeyPoint>>();
        for (int i = 0; i < kp1.Count;i++ ) {
            for (int j = 0; j < kp2.Count;j++ ) {
                if (CVTools.VectorDis((kp1[i] as SURF).LT_TR_LB_BR, (kp2[j] as SURF).LT_TR_LB_BR) < Threshold) {
                    pPair.Add(new CVTools.Pair<AKeyPoint>() { P1 = kp1[i], P2 = kp2[j] });
                    break;
                }
            }
        }
        return pPair;
    }
}
一些工具函数:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// 图像处理工具
/// </summary>
public class CVTools {
    //对
    public class Pair<T>{
        public T P1;
        public T P2;
        public override string ToString() {
            return P1.ToString()+","+P2.ToString();
        }
    }
    /// <summary>
    /// Int型Vector2
    /// </summary>
    public class IntV2 {
        public int x, y;
        public IntV2() { }
        public IntV2(int x,int y) {
            this.x = x;
            this.y = y;
        }
        public IntV2(float x, float y) {
            this.x = (int)x;
            this.y = (int)y;
        }
        public static IntV2 operator +(IntV2 v1, IntV2 v2) {
            return new IntV2(v1.x+v2.x,v1.y+v2.y); ;
        }
        public static IntV2 operator -(IntV2 v1, IntV2 v2) {
            return new IntV2(v1.x - v2.x, v1.y - v2.y); ;
        }
        public static implicit operator IntV2(Vector2 v) {
            return new IntV2((int)(v.x),(int)(v.y));
        }
        public override string ToString() {
            return x + "," + y;
        }
    }
    /// <summary>
    /// 8方向近似梯度,从正右方向0开始,顺时针计算
    /// </summary>
    public class Grad8Dir{
        public int dir;
        public float len;
    }
    /// <summary>
    /// 求对称点
    /// </summary>
    public static IntV2 ReflectByPoint(IntV2 src, IntV2 cent) {
        int x = cent.x * 2 - src.x;
        int y = cent.y * 2 - src.y;
        return new IntV2(x, y);
    }
    //数组越界处理方式
    public enum GetCellType{
        Wrap = 0,
        Repeat = 1,
        Bg = 2,
    }
    /// <summary>
    /// 取数组单元,有越界处理
    /// </summary>
    public static T GetCell<T>(T[] scr, int w, int h, IntV2 pos, GetCellType type, T Bg) {
        int x = pos.x, y = pos.y;
        if ((int)pos.x < 0 || (int)pos.x >= w) { //x is out of range
            if (type == GetCellType.Bg)
                return Bg;
            else if (type == GetCellType.Repeat) {
                x = Mathf.Abs(pos.x) % w;
            } else {
                x = Mathf.Clamp(0, pos.x, w - 1);
            }
        }
        if (pos.y < 0 || pos.y >= h) { //y is out of range
            if (type == GetCellType.Bg)
                return Bg;
            else if (type == GetCellType.Repeat) {
                y = Mathf.Abs(pos.y) % h;
            } else {
                y = Mathf.Clamp(0, pos.y, h - 1);
            }
        }
        return scr[w * y + x];
    }
    /// <summary>
    /// 生成关键点的图像
    /// </summary>
    public static Texture2D ShowKeyPointTex(IList<CVTools.IntV2> kps, int w, int h, Color kpCol, Color bgCol) {
        Texture2D tex = new Texture2D(w,h);
        for (int j = 0; j < h;j++ ) {
            for(int i = 0;i<w;i++){
                tex.SetPixel(i, j, bgCol);
            }
        }
        for (int i = 0; i < kps.Count;i++ ) {
            tex.SetPixel(kps[i].x,kps[i].y,kpCol);
        }
        tex.Apply();
        return tex;
    }
    /// <summary>
    /// 获取某像素点在图中梯度 3x3
    /// </summary>
    public static Vector2 GetGrad(Color[] src,int w,int h,IntV2 point) {
        var NP_33 = GetNP(point, 3, 3);
        float gx = 
            GetCell<Color>(src, w, h, NP_33[2], GetCellType.Repeat, Color.black).grayscale+
            GetCell<Color>(src, w, h, NP_33[5], GetCellType.Repeat, Color.black).grayscale +
            GetCell<Color>(src, w, h, NP_33[8], GetCellType.Repeat, Color.black).grayscale -
            GetCell<Color>(src, w, h, NP_33[0], GetCellType.Repeat, Color.black).grayscale- 
            GetCell<Color>(src, w, h, NP_33[3], GetCellType.Repeat, Color.black).grayscale-
            GetCell<Color>(src, w, h, NP_33[6], GetCellType.Repeat, Color.black).grayscale;
        float gy =
            GetCell<Color>(src, w, h, NP_33[8], GetCellType.Repeat, Color.black).grayscale +
            GetCell<Color>(src, w, h, NP_33[7], GetCellType.Repeat, Color.black).grayscale +
            GetCell<Color>(src, w, h, NP_33[6], GetCellType.Repeat, Color.black).grayscale -
            GetCell<Color>(src, w, h, NP_33[3], GetCellType.Repeat, Color.black).grayscale -
            GetCell<Color>(src, w, h, NP_33[2], GetCellType.Repeat, Color.black).grayscale -
            GetCell<Color>(src, w, h, NP_33[1], GetCellType.Repeat, Color.black).grayscale;
        return new Vector2(gx,gy);
    }
    /// <summary>
    /// 获取整型的梯度
    /// </summary>
    public static IntV2 GetGradInt(Color[] src, int w, int h, IntV2 point) {
        Vector2 fv2 = GetGrad(src,w,h,point);
        return new IntV2((int)fv2.x,(int)fv2.y);
    }
    /// <summary>
    /// 近似梯度值到8方向梯度
    /// </summary>
    public static Grad8Dir NearGrad(Vector2 grad) {
        Grad8Dir g = new Grad8Dir();
        float len = Vector2.SqrMagnitude(grad);
        g.len = len;
        float xyDis = grad.x/(float)grad.y;
        float aDis = Mathf.Abs(xyDis);
        bool xIsPlus;
        bool yIsPlus;
        if(grad.x>=0)
            xIsPlus = true;
        else
            xIsPlus = false;
        if(grad.y>=0)
            yIsPlus = true;
        else
            yIsPlus = false;
        //if (aDis <0.4 || aDis>2.4)
        if (aDis <0.4) {
            if (xIsPlus)
                g.dir = 0;
            else
                g.dir = 4;
        }else if(aDis>2.4){
            if (yIsPlus)
                g.dir = 6;
            else
                g.dir = 2;
        } else if (xIsPlus) {
            if (yIsPlus)
                g.dir = 7;
            else
                g.dir = 1;
        } else {
            if (yIsPlus)
                g.dir = 5;
            else
                g.dir = 3;
        }
        return g;
    }
    /// <summary>
    /// 获取邻近坐标矩阵
    /// </summary>
    public static IntV2[] GetNP(IntV2 point,int rx,int ry) { 
        IntV2[] allPoint = new IntV2[rx*ry];
        int x = point.x;
        int y = point.y;
        int offX = rx / 2;
        int offY = ry / 2;
        for (int h = 0; h < ry;h++ ) {
            for (int w = 0; w < rx;w++ ) {
                allPoint[h * ry + w] = new IntV2(x+w-offX,y+h-offY);
            }
        }
        return allPoint;
    }
    /// <summary>
    /// 获取邻近坐标矩阵(带偏移版)
    /// </summary>
    public static IntV2[] GetNP(IntV2 point,int rx,int ry,int offsetX,int offsetY) {
        IntV2 trueCent = point + new IntV2(offsetX, offsetY);
        return GetNP(trueCent,rx,ry);
    }
    /// <summary>
    /// 归一化N维向量,提供一个float数组,指定区间,将区间内归一化并返回主方向下标
    /// </summary>
    public static int NormalizationArray(float[] array,int start,int end) {
        float max = float.MinValue;
        float min = float.MaxValue;
        int index = start;
        float sum = 0;
        for (int i = start; i < end;i++ ) {
            if (max < array[i]){
                index = i;
            }if(min>array[i]){
                min = array[i];
            }
            sum += array[i];
        }
        for (int i = start; i < end; i++ ) {
            array[i] = (array[i] - min) / sum;
        }
        return index;
    }
    /// <summary>
    /// 归一化N维向量并循环位置使主方向置于首位置,需提供一个float数组,指定区间
    /// </summary>
    public static void NormalizationRotateArray(float[] array, int start, int end) {
        float max = float.MinValue;
        float min = float.MaxValue;
        int index = start;
        float sum = 0;
        for (int i = start; i < end; i++) {
            if (max < array[i]) {
                index = i;
            } if (min > array[i]) {
                min = array[i];
            }
            sum += array[i];
        }
        for (int i = start; i < end; i++) {
            array[i] = (array[i] - min) / sum;
        }
        LeftShiftArray<float>(array,start,end, index);
    }
    /// <summary>
    /// 循环左移一个数组m位(右移以左移值的补来实现)
    /// </summary>
    public static void LeftShiftArray<T>(T[] array,int start,int end,int m) {
        int len = end - start;
        m = m % len;
        ReverseArray<T>(array, start, end - m);
        ReverseArray<T>(array, end - m, end);
        ReverseArray<T>(array,start,end);
    }
    /// <summary>
    /// 反转数组指定区间
    /// </summary>
    public static void ReverseArray<T>(T[] array, int start, int end) {
        T tmp;
        end--;
        for (; start < end;start++,end-- ) {
            tmp = array[end];
            array[end] = array[start];
            array[start] = tmp;
        }
    }
    /// <summary>
    /// 计算两个向量的距离
    /// </summary>
    public static float VectorDis(float[] v1,float[] v2) {
        if (v1.Length != v2.Length)
            return float.MaxValue;
        float t = 0;
        for (int i = 0; i < v1.Length;i++ ) {
            t += Mathf.Pow((v1[i] - v2[i]), 2);
        }
        return Mathf.Sqrt(t);
    }
    /// <summary>
    /// 生成匹配后的映射图
    /// </summary>
    public static Texture2D ShowDisMath(IList<CVTools.Pair<AKeyPoint>> matchedPoints,Texture2D t1,Texture2D t2,Color col) {
        int w = t1.width + t2.width;
        int h = t1.height >= t2.height ? t1.height : t2.height;
        Texture2D showTex = new Texture2D(w,h);
        Rect[] uv = showTex.PackTextures(new Texture2D[] { t1, t2 }, 0);
        IntV2 t1Pos = new IntV2(uv[0].x*w, uv[0].y*h);
        IntV2 t2Pos = new IntV2(uv[1].x*w, uv[1].y*h);
        LogVar.inst.ShowArray<IList<CVTools.Pair<AKeyPoint>>>(matchedPoints);
        Debug.Log("匹配对长度:" + matchedPoints.Count + ",p1起始:" + t1Pos + ",p2起始:" + t2Pos);
        for (int i = 0; i < matchedPoints.Count;i++ ) {
            IntV2 p1, p2;
            p1 = matchedPoints[i].P1 + t1Pos;
            p2 = matchedPoints[i].P2 + t2Pos;
            showTex.SetPixel(p1.x, p1.y, Color.yellow);
            showTex.SetPixel(p2.x, p2.y, Color.yellow);
            DrawLineOnTex(showTex,p1,p2,col);
        }
        showTex.Apply();
        return showTex;
    }
    /// <summary>
    /// 在纹理上画直线
    /// </summary>
    public static void DrawLineOnTex(Texture2D tex,IntV2 start,IntV2 end,Color col) {
        float deltaY = (end.y - start.y) / (float)(end.x - start.x);
        int len = end.x - start.x;
        for (int i = 0; i <= len; i ++) {
            int y = (int)(i* deltaY+start.y);
            var initCol = tex.GetPixel(i + start.x, y);
            if(initCol == Color.black)
                tex.SetPixel(i+start.x, y, col);
        }
    }
}
一个debug工具,用于输出大量数据(需要实现了IEnumerable接口,也就是一般数组链表啥的)不卡顿:

using UnityEngine;
using System.Collections;
//密集计算型数据debug
public class LogVar : MonoBehaviour {

    public static LogVar inst;
    void Awake() {
        inst = this;
    }

    public void ShowArray<T>(T array) where T : IEnumerable {
        StartCoroutine(showArray<T>(array));
    }
    IEnumerator showArray<T>(T array) where T : IEnumerable {
        if (array != null) {
            foreach(var i in array){
                yield return new WaitForEndOfFrame();
                Debug.Log(i.ToString());
            }
        }
    }
}

嗯,慢慢来吧,这里优化好了下一步就是计算映射矩阵了



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值