图像算法面试

来源:互联网 发布:ipad淘宝卖家 编辑:程序博客网 时间:2024/05/21 21:50

图像算法面试

笔试

​ 昨天在拉勾上投了一份简历,马上就收到了hr的电话,问了基本情况,符合基本要求之后就给我发过来了一份笔试试题。要求第二天中午12点之前提交到邮箱。于是晚上很认真地做完了笔试题,提交,不出所料地收到面试邀请。
​ 笔试题如下,都是比较基础的数据结构的题。

这里写图片描述

下面是我的答题代码:

#include <iostream>using namespace std;#define  NUM 19#define MAX_PATH 32767struct Node{    int key;    int flag;    Node()    {        flag=0;    }};class Graph{public:    //stack<int> searchStack;    int resultPath[MAX_PATH][NUM];    int result[NUM+1];//将result设为NUM+1,主要是为了避免发生B->D->B的事情    Node headNode;    Node endNode;    int pathNum;    int nPos;    bool Mark[NUM];public:    Graph()    {        for (int i=0;i<NUM;i++)        {            for (int j=0;j<MAX_PATH;j++)            {                resultPath[i][j]=0;            }            result[i]=0;            Mark[i]=false;        }        result[NUM]=0;        pathNum=0;        nPos=0;    }    /*-----------------------------------------------------------------------     1、将多叉树转换成无向图     2、通过设置 访问是否的变量来避免回路/    -----------------------------------------------------------------------*/    void FindAllPath(int Matrix[NUM][NUM],Node startNodeKey,Node endNodeKey)    {        result[nPos]=startNodeKey.key;  //将当前元素放入结果集中        Mark[startNodeKey.key-1]=true;  //将访问标记为已访问        nPos++;  //结果集索引加1        while(nPos!=0)        {            int tempVal=result[nPos-1];//获取到最前面的元素            if (tempVal==endNodeKey.key)  //若当前元素为目标节点            {                for (int j=0;j<nPos;j++)                {                    resultPath[pathNum][j]=result[j];  //将结果集复制于最后的路径矩阵中                }                nPos--;                result[nPos]=0;                pathNum++;                Mark[endNodeKey.key-1]=false;                break;            }            while(startNodeKey.flag<NUM)            {                if (Matrix[tempVal-1][startNodeKey.flag]==1)                {                    if (Mark[startNodeKey.flag]==false)                    {                        Node tempNode;                        tempNode.key=startNodeKey.flag+1;                        FindAllPath(Matrix,tempNode,endNodeKey);//深度优先遍历算法,                    }                }                startNodeKey.flag++;            }            if (startNodeKey.flag==NUM)//访问结束,            {                nPos--;                startNodeKey.flag=0;                result[nPos]=0;                Mark[startNodeKey.key-1]=false;                break;            }        }    }    //*测试功能函数    void test(char start,char end)    {        //对应无向图的矩阵        int Matrix[NUM][NUM]={            {0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},  //A            {1,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0},  //B            {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},  //C            {1,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0},  //D            {0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},  //E            {0,1,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0},  //F            {0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},  //G            {0,0,0,1,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0},  //H            {0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},  //I            {0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},  //J            {0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0},  //K            {0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0},  //L            {0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,1,1},  //N            {0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0},  //O            {0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0},  //P            {0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0},  //Q            {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0},  //R            {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0},  //S            {0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0}   //T        };        headNode.flag=1;        headNode.key = (int)(start - 64);//输入字符ASCII码转换为数字   //-----------题目中节点没有M,所以ASCII码比'M'大的直接+1        if(end > 77)            end--;        endNode.key = (int)(end - 64);        FindAllPath(Matrix,headNode,endNode);        for(int j=0;j<NUM;j++)        {            if (resultPath[0][j]==0)            {                break;            }            char temp = (char)resultPath[0][j];    //-----------题目中节点没有M,所以ASCII码比'M'大的直接+1            if(temp >= 13)                temp++;            printf("%c   ",temp+64);        }        printf("\n");    }};int main(){    Graph graph;    graph.test('G','R');}

