精华内容
下载资源
问答
  • 目的: 学习图像处理和Opencv基础知识,能够opencv进行独立编码,掌握opencv的基本操作;熟悉对图像的具体操作方式和图像的直方图统计方式... 写个程序直方图统计彩色图片像素; 写个程序能进行直方图均衡化;

    目的

    边缘检测算子很多,基本上都是对图像开一阶导数或者二阶导数,如果将边缘认为是一定数量点亮度发生变化的地方,那么边缘检测大体上就是计算这个亮度变化的导数。这次学习进一步了解图像边缘检测的算子。

    任务

    写一个彩色图片中的物体进行边缘检测,能识别出物体的特征区域轮廓;


    边缘检测

    边缘检测的三个主要评价标准是:

    • 低错误率: 标识出尽可能多的实际边缘,同时尽可能的减少噪声产生的误报。
    • 高定位性: 标识出的边缘要与图像中的实际边缘尽可能接近。
    • 最小响应: 图像中的边缘只能标识一次。

    不同图像灰度不同,边界处一般会有明显的边缘,利用此特征可以分割图像。需要说明的是:边缘和物体间的边界并不等同,边缘指的是图像中像素的值有突变的地方,而物体间的边界指的是现实场景中的存在于物体之间的边界。有可能有边缘的地方并非边界,也有可能边界的地方并无边缘,因为现实世界中的物体是三维的,而图像只具有二维信息,从三维到二维的投影成像不可避免的会丢失一部分信息;另外,成像过程中的光照和噪声也是不可避免的重要因素。正是因为这些原因,基于边缘的图像分割仍然是当前图像研究中的世界级难题,目前研究者正在试图在边缘提取中加入高层的语义信息。

    在实际的图像分割中,往往只用到一阶和二阶导数,虽然,原理上,可以用更高阶的导数,但是,因为噪声的影响,在纯粹二阶的导数操作中就会出现对噪声的敏感现象,三阶以上的导数信息往往失去了应用价值。二阶导数还可以说明灰度突变的类型。在有些情况下,如灰度变化均匀的图像,只利用一阶导数可能找不到边界,此时二阶导数就能提供很有用的信息。二阶导数对噪声也比较敏感,解决的方法是先对图像进行平滑滤波,消除部分噪声,再进行边缘检测。不过,利用二阶导数信息的算法是基于过零检测的,因此得到的边缘点数比较少,有利于后继的处理和识别工作。

    各种算子的存在就是对这种导数分割原理进行的实例化计算,是为了在计算过程中直接使用的一种计算单位。

    为什么对图像进行求导是重要的呢? 假设我们需要检测图像中的 边缘 ,到在物体边缘处,像素值明显改变。导数可以用来表示这一像素值得改变。梯度值的大变预示着图像中内容的变化。如下图:

    检测图像边缘

    用更加形象的图像来解释,假设我们有一张一维图形。下图中灰度值的”跃升”表示边缘的存在:

    这里写图片描述

    使用一阶微分求导我们可以更加清晰的看到边缘“跃升”的存在(这里显示为高峰值)

    这里写图片描述

    从上面可以推论检测边缘可以通过梯度值大于邻域的像素方法找到。以上就是一阶导数检测图像边缘,如果在边缘部分求二阶导数会出现什么情况?

    这里写图片描述

    在一阶导数的极值位置,二阶导数为0。所以我们也可以用这个特点来作为检测图像边缘的方法。 但是, 二阶导数的0值不仅仅出现在边缘(它们也可能出现在无意义的位置),不过可以过滤掉这些点。


    1.Sobel算子

    Sobel算子其主要用于边缘检测,在技术上它是以离散型的差分算子,用来运算图像亮度函数的梯度的近似值, Sobel算子是典型的基于一阶导数的边缘检测算子,由于该算子中引入了类似局部平均的运算,因此对噪声具有平滑作用,能很好的消除噪声的影响。Sobel算子对于象素的位置的影响做了加权,与Prewitt算子、Roberts算子相比因此效果更好。

    Sobel 算子结合了高斯平滑和微分求导,它是一个离散微分算子 (discrete differentiation operator)。用来计算图像灰度函数的近似梯度。

    Sobel算子包含两组 33 的矩阵,分别为横向及纵向模板,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。实际使用中,常用如下两个模板来检测图像边缘。

    算法实现

    首先在在两个方向求导:

    1.水平变化: 将图像 I 与一个奇数大小的内核 Gx 进行卷积。比如,当内核大小为3时,Gx 的计算结果为:

    Gx=121000121I

    2.垂直变化: 将图像 I 与一个奇数大小的内核Gy 进行卷积。比如,当内核大小为3时, Gy 的计算结果为:

    Gy=101202101I

    3.在图像的每一点,结合以上两个结果求出近似梯度(1),但是通常计算机处理常用公式(2)代替:

    G=G2x+G2y(1)

    G=|Gx|+|Gy(2)

    缺点是Sobel算子并没有将图像的主题与背景严格地区分开来,换言之就是Sobel算子并没有基于图像灰度进行处理,由于Sobel算子并没有严格地模拟人的视觉生理特征,所以提取的图像轮廓有时并不能令人满意。

    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    #include <stdio.h>
    #include <stdlib.h>
    
    using namespace std;
    using namespace cv;
    
    int soberEdge(Mat src) {
    
        //    声明变量
        Mat src_gray, grad;
    
        char soberWindows[] = "Sobel Demo -Simple Edge DSetector";
        int delta = 0;
        int scale = 1;
        int ddepth = CV_16S;
    
        //    降噪,使用高斯模糊进行降噪
        GaussianBlur(src, src, Size(3, 3), 0, 0, BORDER_DEFAULT);
    
        //    降噪后图像转变为灰度图像
        cvtColor(src, src_gray, CV_RGB2GRAY);
        namedWindow(soberWindows, CV_WINDOW_NORMAL);
    
        //    使用sobel算子计算x和y方向上的导数
        Mat grad_x, grad_y;
        Mat abs_grad_x, abs_grad_y;
        Sobel(src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT);
        Sobel(src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT);
    
        //    将使用sobel算子计算出来的导数结果转换成为CV_8U
        convertScaleAbs(grad_x, abs_grad_x);
        convertScaleAbs(grad_y, abs_grad_y);
    
        //    将两导数加权求和, G = |Gx| + |Gy|
        addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);
        imshow(soberWindows, grad);
    
        waitKey(0);
    
        return 1;
    }
    
    int main(int argc, char *argv[]) {
    
        Mat src;
    
        src = imread("/Users/summing/Workplaces/data/opencv1.jpg");
        if (!src.data) {
            return -1;
        }
    
        soberEdge(src);
        return 0;
    }

    这里写图片描述

    2.Scharr 算子

    Scharr计算方式与Sobel算子类似,不同的是卷积矩阵不一样,Scharr的卷积矩阵是

    Gx=31030003103I

    Gy=30310010303I

    当内核大小为 3 时,Sobel内核可能产生比较明显的误差(毕竟,Sobel算子只是求取了导数的近似值)。 为解决这一问题 Scharr 函数出现了,但该函数仅作用于大小为3的内核。该函数的运算与Sobel函数一样快,但结果却更加精确。

    //    使用Scharr算子计算x和y方向上的导数
    Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
    
    Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );

    这里写图片描述

    3.Laplacian算子

    Laplace算子是一种各向同性、二阶微分算子,在只关心边缘的位置而不考虑其周围的象素灰度差值时比较合适。Laplace算子对孤立象素的响应要比对边缘或线的响应要更强烈,因此只适用于无噪声图像。存在噪声情况下,使用Laplacian算子检测边缘之前需要先进行低通滤波。所以,通常的分割算法都是把Laplacian算子和平滑算子结合起来使用。

    Laplacian 算子 的定义:

    Laplace(f)=2fx2+2fy2

    Laplacian算子一般不以其原始形式用于边缘检测,因为其作为一个二阶导数,Laplacian算子对噪声具有无法接受的敏感性;同时其幅值产生算边缘,这是复杂的分割不希望有的结果;最后Laplacian算子不能检测边缘的方向;所以Laplacian在分割中所起的作用包括:(1)利用它的零交叉性质进行边缘定位;(2)确定一个像素是在一条边缘暗的一面还是亮的一面;

    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    #include <stdio.h>
    #include <stdlib.h>
    
    using namespace std;
    using namespace cv;
    
    int laplacianEdge(Mat src){
        Mat src_gray, dest;
    
        int kernel_size = 3;
        int scale = 1;
        int delta = 0;
        int ddepth = CV_16S;
        char laplacian_windows[] = "Laplacian Edge";
    
        //    降噪,使用高斯模糊进行降噪
        GaussianBlur(src, src, Size(3,3), 0, 0, BORDER_DEFAULT);
        cvtColor(src, src_gray, CV_RGB2GRAY);//    降噪后图像转变为灰度图像
    
        //对灰度图使用Laplacian算子:
        Mat abs_dest;
        Laplacian(src_gray, dest, ddepth, kernel_size, scale, delta, BORDER_DEFAULT);
    
        //将输出图像的深度转化为 CV_8U
        convertScaleAbs(dest, abs_dest);
    
        //显示结果
        namedWindow(laplacian_windows);
        imshow(laplacian_windows, abs_dest);
    
        waitKey(0);
    
        return 0;
    
    }
    int main(int argc, char *argv[]) {
    
        Mat src;
    
        src = imread("/Users/summing/Workplaces/data/opencv1.jpg");
        if (!src.data) {
            return -1;
        }
    
        laplacianEdge(src);
        return 0;
    }

    这里写图片描述

    4.Canny算子

    根据对信噪比与定位乘积进行测度,得到最优化逼近算子,这就是Canny边缘检测算子。Canny 边缘检测算法 是 John F. Canny 于 1986年开发出来的一个多级边缘检测算法,也被很多人认为是边缘检测的最优算法。

    Canny算子算子功能比前面几种都要好,但是它实现起来较为麻烦,Canny算子是一个具有滤波、增强、检测的多阶段的优化算子,在进行处理前,Canny算子先利用高斯平滑滤波器来平滑图像以除去噪声,Canny分割算法采用一阶偏导的有限差分来计算梯度幅值和方向,在处理过程中,Canny算子还将经过一个非极大值抑制的过程,最后Canny算子还采用两个阈值来连接边缘。读上面这一段有点拗口,现在来看看具体实现思路:

    Canny算子算法思路

    1.滤噪,用高斯滤波器平滑图像。 下面显示了一个 55 的高斯内核:

    K=1159245424912945121512549129424542

    2.用一阶偏导的有限差分来计算梯度的幅值和方向。 此处,按照Sobel滤波器的步骤,分别作对 x 和 y 方向进行卷积运算:

    Gx=121000121

    Gy=101202101

    3.使用下列公式计算梯度幅值和方向,其中梯度方向近似到四个可能角度之一( 0, 45, 90, 135):

    G=G2x+G2y

    θ=arctan(GyGx)

    4.对梯度幅值进行非极大值抑制。 这一步排除非边缘像素,仅仅保留了一些细线条作为候选边缘。

    图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不不能说明该点就是边缘(这仅仅是属于图像增强的过程)。在Canny算法中,非极大值抑制是进行边缘检测的重要步骤,通俗意义上是指寻找像素点局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点。

    这里写图片描述

    要进行非极大值抑制,就首先要确定像素点C的灰度值在其8值邻域内是否为最大。图1中蓝色的线条方向为C点的梯度方向,这样就可以确定其局部的最大值肯定分布在这条线上,也即出了C点外,梯度方向的交点dTmp1和dTmp2这两个点的值也可能会是局部最大值。因此,判断C点灰度与这两个点灰度大小即可判断C点是否为其邻域内的局部最大灰度点。如果经过判断,C点灰度值小于这两个点中的任一个,那就说明C点不是局部极大值,那么则可以排除C点为边缘。这就是非极大值抑制的工作原理。

    需要注意的是:

    1)中非最大抑制是回答这样一个问题:“当前的梯度值在梯度方向上是一个局部最大值吗?” 所以,要把当前位置的梯度值与梯度方向上两侧的梯度值进行比较;

    2)梯度方向垂直于边缘方向。

    但实际上,我们只能得到C点邻域的8个点的值,而dTmp1和dTmp2并不在其中,要得到这两个值就需要对该两个点两端的已知灰度进行线性插值,也即根据图1中的g1和g2对dTmp1进行插值,根据g3和g4对dTmp2进行插值,这要用到其梯度方向,这是上文Canny算法中要求解梯度方向矩阵Thita的原因。

    完成非极大值抑制后,会得到一个二值图像,非边缘的点灰度值均为0,可能为边缘的局部灰度极大值点可设置其灰度为128。根据下文的具体测试图像可以看出,这样一个检测结果还是包含了很多由噪声及其他原因造成的假边缘。因此还需要进一步的处理。

    5.用双阈值算法检测和连接边缘。

    Canny算法中减少假边缘数量的方法是采用双阈值法。选择两个阈值(关于阈值的选取方法在扩展中进行讨论),根据高阈值得到一个边缘图像,这样一个图像含有很少的假边缘,但是由于阈值较高,产生的图像边缘可能不闭合,未解决这样一个问题采用了另外一个低阈值。

    在高阈值图像中把边缘链接成轮廓,当到达轮廓的端点时,该算法会在断点的8邻域点中寻找满足低阈值的点,再根据此点收集新的边缘,直到整个图像边缘闭合。

    Canny使用了两个滞后阈值,分别为高阈值和低阈值:

    • 像素位置幅值超过高阈值, 保留为边缘像素。
    • 像素位置的幅值小于低阈值, 该像素被排除。
    • 像素位置的幅值在两个阈值之间,该像素仅仅在连接到一个高于高阈值的像素时被保留。

    其中Canny 推荐的 高:低 阈值比在 2:1 到3:1之间。

    不要问我幅值是什么,幅值就是做完卷积后的梯度幅值。首先需要构造灰度图的统计直方图,根据上文梯度幅值的计算公式可知,最大的梯度幅值为

    M[i,j]max=2552+2552360
    #include "canny.hpp"
    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    #include <stdio.h>
    #include <stdlib.h>
    
    using namespace cv;
    
    Mat src, dst, detected_edge, src_gray;
    char windowsName[] = "canny edge";
    int lowThreshold;
    const int maxThreshold = 100;
    int ratio = 3;
    int kernel_size = 3;
    
    
    void CannyThreshold(int, void *){
        //GaussianBlur(src_gray, detected_edge, Size(3,3), 0, 0 ,BORDER_DEFAULT);
        blur( src_gray, detected_edge, Size(3,3) );
    
        //运用 Canny 寻找边缘
        Canny(detected_edge, detected_edge, lowThreshold, lowThreshold*ratio, kernel_size);
    
        //填充 dst 图像,填充值为0 (图像全黑).
        dst = Scalar::all(0);
    
        // copyTo 标识被检测到的边缘部分 (背景为黑色).
        src_gray.copyTo(dst, detected_edge);
        imshow(windowsName, dst);
    }
    
    int cannyEdge(Mat org){
        // 创建与src同类型和大小的矩阵(dst)
        src = org;
        dst.create(src.size(), src.type());
    
        // 原图像转换为灰度图像
        cvtColor(src, src_gray, CV_BGR2GRAY);
    
        // 创建显示窗口
        namedWindow(windowsName, CV_WINDOW_NORMAL);
        //每次用户通过trackbar产生变动,回调函数 CannyThreshold 被调用.
        createTrackbar("Threshold", windowsName, &lowThreshold, maxThreshold, CannyThreshold);
        CannyThreshold(0,0);
    
        waitKey(0);
        return 1;
    }
    
    int main(int argc, char *argv[]) {
    
        Mat src;
        src = imread("/Users/summing/Workplaces/data/opencv1.jpg");
        if (!src.data) {
            return -1;
        }
    
        laplacianEdge(src);
        return 0;
    }

    这里写图片描述

    展开全文
  • 本节书摘来自华章出版社《Arduino计算机视觉编程》书中的第3章,第3.1节,作者[土耳其] 欧森·奥兹卡亚(zen zkaya),吉拉伊·伊利茨(Giray Yilliki),更多章节内容可以访问云栖社区“华章计算机”公众号...

    本节书摘来自华章出版社《Arduino计算机视觉编程》一书中的第3章,第3.1节,作者[土耳其] 欧森·奥兹卡亚(zen zkaya),吉拉伊·伊利茨(Giray Yilliki),更多章节内容可以访问云栖社区“华章计算机”公众号查看。

    第3章

    用OpenCV和Arduino进行数据采集

    在本章中,你将了解计算机视觉系统的数据采集部分。相机和传感器的数据都会被处理。本章将讲授如何给视觉系统选择相机和传感器以及如何正确使用它们。因此,本章有两个主要部分:一个部分是关于用OpenCV进行图像和视频采集,另外一个部分是用Arduino进行传感器数据采集。

    3.1 图像和视频采集

    人们通过眼睛来获取周围环境的视觉信息。当涉及机器如何获取视觉数据时,各种各样的相机被用于这个目的。因为视觉数据会用数字来表示,有效的相机处理会得到一个更好的表示。这是你可以从本章中学到的知识。
    在第2章中,你已经安装了OpenCV并且运行了一个典型的hello world应用。在此基础上,我们通过手头实际案例的讲解来学习OpenCV的数据采集功能。
    OpenCV支持各种各样的相机,这样开发者有很大的选择范围。学习如何针对你的应用选择一个合适的相机也是本章的一个主题。你会在本章的相机选择部分找到答案。
    选择相机之后,我们将研究如何使用OpenCV进行图像和视频的采集。在此过程中,我们将会学习如何读写图像、捕捉视频等。现在让我们开始吧!
    3.1.1 相机选择
    相机的选择是和需求紧密相关的。所以,在开始的时候多花点时间仔细考虑你需要的系统。这个简单的步骤会在后面的开发过程中帮你节省时间和金钱。除了需求以外,相机本身的性能也是需要考虑的。所以应该联系项目需求和相机性能来做出最佳选择。这正是本节要讲的内容!
    3.1.1.1 分辨率
    为了给应用程序选择合适的相机,分辨率和相关的相机传感器属性非常重要,先解释一下分辨率这个术语代表的意思。
    当我们谈论一台200万像素相机的时候,这是什么意思?这是指图像(或者一个视频帧)上像素的数目。如果相机产生的图片有1200像素高、1800像素宽,那么图像包括1200×1800 = 2?160?000个像素大概是两百万像素。尽管这个属性被称为相机的分辨率,但是现实中的视觉应用程序往往需要不同的信息比如物体的分辨率,它与相机分辨率紧密相关但是需要更多的细节。
    在实际使用中,分辨率意味着可用来区分两点的最小距离。因为照相的目的是获取一个物体或者事件的细节,能够获取的最小细节就显得非常重要。对于视频也是一样的,它不过是一系列连续的图像。
    物体分辨率这个重要术语意味着可以通过相机察觉的物体最小粒度。因此物体和最小粒度都是非常重要的。下式展示了一个特定物体的大小和可以获取的最小粒度之间的关系:


    44c57e2c96d8a3096f6d57a13af0613f4dc1d455

    让我们举个现实生活中的例子。想象一下你正在看一辆车的车牌。假设你的相机能看到车的全景。车在全视图里面的高度和宽度都是2m。假设为了能看到车牌,你需要的最小粒度是高0.2cm、宽0.2cm。
    为了理解所需的物体分辨率,我们把值代入上面的公式可以得到:


    4aacb25dacd2585fc4c55af430c8b024f2d8ca3e

    所以针对这个情况我们需要的分辨率为100万像素。但是需要注意的是这个计算依赖于物体和相机之间的距离。如果我们是从比较远的地方照相,图像中的物体会更小,因此为了识别车牌需要一个更高的分辨率。反之亦然,如果车离相机更近,那么车牌在图像中就会更大,因此一个更低分辨率的相机就能满足需求。
    距离和分辨率之间的关系有一点复杂,但是可以用一种实用的方法来简化它。下图很好地展示了相机到物体的距离的重要性。


    60c2b7fc98cdbb7859e9ed35b31ab397ad5933ad

    在这种情况下,我们可以很容易看出图片的宽度是车宽度的三倍,高度是车高度的两倍,所以总的分辨率是最初计算的6倍,就是600万像素,见以下计算:


    9f3a47926faadc67d97849e31cc38f0f09e28ab4

    你越是经常在实际例子中实践,就能越准确地猜出特定应用所需相机的分辨率。

    3.1.1.2 颜色
    从颜色的角度区分,相机有两种类型:单色或者彩色。选择单色相机还是彩色相机仅仅取决于应用的要求。如果应用需要彩色信息,那么必须用彩色相机。如果颜色不重要,重要的是形状,最好选择一个单色(灰度)相机。单色相机通常比彩色相机更敏感并能提供更清晰的图片。在某些情况下将这两种相机一起使用也是有可能并且是必要的。
    大部分网络摄像头是彩色的。此外你可以用OpenCV从彩色相机里面捕获一个灰度图像。一般来说,考虑未来的扩展性,彩色相机是首选。通过这种方式,你可以同时获取颜色和形状信息。
    3.1.1.3 帧率
    帧率是用每秒的帧数(FPS)来衡量的。帧率这个术语描述了相机每秒能够捕获和传输的图像的数量。一般来说,网络摄像头最高可以达到30FPS。有一些相机允许你动态调整帧率。
    帧率越高,传感器运行得更快。此外,更高的帧率也会导致更多数据的存储。需要注意的是,如果你的应用程序不够快,也许无法充分利用相机的最高FPS。如果要在工厂每分钟生产6000个产品的生产线上找到产品缺陷,你可能需要一个高FPS相机。每分钟6000个产品意味着每秒100个,假设你需要为每个产品花10帧来找到缺陷,那么在这个应用中你需要一个10×100 = 1000FPS的相机。
    FPS越高的相机,价格也越高。针对工厂这个例子做一个启发性计算并用帧速率来定义平均速度。比如一个普通的网络摄像机的FPS可能是50,而上文讨论的工厂的例子至少需要一个1000FPS的相机来检测缺陷,如果做一个粗略估计,1000FPS相机的价格可能是普通相机的20倍。
    3.1.1.4 2D还是3D
    从原理上来说,相机获取的图像是对场景的2D投影。如果你熟悉网络摄像头,它们都是2D网络摄像头。
    3D相机加了第三个维度——到数据的距离。3D相机这个词指的是广角相机或者立体相机。广角相机会生成一个每个像素带着到特定点距离信息的图像。立体相机是用2个平行相机模仿人类的视觉来捕获三维图像。通过两张图片对应点的偏移量,可以计算出图像中任意点的深度。
    如果你的应用需要3D功能,那么你需要一个3D相机比如Kinect或者Asus Xtion Pro Live。当然,还有很多其他3D相机!
    再次提醒,3D相机需要更多的存储空间。所以如果你不需要3D信息,请使用2D相机。
    3.1.1.5 通信接口
    通信接口影响相机系统的很多属性。流行的接口有USB、FireWire、GigE和CameraLink。有很多变量可以进行比较,见下表:
    通信接口 最大距离 最大带宽 支持多相机 实时性 即插即用
    USB 2.0 5m 40MB/s 中等 低 高
    USB 3.0 8m 350MB/s 中等 高 高
    FireWire 4.5m 65MB/s 中等 高 高
    CameraLink 10m 850MB/s 低 高 低
    GigE 100m 100MB/s 高 中等 中等

    从表中可以看到,通信接口对控制器到相机的距离、带宽、FPS甚至可用性会产生巨大影响!所以,请评估你的需求并且给应用选择合适的接口。
    网络摄像头有一个USB接口。所以它们有不错的控制器到相机距离、带宽、FPS和可用性。这使得绝大多数计算机视觉应用可以使用网络摄像头。
    3.1.2 图像采集
    到现在为止,我们已经对如何选择相机有了一个比较清楚的认识。现在是时候更进一步了,让我们从相机中读取数据。
    大道至简,最好让一切事情保持简单并且易于使用。正因如此,我们的例子将使用一个标准的网络摄像头。这个例子将选用罗技C120作为网络摄像头。请注意安装网络摄像头的驱动以便可以与OpenCV一起工作。
    我们使用了OpenCV C++ API来从相机中读取数据,此外,OpenCV提供了很棒的文档,可以用C、Python和Java API轻松实现这个例子。如果需要更多信息,可以访问opencv.org。
    3.1.2.1 读取静态图片
    在设计的概念验证阶段,使用静态图片进行工作是有好处的。例如,假设你想开发一个人脸识别的应用程序。首先,你应该在一组人脸图像的样本上工作,所以,从数据存储中读取静态数据将是这个过程的第一步。
    OpenCV使计算机视觉系统的开发变得简单!让我们用OpenCV从存储中读取静态数据。现在编写代码来读取静态图片并将它展现在窗口中。请把下面的代码输入到OpenCV开发环境中并将其保存为read_static_image.cpp:


    132e2a75681f0d9619a066f38338d610a7d17125


    710e01715feee3ae20cf2e30ff53fa99f69f2e12

    当你编译这段代码时,它将生成一个可执行文件。在Windows环境下,我们姑且认为它是read_static_image.exe。你可以把计算机视觉里面著名的lena.jpg图片作为输入。在Windows环境下,把这个图片复制到C:/cv_book/目录下。在cmd.exe窗口中,导航到包含read_static_image.exe的目录下。该指令如下:


    1bb76961a8a95dffd69f7229e0d3a527db61b7a8


    1bb76961a8a95dffd69f7229e0d3a527db61b7a8

    在其他平台上,指令也类似。比如你的可执行文件是read_static_image.exe并且与lena.jpg在同一个目录下,你可以执行下面的指令来运行程序:

    6add1ecba32ca18b22356199185ea8d50f70e11b

    现在让我们解释这段代码。引入头文件这样的基础部分已经在前面讲过。让我们从新的部分开始,如下代码片段所示:


    9842ae6e5fe15a1f62080150a087794538b45f85

    首先检查参数的数目是否正确。在前面的命令行中看到应该有两个参数:第一个参数是程序本身,第二个参数是图片路径。如果参数的数量不是两个,那么程序将打印一行帮助信息然后报错退出。
    下一步创建了一个Mat对象用来存储图像数据。图片数据以RGB格式读取,如下代码所示:


    b8de9ecb84299458f49586d87a4f5d4c03075a28

    然后判断是否成功读取图片,并确认图片数据不为空。创建一个名为Read Static Image的窗口,图片会显示在该窗口中。


    496444e102209f87aa11da6bf8b022679d17e4f3

    我们想要图片持续显示直到用户按键退出,所以我们使用waitKey函数,它只有一个用来表示等待用户输入多久(单位是毫秒)的参数。零意味着永远等待:


    32c0266721ab65bcae2961865d40fe2a7a9790c7

    程序的输出如下:


    4ed296a3589d5f1b5d580f9671a7b7766ae67f37

    你可以使用CV_LOAD_IMAGE_GRAYSCALE属性来以灰度图的格式加载图片!

    3.1.2.2 在网络摄像头里面做快照
    有时候需要用相机做快照并且保存下来以便进一步分析。这个过程通常通过一个触发器启动。
    回想一下基于人脸识别的门锁例子。当访客按门铃时,系统需要对脸部做一个快照并且对它进行人脸识别。这个应用是快照的一个很好的例子,我们还能找到其他需要做快照的相似情形。使用以下代码创建一个项目并且保存为take_snapshot.cpp。


    2e0b9b0c9636af2fd490572510d8be20eb67fbd4

    前面的代码和read_static_image.cpp的代码相似。但它只是从相机中读取一帧的图像并用命令行传进来的名字对这一帧进行保存。命令行如下所示:


    e8372024ad5726a6f85aa6d606ab53f5aab0f925

    如果应用的名字不是take_snapshot,那么想要运行这段命令应把take_snapshot替换成应用的名字。现在让我们来解释代码!
    首先,我们创建了一个视频捕捉对象用来从连接的相机中捕获数据。通常情况下CV_CAP_ANY的值是0。如果你使用多台相机,需要手动增加索引,比如cap(1):


    3b39ee04961dc5d9ccd21b8c77f6c3a511f522b1

    下一步创建了一个用来保存帧的矩阵对象,并把帧数据从相机对象读到矩阵对象中:


    3e02be8501556591d6648b8d6456598e6ffc811d

    在检查帧不为空之后,就开始读取数据,然后用imwrite函数来保存图片。图片的名字是命令行的第一个参数。


    c297835e5a09e4e321b36a3c9ef8f05c7790c027

    3.1.2.3 从网络摄像头获取视频流
    视频处理应用程序通常需要一个视频流作为输入数据。通过分析帧间数据,就可以完成一些更高级的任务,比如运动识别等。
    视频流的FPS值和分辨率在这一过程中就变得非常重要,因为它们直接影响实时行为。越高的FPS意味着用来进行处理的时间越少。同样,越高的分辨率也让计算变得更加复杂和耗时。
    基于手势的运动识别是一个非常好的视频流应用例子。同样,寻线机器人也需要捕获一个实时视频流来持续地沿线行走。所以我们要理解这一部分的重要性,让我们用下述代码来捕获实时视频流:


    9c28b5a297998eb947347d168a02c58b7f03307f


    26b329bc2cc4e8c39a6808fc6ff3168482094352

    这段代码和快照代码非常相似,除了帧数据的读取过程是在一个无限while循环中。有一个可选的ProcessFrame函数来把视频帧转成灰度帧。如果你想要一个彩色的帧,那么注释掉ProcessFrame函数。
    在运行这段代码时,你能看到相机里面的实时视频流!
    如果要改变视频流的FPS,你可以使用函数bool VideoCapture::set(int propId, double value),propId的值为CV_CAP_PROP_FPS。如果你要把FPS设为10,你可以输入:


    27db4560ece5b299446c186da2a2cb6e884ea629

    别忘了先检查你的相机是否支持不同的帧率。
    3.1.2.4 与Kinect交互
    Kinect是一个革命性的3D图像设备,由微软研发并被广泛应用于计算机视觉应用中。Kinect的第一个版本是与大受欢迎的游戏主机Xbox 360一起发布的,Xbox的新版本名为Xbox One。尽管它最初是为游戏创造的,但是Kinect对计算机视觉应用产生了巨大的促进作用。
    Kinect的默认连接器是为Xbox专门设计的,电源线和数据线是一根线。如果电脑或者微机要使用Kinect,你需要一根USB转换线(见下图)。
    现在让我们开始与Kinect进行交互并通过OpenCV来访问它。然后你将开始学习3D视觉应用程序!


    58a41859e14a8886cf82c3feee9fbb0c434b5a14

    3.1.2.5 将OpenCV与Kinect集成
    安装点云库(Point Clond Library,PCL)将安装所有Kinect需要的软件和驱动。PCL是一个多平台库。你可以从http://pointclouds.org/得到PCL的安装包。在Mac上你无需安装PCL来使用Kinect。只要执行这一章的步骤就可以让Kinect工作起来。
    你需要安装libfreenect驱动来让Kinect与OpenCV通信。现在我们进入主题!我们将在Mac示范这个例子,在其他平台上也同样有效。
    在Mac上安装
    最好安装一个诸如Homebrew、Macports或者Fink这样的包管理器。正如我们之前的选择,我们将用Homebrew进行安装。
    让我们按照以下步骤安装libfreenect:
    1.?如果你还没有安装Homebrew,你可以看下2.2.2节中的指令。不管你是否安装了Homebrew,运行下列程序来更新Homebrew:


    6666d87803115b349d78b78ea374d5b992c92f11

    2.?要安装libfreenect,请在终端输入如下指令:


    33150dbb9f505dfa8df6b005f51c46e9254fce36

    3.?这就完成了,现在你可以把Kinect插到电脑上并执行下面的命令来体验下带深度信息的图像:


    940bd528d1d2c07610441bbf723a401fbe22ad17

    Xcode集成
    与OpenCV和Xcode的集成一样,我们将使用相同的步骤来集成libfreenect以便在OpenCV中使用Kinect的功能。
    1.按照之前创建OpenCV_Template项目的步骤来创建一个新的项目。命名为OpeCV_Kinect。
    2.在OpeCV_Kinect项目上依次单击Build Settings | Search Paths | Header Search Paths并单击加号来添加如下的两个路径:


    9babbf030df0717919ab5a8d9911ee9fea2566cc

    3.然后,依次单击Build Phases | Link Binary with Libraries并单击加号来添加两个需要的框架:


    937c6ae3f7b745e215368cc8f4b0c1235e010381

    4.加完框架以后,需要添加libfreenect库和libusb库。然后,再次单击加号,再单击Add Other…。
    5.按cmd+Shift+g组合键然后在弹出窗口中输入/usr/local/lib。之后选择ibfreenect_*.dylibs。在选择的同时持续按下cmd键。
    6.现在让我们执行相同的步骤来加入libusb库。按cmd+Shift+g组合键然后在弹出窗口输入/usr/local。之后选择libusb*.dylibs。
    这样就集成好了!现在按照以下步骤开始编码。
    1.?下载libfreenect-master得到我们需要的源代码。你可以从https://github.com/OpenKinect/libfreenect下载。
    2.?复制粘贴libfreenect-master/wrappers/opencv/cvdemo.c到OpenCV_Kinect项目的main.cpp文件里。
    3.?最后,我们需要把libfreenect_cv.h和libfreenect_cv.c拖到OpenCV_Kinect项目的文件夹中(与main.cpp在一个文件夹中)并构建!以下是main.cpp的代码:


    d9fbaffd248ecd3e60919ada904ca79ab7305a6f


    12ad616ef545f6afcfa20e02f397b014cdd76622


    887e89dfab78ffec1a9f6a69e5358474633f930c

    现在,让我们通过代码来解释它是如何工作的,以及如何从Kinect中获取带深度信息的图像:


    f1e56d10bd53b447e36593037b22091422e69583


    d73931ea526653aecb78422c382cbfb1717b4f4d

    来自Kinect的数据需要转换成合适的格式以便存储深度信息。在IplImage *
    GlViewColor(IplImage *depth)函数的for循环里,深度信息被转换成颜色。这个函数返回了一个缩放过的图像来进行表示。这是main函数里面的代码部分:


    8b13730c2d5299ccf75de09f9b0bdd2f52453b0d

    在main函数中,有一个while循环会一直循环直到有按键输入。While循环会每10ms检测一次,这是因为调用了cvWaitKey(10),所以会等待10ms就检测一次:


    19d419e81b2860c3f2d44b510bbc435a26902b40

    代码中使用Libfreenect库的freenect_sync_get_rgb_cv函数接收RGB相机的图像并把其加载到IplImage*这个图像指针指向的内存地址,如上述代码所示。代码段中的if语句会检测图像是否正确加载,如果没有正确加载会返回-1来中止程序。在cvCvtColor函数的帮助下,图像会从RGB格式转换为BGR格式。


    77313b9ca9e9028cfc6b41304e9db91ea769e64b

    对于图像的深度信息会执行一个类似的过程。freenect_sync_get_depth_cv函数会去获取带深度信息的原始图像并把它放到IplImage *depth指针指向的内存地址。然后,会检测深度图像是否正确加载。下面是if循环之后的代码:


    e02e99db20ef405fea0bc993d52fa7654a79395b

    现在是时候用RGB信息和深度信息把图像展示出来了。如果仔细看前面代码片段的第二行,会注意到原始的深度图像使用了GlViewColor函数进行缩放。
    运行代码后,你会看到下图:


    949d98f7107dba19fb57c2d87e714b43c5afcae3

    在深度信息图中,可以看到靠近摄像机的物体会更红一点,而远离摄像机的物体会更绿一点。
    展开全文
  • 如何Python和深度神经网络识别图像

    千次阅读 多人点赞 2017-12-28 00:00:00
    )视觉进化的作用,让人类对图像的处理非常高效。这里,我给你展示一张照片。如果我这样问你:你能否分辨出图片中哪个是猫,哪个是狗?你可能立即会觉得自己遭受到了莫大的侮辱。并且大声质问我:你觉得我智商有问题...
        

    640?wx_fmt=png&wxfrom=5&wx_lazy=1

    (由于微信公众号外部链接的限制,文中的部分链接可能无法正确打开。如有需要,请点击文末的“阅读原文”按钮,访问可以正常显示外链的版本。)

    视觉

    进化的作用,让人类对图像的处理非常高效。

    这里,我给你展示一张照片。

    640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

    如果我这样问你:

    你能否分辨出图片中哪个是猫,哪个是狗?

    你可能立即会觉得自己遭受到了莫大的侮辱。并且大声质问我:你觉得我智商有问题吗?!

    息怒。

    换一个问法:

    你能否把自己分辨猫狗图片的方法,描述成严格的规则,教给计算机,以便让它替我们人类分辨成千上万张图片呢?

    对大多数人来说,此时感受到的,就不是羞辱,而是压力了。

    如果你是个有毅力的人,可能会尝试各种判别标准:图片某个位置的像素颜色、某个局部的边缘形状、某个水平位置的连续颜色长度……

    你把这些描述告诉计算机,它果然就可以判断出左边的猫和右边的狗了。

    问题是,计算机真的会分辨猫狗图片了吗?

    我又拿出一张照片给你。

    0?wx_fmt=jpeg

    你会发现,几乎所有的规则定义,都需要改写。

    当机器好不容易可以用近似投机取巧的方法正确分辨了这两张图片里面的动物时,我又拿出来一张新图片……

    0?wx_fmt=png

    几个小时以后,你决定放弃

    别气馁。

    你遭遇到的,并不是新问题。就连大法官,也有过同样的烦恼。

    0?wx_fmt=jpeg

    1964年,美国最高法院的大法官Potter Stewart在“Jacobellis v. Ohio”一案中,曾经就某部电影中出现的某种具体图像分类问题,说过一句名言“我不准备就其概念给出简短而明确的定义……但是,我看见的时候自然会知道”(I know it when I see it)。

    原文如下:

    I shall not today attempt further to define the kinds of material I understand to be embraced within that shorthand description (“hard-core pornography”), and perhaps I could never succeed in intelligibly doing so. But I know it when I see it, and the motion picture involved in this case is not that.

    考虑到精神文明建设的需要,这一段就不翻译了。

    人类没法把图片分辨的规则详细、具体而准确地描述给计算机,是不是意味着计算机不能辨识图片呢?

    当然不是。

    2017年12月份的《科学美国人》杂志,就把“视觉人工智能”(AI that sees like humans)定义为2017年新兴技术之一。

    0?wx_fmt=jpeg

    你早已听说过自动驾驶汽车的神奇吧?没有机器对图像的辨识,能做到吗?

    你的好友可能(不止一次)给你演示如何用新买的iPhone X做面部识别解锁了吧?没有机器对图像的辨识,能做到吗?

    0?wx_fmt=jpeg

    医学领域里,计算机对于科学影像(如X光片)的分析能力,已经超过有多年从业经验的医生了。没有机器对图像的辨识,能做到吗?

    0?wx_fmt=png

    你可能一下子觉得有些迷茫了——这难道是奇迹?

    不是。

    计算机所做的,是学习

    通过学习足够数量的样本,机器可以从数据中自己构建模型。其中,可能涉及大量的判断准则。但是,人类不需要告诉机器任何一条。它是完全自己领悟和掌握的。

    你可能会觉得很兴奋。

    那么,下面我来告诉你一个更令你兴奋的消息——你自己也能很轻易地构建图片分类系统!

    不信?请跟着我下面的介绍,来试试看。

    数据

    咱们就不辨识猫和狗了,这个问题有点不够新鲜。

    咱们来分辨机器猫,好不好?

    对,我说的就是哆啦a梦。

    0?wx_fmt=png

    把它和谁进行区分呢?

    一开始我想找霸王龙,后来觉得这样简直是作弊,因为他俩长得实在差别太大

    0?wx_fmt=png

    既然哆啦a梦是机器人,咱们就另外找个机器人来区分吧。

    一提到机器人,我立刻就想起来了它。

    0?wx_fmt=png

    对,机器人瓦力(WALLE)。

    我给你准备好了119张哆啦a梦的照片,和80张瓦力的照片。图片已经上传到了这个Github项目。

    请点击这个链接,下载压缩包。然后在本地解压。作为咱们的演示目录

    解压后,你会看到目录下有个image文件夹,其中包含两个子目录,分别是doraemon和walle。

    0?wx_fmt=jpeg

    打开其中doraemon的目录,我们看看都有哪些图片。

    0?wx_fmt=jpeg

    可以看到,哆啦a梦的图片真是五花八门。各种场景、背景颜色、表情、动作、角度……不一而足。

    这些图片,大小不一,长宽比例也各不相同。

    我们再来看看瓦力,也是类似的状况。

    0?wx_fmt=jpeg

    数据已经有了,下面我们来准备一下环境配置。

    环境

    我们使用Python集成运行环境Anaconda。

    请到这个网址 下载最新版的Anaconda。下拉页面,找到下载位置。根据你目前使用的系统,网站会自动推荐给你适合的版本下载。我使用的是macOS,下载文件格式为pkg。

    0?wx_fmt=png

    下载页面区左侧是Python 3.6版,右侧是2.7版。请选择2.7版本。

    双击下载后的pkg文件,根据中文提示一步步安装即可。

    0?wx_fmt=jpeg

    安装好Anaconda后,我们需要安装TuriCreate。

    请到你的“终端”(Linux, macOS)或者“命令提示符”(Windows)下面,进入咱们刚刚下载解压后的样例目录。

    执行以下命令,我们来创建一个Anaconda虚拟环境,名字叫做turi。

    conda create -n turi python=2.7 anaconda

    然后,我们激活turi虚拟环境。

    source activate turi

    在这个环境中,我们安装最新版的TuriCreate。

    pip install -U turicreate

    安装完毕后,执行:

    jupyter notebook

    0?wx_fmt=jpeg

    这样就进入到了Jupyter笔记本环境。我们新建一个Python 2笔记本。

    0?wx_fmt=jpeg

    这样就出现了一个空白笔记本。

    0?wx_fmt=jpeg

    点击左上角笔记本名称,修改为有意义的笔记本名“demo-python-image-classification”。

    0?wx_fmt=jpeg

    准备工作完毕,下面我们就可以开始编写程序了。

    代码

    首先,我们读入TuriCreate软件包。它是苹果并购来的机器学习框架,为开发者提供非常简便的数据分析与人工智能接口。

    import turicreate as tc

    我们指定图像所在的文件夹image。

    img_folder = 'image'

    前面介绍了,image下,有哆啦a梦和瓦力这两个文件夹。注意如果将来你需要辨别其他的图片(例如猫和狗),请把不同类别的图片也在image中分别存入不同的文件夹,这些文件夹的名称就是图片的类别名(cat和dog)。

    然后,我们让TuriCreate读取所有的图像文件,并且存储到data数据框。

    data = tc.image_analysis.load_images(img_folder, with_path=True)

    这里可能会有错误信息。

    Unsupported image format. Supported formats are JPEG and PNG    file: /Users/wsy/Dropbox/var/wsywork/learn/demo-workshops/demo-python-image-classification/image/walle/.DS_Store

    本例中提示,有几个.DS_Store文件,TuriCreate不认识,无法当作图片来读取。

    这些.DS_Store文件,是苹果macOS系统创建的隐藏文件,用来保存目录的自定义属性,例如图标位置或背景颜色。

    我们忽略这些信息即可。

    下面,我们来看看,data数据框里面都有什么。

    data

    0?wx_fmt=png

    可以看到,data包含两列信息,第一列是图片的地址,第二列是图片的长宽描述。

    因为我们使用了119张哆啦a梦图片,80张瓦力图片,所以总共的数据量是199条。数据读取完整性验证通过。

    下面,我们需要让TuriCreate了解不同图片的标记(label)信息。也就是,一张图片到底是哆啦a梦,还是瓦力呢?

    这就是为什么一开始,你就得把不同的图片分类保存到不同的文件夹下面。

    此时,我们利用文件夹名称,来给图片打标记。

    data['label'] = data['path'].apply(lambda path: 'doraemon' if 'doraemon' in path else 'walle')

    这条语句,把doraemon目录下的图片,在data数据框里打标记为doraemon。反之就都视为瓦力(walle)。

    我们来看看标记之后的data数据框。

    data

    0?wx_fmt=png

    可以看到,数据的条目数量(行数)是一致的,只是多出来了一个标记列(label),说明图片的类别。

    我们把数据存储一下。

    data.save('doraemon-walle.sframe')

    这个存储动作,让我们保存到目前的数据处理结果。之后的分析,只需要读入这个sframe文件就可以了,不需要从头去跟文件夹打交道了。

    从这个例子里,你可能看不出什么优势。但是想象一下,如果你的图片有好几个G,甚至几个T,每次做分析处理,都从头读取文件和打标记,就会非常耗时。

    我们深入探索一下数据框。

    TuriCreate提供了非常方便的explore()函数,帮助我们直观探索数据框信息。

    data.explore()

    这时候,TuriCreate会弹出一个页面,给我们展示数据框里面的内容。

    0?wx_fmt=png

    原先打印data数据框,我们只能看到图片的尺寸,此时却可以浏览图片的内容。

    如果你觉得图片太小,没关系。把鼠标悬停在某张缩略图上面,就可以看到大图。

    0?wx_fmt=png

    数据框探索完毕。我们回到notebook下面,继续写代码。

    这里我们让TuriCreate把data数据框分为训练集合和测试集合。

    train_data, test_data = data.random_split(0.8, seed=2)

    训练集合是用来让机器进行观察学习的。电脑会利用训练集合的数据自己建立模型。但是模型的效果(例如分类的准确程度)如何?我们需要用测试集来进行验证测试。

    这就如同老师不应该把考试题目都拿来给学生做作业和练习一样。只有考学生没见过的题,才能区分学生是掌握了正确的解题方法,还是死记硬背了作业答案。

    我们让TuriCreate把80%的数据分给了训练集,把剩余20%的数据拿到一边,等待测试。这里我设定了随机种子取值为2,这是为了保证数据拆分的一致性。以便重复验证我们的结果。

    好了,下面我们让机器开始观察学习训练集中的每一个数据,并且尝试自己建立模型。

    下面代码第一次执行的时候,需要等候一段时间。因为TuriCreate需要从苹果开发者官网上下载一些数据。这些数据大概100M左右。

    需要的时长,依你和苹果服务器的连接速度而异。反正在我这儿,下载挺慢的。

    好在只有第一次需要下载。之后的重复执行,会跳过下载步骤。

    model = tc.image_classifier.create(train_data, target='label')

    下载完毕后,你会看到TuriCreate的训练信息。

    Resizing images...
    Performing feature extraction on resized images...
    Completed 168/168
    PROGRESS: Creating a validation set from 5 percent of training data. This may take a while.
             You can set ``validation_set=None`` to disable validation tracking.

    你会发现,TuriCreateh会帮助你把图片进行尺寸变换,并且自动抓取图片的特征。然后它会从训练集里面抽取5%的数据作为验证集,不断迭代寻找最优的参数配置,达到最佳模型。

    这里可能会有一些警告信息,忽略就可以了。

    当你看到下列信息的时候,意味着训练工作已经顺利完成了。

    0?wx_fmt=png

    可以看到,几个轮次下来,不论是训练的准确度,还是验证的准确度,都已经非常高了。

    下面,我们用获得的图片分类模型,来对测试集做预测。

    predictions = model.predict(test_data)

    我们把预测的结果(一系列图片对应的标记序列)存入了predictions变量。

    然后,我们让TuriCreate告诉我们,在测试集上,我们的模型表现如何。

    先别急着往下看,猜猜结果正确率大概是多少?从0到1之间,猜测一个数字。

    猜完后,请继续。

    metrics = model.evaluate(test_data)
    print(metrics['accuracy'])

    这就是正确率的结果:

    0.967741935484

    我第一次看见的时候,震惊不已。

    我们只用了100多个数据做了训练,居然就能在测试集(机器没有见过的图片数据)上,获得如此高的辨识准确度。

    为了验证这不是准确率计算部分代码的失误,我们来实际看看预测结果。

    predictions

    这是打印出的预测标记序列:

    dtype: str
    Rows: 31
    ['doraemon', 'doraemon', 'doraemon', 'doraemon', 'walle', 'doraemon', 'walle', 'doraemon', 'walle', 'walle', 'doraemon', 'doraemon', 'doraemon', 'doraemon', 'doraemon', 'walle', 'doraemon', 'doraemon', 'walle', 'walle', 'doraemon', 'doraemon', 'walle', 'walle', 'walle', 'doraemon', 'doraemon', 'walle', 'walle', 'doraemon', 'walle']

    再看看实际的标签。

    test_data['label']

    这是实际标记序列:

    dtype: str
    Rows: 31
    ['doraemon', 'doraemon', 'doraemon', 'doraemon', 'walle', 'doraemon', 'walle', 'walle', 'walle', 'walle', 'doraemon', 'doraemon', 'doraemon', 'doraemon', 'doraemon', 'walle', 'doraemon', 'doraemon', 'walle', 'walle', 'doraemon', 'doraemon', 'walle', 'walle', 'walle', 'doraemon', 'doraemon', 'walle', 'walle', 'doraemon', 'walle']

    我们查找一下,到底哪些图片预测失误了。

    你当然可以一个个对比着检查。但是如果你的测试集有成千上万的数据,这样做效率就会很低。

    我们分析的方法,是首先找出预测标记序列(predictions)和原始标记序列(test_data['label'])之间有哪些不一致,然后在测试数据集里展示这些不一致的位置。

    test_data[test_data['label'] != predictions]

    0?wx_fmt=png

    我们发现,在31个测试数据中,只有1处标记预测发生了失误。原始的标记是瓦力,我们的模型预测结果是哆啦a梦。

    我们获得这个数据点对应的原始文件路径。

    wrong_pred_img_path = test_data[predictions != test_data['label']][0]['path']

    然后,我们把图像读取到img变量。

    img = tc.Image(wrong_pred_img_path)

    用TuriCreate提供的show()函数,我们查看一下这张图片的内容。

    img.show()

    0?wx_fmt=png

    因为深度学习的一个问题在于模型过于复杂,所以我们无法精确判别机器是怎么错误辨识这张图的。但是我们不难发现这张图片有些特征——除了瓦力以外,还有另外一个机器人。

    如果你看过这部电影,应该知道两个机器人之间的关系。这里我们按下不表。问题在于,这个右上方的机器人圆头圆脑,看上去与棱角分明的瓦力差别很大。但是,别忘了,哆啦a梦也是圆头圆脑的。

    原理

    按照上面一节的代码执行后,你应该已经了解如何构建自己的图片分类系统了。在没有任何原理知识的情况下,你研制的这个模型已经做得非常棒了。不是吗?

    如果你对原理不感兴趣,请跳过这一部分,看“小结”。

    如果你对知识喜欢刨根问底,那咱们来讲讲原理。

    虽然不过写了10几行代码,但是你构建的模型却足够复杂和高大上。它就是传说中的卷积神经网络(Convolutional Neural Network, CNN)。

    它是深度机器学习模型的一种。最为简单的卷积神经网络大概长这个样子:

    0?wx_fmt=png

    最左边的,是输入层。也就是咱们输入的图片。本例中,是哆啦a梦和瓦力。

    在计算机里,图片是按照不同颜色(RGB,即Red, Green, Blue)分层存储的。就像下面这个例子。

    0?wx_fmt=jpeg

    根据分辨率不同,电脑会把每一层的图片存成某种大小的矩阵。对应某个行列位置,存的就是个数字而已。

    这就是为什么,在运行代码的时候,你会发现TuriCreate首先做的,就是重新设置图片的大小。因为如果输入图片大小各异的话,下面步骤无法进行。

    有了输入数据,就顺序进入下一层,也就是卷积层(Convolutional Layer)。

    卷积层听起来似乎很神秘和复杂。但是原理非常简单。它是由若干个过滤器组成的。每个过滤器就是一个小矩阵。

    使用的时候,在输入数据上,移动这个小矩阵,跟原先与矩阵重叠的位置上的数字做乘法后加在一起。这样原先的一个矩阵,就变成了“卷积”之后的一个数字。

    下面这张动图,很形象地为你解释了这一过程。

    0?wx_fmt=gif

    这个过程,就是不断从一个矩阵上去寻找某种特征。这种特征可能是某个边缘的形状之类。

    再下一层,叫做“池化层”(Pooling Layer)。这个翻译简直让人无语。我觉得翻译成“汇总层”或者“采样层”都要好许多。下文中,我们称其为“采样层”。

    采样的目的,是避免让机器认为“必须在左上角的方格位置,有一个尖尖的边缘”。实际上,在一张图片里,我们要识别的对象可能发生位移。因此我们需要用汇总采样的方式模糊某个特征的位置,将其从“某个具体的点”,扩展成“某个区域”。

    如果这样说,让你觉得不够直观,请参考下面这张动图。

    0?wx_fmt=gif

    这里使用的是“最大值采样”(Max-Pooling)。以原先的2x2范围作为一个分块,从中找到最大值,记录在新的结果矩阵里。

    一个有用的规律是,随着层数不断向右推进,一般结果图像(其实正规地说,应该叫做矩阵)会变得越来越小,但是层数会变得越来越多。

    只有这样,我们才能把图片中的规律信息抽取出来,并且尽量掌握足够多的模式。

    如果你还是觉得不过瘾,请访问这个网站。

    它为你生动解析了卷积神经网络中,各个层次上到底发生了什么。

    0?wx_fmt=jpeg

    左上角是用户输入位置。请利用鼠标,手写一个数字(0-9)。写得难看一些也没有关系。

    我输入了一个7。

    观察输出结果,模型正确判断第一选择为7,第二可能性为3。回答正确。

    让我们观察模型建构的细节。

    我们把鼠标挪到第一个卷积层。停在任意一个像素上。电脑就告诉我们这个点是从上一层图形中哪几个像素,经过特征检测(feature detection)得来的。

    0?wx_fmt=png

    同理,在第一个Max pooling层上悬停,电脑也可以可视化展示给我们,该像素是从哪几个像素区块里抽样获得的。

    0?wx_fmt=jpeg

    这个网站,值得你花时间多玩儿一会儿。它可以帮助你理解卷积神经网络的内涵。

    回顾我们的示例图:

    0?wx_fmt=png

    下一层叫做全连接层(Fully Connected Layer),它其实就是把上一层输出的若干个矩阵全部压缩到一维,变成一个长长的输出结果。

    之后是输出层,对应的结果就是我们需要让机器掌握的分类。

    如果只看最后两层,你会很容易把它跟之前学过的深度神经网络(Deep Neural Network, DNN)联系起来。

    0?wx_fmt=png

    既然我们已经有了深度神经网络,为什么还要如此费力去使用卷积层和采样层,导致模型如此复杂呢?

    这里出于两个考虑:

    首先是计算量。图片数据的输入量一般比较大,如果我们直接用若干深度神经层将其连接到输出层,则每一层的输入输出数量都很庞大,总计算量是难以想像的。

    其次是模式特征的抓取。即便是使用非常庞大的计算量,深度神经网络对于图片模式的识别效果也未必尽如人意。因为它学习了太多噪声。而卷积层和采样层的引入,可以有效过滤掉噪声,突出图片中的模式对训练结果的影响。

    你可能会想,咱们只编写了10几行代码而已,使用的卷积神经网络一定跟上图差不多,只有4、5层的样子吧?

    不是这样的,你用的层数,有足足50层呢!

    它的学名,叫做Resnet-50,是微软的研发成果,曾经在2015年,赢得过ILSRVC比赛。在ImageNet数据集上,它的分类辨识效果,已经超越人类

    我把对应论文的地址附在这里,如果你有兴趣,可以参考。

    0?wx_fmt=jpeg

    请看上图中最下面的那一个,就是它的大略样子。

    足够深度,足够复杂吧。

    如果你之前对深度神经网络有一些了解,一定会更加觉得不可思议。这么多层,这么少的训练数据量,怎么能获得如此好的测试结果呢?而如果要获得好的训练效果,大量图片的训练过程,岂不是应该花很长时间吗?

    没错,如果你自己从头搭建一个Resnet-50,并且在ImageNet数据集上做训练,那么即便你有很好的硬件设备(GPU),也需要很长时间。

    如果你在自己的笔记本上训练……算了吧。

    那么,TuriCreate难道真的是个奇迹?既不需要花费长时间训练,又只需要小样本,就能获得高水平的分类效果?

    不,数据科学里没有什么奇迹。

    到底是什么原因导致这种看似神奇的效果呢?这个问题留作思考题,请善用搜索引擎和问答网站,来帮助自己寻找答案。

    小结

    通过本文,你已掌握了以下内容:

    • 如何在Anaconda虚拟环境下,安装苹果公司的机器学习框架TuriCreate。

    • 如何在TuriCreate中读入文件夹中的图片数据。并且利用文件夹的名称,给图片打上标记。

    • 如何在TuriCreate中训练深度神经网络,以分辨图片。

    • 如何利用测试数据集,检验图片分类的效果。并且找出分类错误的图片。

    • 卷积神经网络(Convolutional Neural Network, CNN)的基本构成和工作原理。

    但是由于篇幅所限,我们没有提及或深入解释以下问题:

    • 如何批量获取训练与测试图片数据。

    • 如何利用预处理功能,转换TuriCreate不能识别的图片格式。

    • 如何从头搭建一个卷积神经网络(Convolutional Neural Network, CNN),对于模型的层次和参数做到完全掌控。

    • 如何既不需要花费长时间训练,又只需要小样本,就能获得高水平的分类效果(提示关键词:迁移学习,transfer learning)。

    请你在实践中,思考上述问题。欢迎留言和发送邮件,与我交流你的思考所得。

    讨论

    你之前做过图片分类任务吗?你是如何处理的?用到了哪些好用的工具?比起咱们的方法,有什么优缺点?欢迎留言,把你的经验和思考分享给大家,我们一起交流讨论。

    如果你对我的文章感兴趣,欢迎点赞,并且微信关注和置顶我的公众号“玉树芝兰”(nkwangshuyi)。

    如果本文可能对你身边的亲友有帮助,也欢迎你把本文通过微博或朋友圈分享给他们。让他们一起参与到我们的讨论中来。


    如果喜欢我的文章,请微信扫描下方二维码,关注并置顶我的公众号“玉树芝兰”。

    0?wx_fmt=jpeg

    如果你希望支持我继续输出更多的优质内容,欢迎微信识别下方的赞赏码,打赏本文。感谢支持!

    0?wx_fmt=png

    欢迎微信扫码加入我的“知识星球”圈子。第一时间分享给你我的发现和思考,优先解答你的疑问。

    0?wx_fmt=jpeg


    展开全文
  • 今天为大家带来的是篇关于图像Gamma校正的文章 1. 关于Gamma(伽马) 1.1 什么是Gamma 1.2 什么是Gamma校正 1.3 为什么需要Gamma校正 2. 如何实现Gamma校正

    今天为大家带来的是一篇关于图像自动Gamma校正的文章,主要是利用了html5的canvas做图像处理。一般处理Gamma校正都是通过用户手动调节Gamma值来完成的,自动Gamma校正则是通过一系列的计算得出理想的Gamma值后再做处理。网上关于JavaScript做Gamma校正的文章很少,而自动Gamma校正的文章可以说是几乎找不到,这篇文章里图像自动Gamma校正的相关内容,是我通过查阅有限的资料实现的,所以还是挺有成就感的。今天分享给大家,如果文中有不正确的地方,欢迎大家指出来;有更好的想法也欢迎大家提出来,我们一起探讨学习。感兴趣的话可到文末的链接下载源码,程序的效果如下:


    忽略gif图片画质过差的问题,你会发现自动Gamma校正后,图片的整体亮度提高了,石头的细节部分也能看到了。因为这里选用的图片原本是比较暗的,所以最后图片变亮了,如果选用的图片过亮,经过自动Gamma校正后,图片则会变暗。


    1. 关于Gamma(伽马)
    1.1 什么是Gamma

    Gamma(伽马)是图像处理和视频制作中常用的技术用语。它定义了一个像素的数值和它的实际亮度之间的关系。从数学的角度来说,Gamma是用来改变某些输入值的“幂指数”。可以用如下简短的公式进行说明。


    下面分别以2,1和0.5的Gamma值为例来简单说明这个公式


    根据函数图,Gamma与输入、输出值间的关系就一目了然了,简单总结如下:
    ①当Gamma值比1大时,在输入值相同的情况下,输出值减小;
    ②当Gamma值为1时,输出值不变;
    ③当Gamma值比1小时,在输入值相同的情况下,输出值增加。

    1.2 什么是Gamma校正
    我是以前使用Photoshop时,才第一次接触到伽马校正(Gamma Correction),但当时并不知道Gamma校正的含义,现在通过一系列调查,才对它有了一个清晰的认识。简而言之,Gamma校正是对动态范围内亮度的非线性存储/还原算法,即输入值进行的非线性操作,使输出值与输入值呈指数关系;从效果上来说Gamma校正调整图像的整体亮度,没有校正的图像看起来可能会存在过亮或太暗的情况,所以想要图像显示效果更完美,Gamma校正就显得很重要了。

    使用Photoshop做Gamma校正时,如果设置的Gamma比1大,输出值会增加,即图片会变亮;而如果设置的Gamma值比1小时,输出值会减小,即图片会变暗。似乎不符合我们上一节针对公式所做的总结,刚好是相反的,这是因为PhotoShop是以Gamma的倒数作为输入值的幂,来计算输出值,如下所示:


    我们后面的实现也是基于这个公式。

    1.3 为什么需要Gamma校正
    主要的原因有两个方面:

    ① 人类对于外界刺激变化程度的感受,不是线性的,是指数形式的,如下图所示:


    也就是亮度相同的几盏灯,人类对于1盏灯→2盏灯产生的亮度变化感受,与2盏灯→4盏灯的变化感受是相同的。

    而且人类对于较暗的细节更加敏感,光越亮我们对于亮度的变化就不是那么敏感了。所以通过Gamma校正,可以尽可能的保留暗的部分的细节,以满足人眼对暗部敏感的需求。

    ② 图片文件的色阶很有限,24位色图片每个通道只有2^8个色阶,总共只能显示2^24种颜色;而且亮度级别过于庞大,如果按照线性方案进行存储,那么存储/传输的代价就会变得很昂贵。


    2. 基本运用

    调整亮度,以及灰度图像中的Gamma校正,使用上一节介绍的指数函数把每个像素的RGB值进行变换。具体执行下列转换公式(假定像素值的取值范围为0到255):


    使用之前提到的输入值的倒数幂,来完成Gamma校正。


    3. 如何实现Gamma校正
    利用 canvas = document.createElement('canvas'); 创建出canvas 元素;
    再通过 canvas.getContext('2d')方法得到CanvasRenderingContext2D 对象,这个对象实现了画布绘制所使用的大多数方法;
    然后调用 CanvasRenderingContext2D 对象的drawImage() 方法将图片绘制到canvas 上;
    之后使用 CanvasRenderingContext2D 对象的getImageData() 方法得到ImageData 对象,该对象拷贝了画布指定矩形的像素数据,即该对象中的每个像素,都保存着 RGBA 值,也就是红、绿、蓝以及 alpha 通道,这些数据(color/alpha信息)以数组形式存在,并存储于 ImageData对象的data 属性中;
    对每个像素的RGB值进行Gamma校正,调用的是我们自己实现的resetPixelColor() 方法;
    最后,调用 CanvasRenderingContext2D 对象的putImageData()方法将图像数据拷贝回画布上,这时 canvas 上的数据就是经过Gamma校正后的了,如果要得到图片数据,再通过调用canvas.toDataURL('image/png') 方法就可以得到Base64的图片数据。

    下面把Gamma校正的代码贴出来:

    function adjustImageGamma(img) {
    	image = img;
    	canvas = document.createElement('canvas');
    	canvas.id = img.id;
    	canvas.className = img.className;
    	//canvas.width = img.naturalWidth;
    	canvas.width = img.width;
    	//canvas.height = img.naturalHeight;
    	canvas.height = img.height;
    	
    	ctx = canvas.getContext('2d');
    	//ctx.drawImage(img, 0, 0);
    	ctx.drawImage(img, 0, 0, img.width, img.height);
    	
    	var image_parentNode = img.parentNode;
    	image_parentNode.replaceChild(canvas, img);
    		
    	//imageData = ctx.getImageData(0, 0, img.naturalWidth, img.naturalHeight);
    	imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    	
    	var gammaVal = getGammaVal();
    	gammaCorrection = 1 / gammaVal;
    	
    	setTimeout(function() {
    		//for ( y = 0; y < image.naturalHeight; y++) {
    		for ( y = 0; y < canvas.height; y++) {
    			//for ( x = 0; x < image.naturalWidth; x++) {
    			for ( x = 0; x < canvas.width; x++) {
    				var index = parseInt(x + canvas.width * y) * 4;
    				resetPixelColor(index);
    			}
    		}
    		ctx.putImageData(imageData, 0, 0);
    		//var dataURL = canvas.toDataURL('image/png');
    	}, 0);
    }
    
    function resetPixelColor(index) {
    	imageData.data[index + 0] = Math.pow(
    		(imageData.data[index + 0] / 255), gammaCorrection) * 255;
    	imageData.data[index + 1] = Math.pow(
    		(imageData.data[index + 1] / 255), gammaCorrection) * 255;
    	imageData.data[index + 2] = Math.pow(
    		(imageData.data[index + 2] / 255), gammaCorrection) * 255;
    }

    4. 如何实现自动Gamma校正

    上一节的方法中调用过 getGammaVal() 方法来计算出gamma值,这节就具体介绍下这个值的计算过程:
    将图片的像素数据转变为非RGB的数据来计算均值,我们这里是将其转变为灰度数据。之所以要转变成非RGB的的色彩模式,是因为如果使用RGB的数据来计算均值,可能会造成色彩平衡偏移。
    将计算出来的均值带入 gammaVal = log(mean/255)/log(midrange) 这个公式中,就可以得到Gamma值了。
    总的来说,计算Gamma值的过程并不复杂,但之前在调查这部分内容花费了我挺多时间的,因为网上的资料实在是太少了。好了,接下来就把代码贴出来:

    function getGammaVal() {
    	var pixData = imageData.data;
    	var grayNum = pixData.length / 4;
    	var totalGrayVal = 0;
    	for (var i = 0; i < pixData.length; i += 4) {
    		/* RGB to Luma: 
    		 * http://stackoverflow.com/questions/37159358/save-canvas-in-grayscale */
    		//var grayscale = pix[i] * 0.2126 + pix[i+1] * 0.7152 + pix[i+2] * 0.0722;
    		var grayscale = (pixData[i] + pixData[i+1] + pixData[i+2]) / 3;
    		totalGrayVal = totalGrayVal + grayscale;
    	}
    	var mean = totalGrayVal / grayNum;
    	var gammaVal = Math.log10(mean/255) / Math.log10(0.5);
    	return gammaVal;
    }

    需要注意的问题:

    之前用火狐浏览器运行程序没报错,但是在谷歌的Chrome浏览器运行程序的时候,报了一个错 auto_gamma.js:31 Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.这是为了阻止欺骗,浏览器会追踪 imageData。当你把一个跟canvas的域不同的图片放到canvas上,这个canvas就成为 “tainted”(被污染的,脏的),浏览器就不让你操作该canvas 的任何像素。这对于阻止多种类型的XSS/CSRF攻击(两种典型的跨站攻击)是非常有用的。

    解决方案:

    https://github.com/mrdoob/three.js/wiki/How-to-run-things-locally我参考的这个链接里的内容,将程序运行于Server上,使图片和canvas处于同一个域,因为刚好安装了node.js的环境,所以我就直接 npm install http-server,然后再启动 http-server 加载html就可以了。


    程序源代码:

    基于H5/JS实现图像自动Gamma校正

    展开全文
  • python实现将深度学习应用于医学图像以辅助医疗

    千次阅读 多人点赞 2018-04-21 14:54:32
    转自fc013,仅做学习问题导读:1.怎样利用python处理图像?2.什么是卷积?3.什么是Keras? 运用深度学习技术进行图像和视频分析,并将它们用于自动驾驶汽车、无人机等多种应用场景中已成为研究前沿。近期诸如《A ...
  • 图像镶嵌

    千次阅读 2006-06-24 11:42:00
    图像镶嵌目标程序的基本目标是将一系列的平坦图像连接起来一个连续的全景图像 (镶嵌图像).这里的困难是把一些弯曲(变形)的图像组合起来去适合不同的观察角度. 刚开始的做法通常非常不严密并且在二个图像之间出现了...
  • 图像金字塔最初用于机器视觉和图像压缩,一幅图像的金字塔是一系列以金字塔形状排列的分辨率逐步降低,且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到达到某个终止条件才停止采样。 金字塔的底部是...
  • 图像拼接

    万次阅读 2017-02-13 11:18:33
    图像拼接的基本流程 ...其基本思想是:首先找到待配准图像与参考图像的模板或特征点的对应位置,然后根据对应关系建立参考图像与待配准图像之间的转换数学模型,将待配准图像转换到参考图像
  • 机器学习系列(3)_逻辑回归应用之Kaggle泰坦尼克之灾

    万次阅读 多人点赞 2015-11-12 12:07:12
    引言先说句,年末双十一什么的一来,真是非(mang)常(cheng)欢(gou)乐(le)!然后push自己抽出时间来写这篇blog的原因也非常简单: 写完前两篇逻辑回归的介绍和各个角度理解之后,小伙伴们纷纷表示『好像很高级的样...
  • 重拾图形图像处理 ---- 笔试题

    千次阅读 2020-11-30 17:00:38
    2、存储幅大小为1024*1024,256个灰度级的图像,需要8M bit。 3、亮度鉴别实验表明,韦伯比越大,则亮度鉴别能力越差 。 4、直方图均衡化适用于增强直方图呈尖峰 分布的图像。 5、依据图像的保真度,图像压缩可...
  • 图形图像库集合

    千次阅读 2014-08-10 15:14:42
    图像处理类库 CImg 2D图形库 AGG Google 图形处理引擎 skia 三维图形渲染引擎 OGRE 开源图形库 FreeImage 3D引擎 Irrlicht Engine 移动设备上的OpenGL OpenGL ES 高质量图形图表库 MathGL 开源图形库 ...
  • 个经历了 39 场 Kaggle 比赛的团队在 reddit 上发帖表示,他们整理了份结构化的图像分割技巧列表,涵盖数据增强、建模、损失函数、训练技巧等多个方面,不失为份可以参考的图像分割技巧资料。 图像分割是图像...
  • 基于FPGA的图像边缘检测系统 参考文献:手把手教你学FPGA设计:基于大道至简的至简设计法 基于VIP_Board Big的FPGA入门进阶及图像处理算法开发教程-V3.0
  • 数字图像处理-第十章和第十

    千次阅读 2017-08-31 00:56:00
    *****学习了第10章的后半部分和第11章***** plot x=0:pi/100:2*pi; y=2exp(-0.5x).sin(2pi*x);...程序执行后,打开个图形窗口,在...这是参数形式给出的曲线方程,只要给定参数向量,再分别求出x,y向量即可输...
  • 原文: ... 倒是觉得此文中对于圆识别的各种约束条件,挺有用。   Opencv中提供了SimpleBlobDetector的特征点检测方法,正如它的名称,该算法使用...首先通过一系列连续的阈值把输入的灰度图像转换为一个二值图像的...
  • 图像分割阈值选取技术综述

    万次阅读 2006-02-27 09:28:00
    图像分割阈值选取技术综述中科院成都计算所 刘平 2004-2-26摘要 图像分割是图像处理与计算机视觉领域低层次视觉中最为基础和重要的领域之,它是对图像进行视觉分析和模式识别的基本前提.阈值法是种传统的...
  • Tensorflow手把手实现第个CNN

    千次阅读 2017-09-22 14:05:04
    """转换图像数据格式时需要将它们的颜色空间变为灰度空间,将图像尺寸修改为同一尺寸,并将标签依附于每幅图像""" import tensorflow as tf sess = tf.Session() import glob image_filenames = glob.glob("./...
  • 图像拼接算法及实现

    万次阅读 多人点赞 2014-10-30 17:09:56
    图像拼接解决的问题一般式,通过对齐一系列空间重叠的图像,构成一个无缝的、高清晰的图像,它具有比单个图像更高的分辨率和更大的视野。  早期的图像拼接研究一直用于照相绘图学,主要是对大量
  • 数字图像处理第八章——图像压缩

    万次阅读 2019-05-09 03:33:04
    数字图像处理第八章数字图像处理---图像压缩() 背景(二) 编码冗余2.1 霍夫曼码2.2 霍夫曼编码2.3 霍夫曼译码(三)空间冗余(四)不相关的信息(五)JPEG 压缩5.1 JPEG5.2 JPEG 2000(六)视频压缩6.1 MATLAB...
  • 基于FPGA的图像处理()--System Generator介绍

    万次阅读 多人点赞 2012-11-03 17:21:44
    此外,System Generator是Xilinx公司XtremeDSP解决方案的关键组成,集成了先进的FPGA设计工具以及IP 核,支持Xilinx公司全系列的FPGA芯片,提供从初始算法验证到硬件设计的通道。System Generator最大的特点就是可...
  • 频域图像增强算法

    万次阅读 2020-10-29 01:27:11
    图像传递系统包括图像采集、图像压缩、图像编码、图像存储、图像通信、图像显示这六个部分。在实际应用中每个部分都有可能导致图像品质变差,使图像传递的信息无法被正常读取和识别。例如,在采集图像过程中由于光照...
  • 图像处理(5)--图像的傅里叶变换

    万次阅读 多人点赞 2019-07-06 10:42:52
    傅里叶变换及其反变换1.1 为什么要在频率域研究图像增强?1.2 傅里叶变换 1. 傅里叶变换及其反变换 1.1 为什么要在频率域研究图像增强? 可以利用频率成分和图像外表之间的对应关系。一些在空间域表述困难的增强...
  • 原始图像数据

    千次阅读 2014-09-04 09:14:34
    位图:图像数据都是由位图(一系列表示开启和关闭像素值的0和1)表示
  • 一幅图像的金字塔式一系列以金字塔形状排列的,分辨率逐步降低且来源于同一张原始图的图像集合。其通过梯次向下采样获得,直到到达某个终止条件才停止采样。金字塔的底部是待处理图像的高分辨率表示,而顶部是低...
  • 基于多种原因,决策树是种广受欢迎的监督学习方法。决策树的优点包括可以同时用于回归和分类,易于解释且不需要特征缩放。同时决策树也存在劣势,容易出现过度拟合就是其中之。本教程主要介绍了用于分类的决策树...
  • 实验内容 实验: 自选图像,并对其分别添加一定强度的周期噪声和高斯噪声,然后...1、编写个程序,要求实现下列算法:首先将图像分割为8×8的子图像,对每个子图像进行FFT,对每个子图像中的64个系数,按...
  • 指纹图像预处理

    万次阅读 多人点赞 2020-10-16 16:54:31
    指纹图像预处理是指纹识别的前提,它的好坏直接影响到指纹识别的成败,但由于指纹图像降质带来的困难,并根据指纹图像的特征提出了合理的假设,再根据假设提出了增强指纹图像对比度的算法、提取指纹有效区域的算法、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,892
精华内容 6,356
关键字:

下列用以显示一系列图像的是