这里写图片描述
以下是我的答题代码

#include <iostream>#include <stdlib.h>#include <unistd.h>#include <time.h>using namespace std;class List {public:    List(){create_List();}    ~List(){clear();}    //创建头结点    void create_List();    //插入函数    void insert(const int& d);    //打印    void print();    //创建原始的50个item的链表    void creat50Item();    //每秒钟新增    void IN();    //每秒钟淘汰    void OUT();    //每秒钟age自增    void agePlus();    int id = 0;private:    //节点结构    struct Item{        int age;        int id;        Item * next;        Item(const int& d,const int& i):age(d),id(i),next(NULL){}    };    Item * head;//头节点    //清理链表函数    void clear(){        Item * p = head;        //从头节点开始循环删除        while(p){            Item * q = p->next;            delete p;            p = q;        }    }    //查找数据d的上一个节点位置的函数    //为了方便后面删除操作    Item* find(const int& d){        Item * p = head;        for(;p;p=p->next){            if(p->next->age==d)                break;        }        return p;    }};//创建头结点void List::create_List(){    head = new Item(0,id);    id++;}//从头插入一个节点void List::insert(const int& d){    Item *p = new Item(d,id);    p->next = head->next;    head->next = p;}//打印函数void List::print(){    int count = 1;    for(Item * p = head->next;p;p=p->next){        cout <<"age" << p->age ;        cout << "   id" << p->id << endl;        count ++;    }    cout << "-------------------------------链表节点数:" << count - 1 << endl;}//创建50个itemvoid List::creat50Item(){    //将原始的链表中所有age随机生成0-9中的任意数字    srand((unsigned)time(NULL));    for(int i=0;i<50;i++)    {        int age = rand() % 10;        insert(age);        id++;    }}//随机位置新增itemvoid List::IN(){    srand((unsigned)time(NULL));    int pos = rand() % 50;//随机位置    int i = 0;    Item * p = NULL;    for(p = head->next;p;p=p->next)    {        if(i >= pos)            break;        i++;    }    Item * q = new Item(0,id);    q->next = p->next;    p->next = q;    id++;    cout << "随机位置新增id为: " << id -1 << endl;}//按规则淘汰itemvoid List::OUT(){    bool hasNoBigAge = true;    int deleteID = 0;    int count = 1;    for(Item * p = head->next;p;p=p->next)    {        int deleteAge = p->age;        deleteID = p->id;        if(deleteAge >= 10)//淘汰age大于10的item        {            Item * a = find(deleteAge);            Item *q = a->next;            a->next = a->next->next;            delete q;            hasNoBigAge = false;            break;        }        count ++;    }    if(hasNoBigAge && count > 100)//所有的age都小于10,且cache中item数量超过了100,淘汰第一个    {        head = head->next;        deleteID = head->id;    }    cout << "被删除的item的id为: " << deleteID << endl;}//age自增void List::agePlus(){    for(Item * p = head->next;p;p=p->next)    {        p->age++;    }}int main(int argc, const char * argv[]){    List list;    //创建原始的50个item的链表    list.creat50Item();    list.print();    while(1)    {        list.agePlus();//每秒所有item的age加1        list.IN();//新增        list.print();        list.OUT();//淘汰        list.print();        sleep(1);//延时1秒    }    return 0;}

笔试题目比较基础,数据结构的一些常识性东西

面试准备

​ 跟hr聊天过程中得知,公司招聘岗位的薪资待遇很不错,是我目前工资的1.8倍,我当然有点心动,然后约好面试时间之后,我就想着准备一下面试技巧来应对这次的面试。因为我应聘的岗位是图像算法工程师,所以我着重地复习了下图像方面的一些知识点。

1、常用边缘检测有哪些算子,各有什么特性?

解:常用边缘检测算子如下所述:

  1. Sobel算子,其主要用于边缘检测,在技术上它是以离散型的差分算子,用来运算图像亮度函数的梯度的近似值, Sobel算子是典型的基于一阶导数的边缘检测算子,由于该算子中引入了类似局部平均的运算,因此对噪声具有平滑作用,能很好的消除噪声的影响。Sobel算子对于象素的位置的影响做了加权,与Prewitt算子、Roberts算子相比因此效果更好。Sobel算子包含两组3x3的矩阵,分别为横向及纵向模板,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。缺点是Sobel算子并没有将图像的主题与背景严格地区分开来,换言之就是Sobel算子并没有基于图像灰度进行处理,由于Sobel算子并没有严格地模拟人的视觉生理特征,所以提取的图像轮廓有时并不能令人满意。

  2. Isotropic Sobel算子 Sobel算子另一种形式是(Isotropic Sobel)算子,加权平均算子,权值反比于邻点与中心点的距离,当沿不同方向检测边缘时梯度幅度一致,就是通常所说的各向同性Sobel(Isotropic Sobel)算子。模板也有两个,一个是检测水平边沿的 ,另一个是检测垂直平边沿的 。各向同性Sobel算子和普通Sobel算子相比,它的位置加权系数更为准确,在检测不同方向的边沿时梯度的幅度一致。

  3. Roberts算子 罗伯茨算子、Roberts算子是一种最简单的算子,是一种利用局部差分算子寻找边缘的算子,他采用对角线方向相邻两象素之差近似梯度幅值检测边缘。检测垂直边缘的效果好于斜向边缘,定位精度高,对噪声敏感,无法抑制噪声的影响。1963年,Roberts提出了这种寻找边缘的算子。Roberts边缘算子是一个2x2的模板,采用的是对角方向相邻的两个像素之差。从图像处理的实际效果来看,边缘定位较准,对噪声敏感。适用于边缘明显且噪声较少的图像分割。Roberts边缘检测算子是一种利用局部差分算子寻找边缘的算子,Robert算子图像处理后结果边缘不是很平滑。经分析,由于Robert算子通常会在图像边缘附近的区域内产生较宽的响应,故采用上述算子检测的边缘图像常需做细化处理,边缘定位的精度不是很高。

  4. Prewitt算子 Prewitt算子是一种一阶微分算子的边缘检测,利用像素点上下、左右邻点的灰度差,在边缘处达到极值检测边缘,去掉部分伪边缘,对噪声具有平滑作用 。其原理是在图像空间利用两个方向模板与图像进行邻域卷积来完成的,这两个方向模板一个检测水平边缘,一个检测垂直边缘。经典Prewitt算子认为:凡灰度新值大于或等于阈值的像素点都是边缘点。即选择适当的阈值T,若P(i,j)≥T,则(i,j)为边缘点,P(i,j)为边缘图像。这种判定是欠合理的,会造成边缘点的误判,因为许多噪声点的灰度值也很大,而且对于幅值较小的边缘点,其边缘反而丢失了。Prewitt算子对噪声有抑制作用,抑制噪声的原理是通过像素平均,但是像素平均相当于对图像的低通滤波,所以Prewitt算子对边缘的定位不如Roberts算子。因为平均能减少或消除噪声,Prewitt梯度算子法就是先求平均,再求差分来求梯度。该算子与Sobel算子类似,只是权值有所变化,但两者实现起来功能还是有差距的,据经验得知Sobel要比Prewitt更能准确检测图像边缘

  5. Laplacian算子Laplace算子是一种各向同性算子,二阶微分算子,在只关心边缘的位置而不考虑其周围的象素灰度差值时比较合适。Laplace算子对孤立象素的响应要比对边缘或线的响应要更强烈,因此只适用于无噪声图象。存在噪声情况下,使用Laplacian算子检测边缘之前需要先进行低通滤波。所以,通常的分割算法都是把Laplacian算子和平滑算子结合起来生成一个新的模板。拉普拉斯算子也是最简单的各向同性微分算子,具有旋转不变性。一个二维图像函数的拉普拉斯变换是各向同性的二阶导数。拉式算子用来改善因扩散效应的模糊特别有效,因为它符合降制模型。扩散效应是成像过程中经常发生的现象。Laplacian算子一般不以其原始形式用于边缘检测,因为其作为一个二阶导数,Laplacian算子对噪声具有无法接受的敏感性;同时其幅值产生算边缘,这是复杂的分割不希望有的结果;最后Laplacian算子不能检测边缘的方向;所以Laplacian在分割中所起的作用包括:(1)利用它的零交叉性质进行边缘定位;(2)确定一个像素是在一条边缘暗的一面还是亮的一面;一般使用的是高斯型拉普拉斯算子(Laplacian of a Gaussian,LoG),由于二阶导数是线性运算,利用LoG卷积一幅图像与首先使用高斯型平滑函数卷积改图像,然后计算所得结果的拉普拉斯是一样的。所以在LoG公式中使用高斯函数的目的就是对图像进行平滑处理,使用Laplacian算子的目的是提供一幅用零交叉确定边缘位置的图像;图像的平滑处理减少了噪声的影响并且它的主要作用还是抵消由Laplacian算子的二阶导数引起的逐渐增加的噪声影响。

  6. Canny算子Canny算子是一个具有滤波,增强,检测的多阶段的优化算子,在进行处理前,Canny算子先利用高斯平滑滤波器来平滑图像以除去噪声,Canny分割算法采用一阶偏导的有限差分来计算梯度幅值和方向,在处理过程中,Canny算子还将经过一个非极大值抑制的过程,最后Canny算子还采用两个阈值来连接边缘。边缘提取的基本问题是解决增强边缘与抗噪能力间的矛盾,由于图像边缘和噪声在频率域中同是高频分量,简单的微分提取运算同样会增加图像中的噪声,所以一般在微分运算之前应采取适当的平滑滤波,减少噪声的影响。Canny运用严格的数学方法对此问题进行了分析,推导出由# 个指数函数线性组合形式的最佳边缘提取算子网,其算法的实质是用一个准高斯函数作平滑运算,然后以带方向的一阶微分定位导数最大值,Canny算子边缘检测是一种比较实用的边缘检测算子,具有很好的边缘检测性能。Canny边缘检测法利用高斯函数的一阶微分,它能在噪声抑制和边缘检测之间取得较好的平衡。

  7. Laplacian of Gaussian(LoG)算子 利用图像强度二阶导数的零交叉点来求边缘点的算法对噪声十分敏感,所以,希望在边缘增强前滤除噪声.为此,将高斯滤波和拉普拉斯边缘检测结合在一起,形成LoG(Laplacian of Gaussian, LoG)算法,也称之为拉普拉斯高斯算法.LoG边缘检测器的基本特征是: 平滑滤波器是高斯滤波器.增强步骤采用二阶导数(二维拉普拉斯函数).边缘检测判据是二阶导数零交叉点并对应一阶导数的较大峰值.使用线性内插方法在子像素分辨率水平上估计边缘的位置.这种方法的特点是图像首先与高斯滤波器进行卷积,这一步既平滑了图像又降低了噪声,孤立的噪声点和较小的结构组织将被滤除.由于平滑会导致边缘的延展,因此边缘检测器只考虑那些具有局部梯度最大值的点为边缘点.这一点可以用二阶导数的零交叉点来实现.拉普拉斯函数用作二维二阶导数的近似,是因为它是一种无方向算子.为了避免检测出非显著边缘,应选择一阶导数大于某一阈值的零交叉点作为边缘点.

    http://www.csdn123.com/html/blogs/20131030/91194.htm

2、叙述GABOR滤波器原理

​ garbor是一种有效的纹理检测工具 ,在二维空间中,使用一个三角函数(如正弦函数)与一个高斯函数叠加就得到了一个Gabor滤波器,三角函数乘以高斯函数

http://blog.csdn.net/xue_wenyuan/article/details/51533953

3、常用的分类器有哪些,并简述其工作原理

8FA73C06-A846-4DBA-8384-496C072AFC0B

4、图像特征提取的几种方式

HOG LBP haar

​ 1、HOG 方向梯度直方图特征,它通过计算和统计图像局部区域的梯度方向直方图来构成特征。HOG + SVM 常用来行人检测。2005 CVPR首次提出。

​ 2、LBP(Local Binary Pattern,局部二值模式)是一种用来描述图像局部纹理特征的算子;它具有旋转不变性和灰度不变性等显著的优点。

​ 3、Haar特征分为三类:边缘特征、线性特征、中心特征和对角线特征

5、简述SVM,GMM,SIFT/SURF和LDA/PCA的基本原理?

还有其他的一些面试必备的问题,我都看了
http://m.blog.csdn.net/blog/songzitea/13629389

面试

1、语言层面

​ 我的简历上写的 linux平台精通

①、 面试官:用过linux下定时启动没有? 用vim配置过菜单栏没有?如果不能熟练的使用vim请不要说自己精通linux。只能说会使用。精通linux,那么我来考考你,用正则表达式来匹配一个qq邮箱。

​ 我用过vim,之前写C的时候经常都是用vim来写,但是在现在的开发环境下,用的都是qt,完全不需要自己用vim,然后再写makefile,用qt都直接一套直接搞定了。 不知道这个算不算面试官刁钻。但是自己确实再也不敢说自己linux多么多牛逼了,只能说自己会用。关于正则表达式,自己确实用过,在匹配字符串的时候经常用到,用的时候都是现用现查的,但是现场让我写,我只是知道个大概,写不出来。

②、熟练掌握c++,那么c++里面的stl能熟练使用吗?

​ 经过上面的之后确实不能说自己能熟练掌握c++ stl了,没有可以地去一定使用stl,但是平常 vector list 迭代器还是有用到。

③、熟练使用python语言,那么python的垃圾回收机制懂吗?

​ 额,这个我确实不懂,在算法开发过程中没有使用过。到这里,我的心里已经是崩溃的状态了。

④、既然语言层面的都不懂,那就再问问算法层面的吧。用两个栈,实现一个队列的效果

​ 栈是先进后出,队列是先进先出。脑子里面已经全部是空白,特别紧张了。这么简单的东西,当时只是在纸上随便画了下。其实直接就是 A栈 和B栈,一个数据先放在A栈中 直接就弹出存到B栈,A栈只是一个过渡作用。计算机二级都不会考这么简单的东西,只怪当时自己脑子抽风。

⑤、满二叉树的节点数

​ 推导公式,实在是简单

⑥、计算相似度有哪几种方法

​ 余弦相似度,我踏马当时真的只想起了这个余弦相似度。 其实相似度计算有很多种方法。

​ 余弦相似度的计算代码实现。

⑥、svm的原理

⑦、介绍下tensorflow的工作流程。

​ 其实后面的关于机器学习的还回答的不错,但是前面的已经完全被问的没有了脾气,导致心理拔凉拔凉的了,可想而知面试结果当然是gg了。目前是做深度学习相关的东西,很多东西都是参考别人在github上的算法,自己实现的部分很少。导致对于c++的训练和使用仅仅局限于那么个小方面,对于数据结构算法方面几乎是仅仅记得学校时候的那些二级的知识。

​ 完全没想到这次的面试结果会是这样,也怪自己的底子不扎实,接下来一段时间要好好的将基础的数据结构和算法捡起来了。

原创粉丝点击