<?xml version="1.0" encoding="utf-8"?>
<search>
  <entry>
    <title><![CDATA[Dockers for windows]]></title>
    <url>%2F%2F2018%2Fnormal_05.html</url>
    <content type="text"><![CDATA[Dockers for windows 安装dockersDocker是一种容器技术，可以将应用和环境等进行打包，形成一个独立的、类似于iOS的App形式的“应用”。这个应用可以直接被分发到任意一个支持Docker的环境中，通过简单的命令即可启动运行。Docker是一种最流行的容器化实现方案，和虚拟化技术类似，它极大地方便了应用服务的部署；又与虚拟化技术不同，它以一种更轻量的方式实现了应用服务的打包。使用Docker，可以让每个应用彼此相互隔离，在同一台机器上同时运行多个应用，不过它们彼此之间共享同一个操作系统。Docker的优势在于，它可以在更细的粒度上进行资源管理，也比虚拟化技术更加节约资源。 如果你的系统是Windows 10 64位，那么推荐使用Docker for Windows。此时直接从Docker官方网站下载最新的Docker for Windows 安装包即可： Docker for windows 如果不是Windows 10 64位系统，则可以下载Docker Toolbox： Docker Toolbox 不推荐使用 docker toolbox，建议使用新的 docker for mac 及 docker for windows 需要注意的是，Windows上安装Docker对系统有以下的要求： 需要支持Hyper-V的windows版本，Hyper-V目前仅在Windows 10之后的版本支持 BIOS里需要启用Virtualization（虚拟化） 安装完成之后，运行我们的dockers。 测试安装的docker，在docker终端输入命令： C:\Users\Leowen&gt;docker --version Docker version 18.03.1-ce, build 9ee9f40 安装好Docker之后，为了为了提高镜像的下载速度，我们使用国内镜像来加速下载，于是就有了Docker加速器一说。 推荐的Docker加速器有DaoCloud（详见https://www.daocloud.io/mirror）和阿里云（详见https://cr.console.aliyun.com/#/accelerator） 不同平台的镜像加速方法配置可以参考DaoCloud的官方文档：http://guide.daocloud.io/dcs/daocloud-9153151.html 以 Docker for windows为例，点击加速器 然后获取Windows加速器地址 配置dockers for Windows 接下来，下载我们需要的镜像。 更多的参考：Dockers command 注册dockers Windows上做Python开发太痛苦？Docker了解一下！]]></content>
      <categories>
        <category>平时</category>
      </categories>
      <tags>
        <tag>python</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[创建python虚拟环境]]></title>
    <url>%2F%2F2018%2F12%2F03%2F%E5%88%9B%E5%BB%BApython%E8%99%9A%E6%8B%9F%E7%8E%AF%E5%A2%83.html</url>
    <content type="text"><![CDATA[创建python虚拟环境 为什么需要虚拟环境在实际项目开发中，我们通常会根据自己的需求去下载各种相应的框架库，如Scrapy、Beautiful Soup等，但是可能每个项目使用的框架库并不一样，或使用框架的版本不一样，这样需要我们根据需求不断的更新或卸载相应的库。直接怼我们的Python环境操作会让我们的开发环境和项目造成很多不必要的麻烦，管理也相当混乱。因此我们需要使用python虚拟环境。 安装python这一步就略过了。建议安装anaconda。 安装virtualenv在cmd命令行窗口中，我们可以通过pip命令简单的实现安装： 可以看到，我前面其实是已经安装好了的。 创建虚拟环境安装完成之后，我们可以通过以下命令创建我们的虚拟环境 激活虚拟环境cmd中，定位到myblog/scripts中，执行activate.bat(或者执行myblog/Scripts/activate) 激活成功后，命令行前面会有(myblog)字样，如下 取消激活 上面其实是完成了我们虚拟环境的创建，但是有木有觉得很麻烦。使用virtualenv，需要进入相对应的路径进行操作，接下来我们可以通过使用virtualwrapper来简化对虚拟环境的操作。 注意：Virtualenvwrapper的使用(virtualenvwrapper-win依赖于virtualenv，所以也要安装virtualenv) 在cmd中，执行 设置WORK_HOME环境变量WORK_HOME环境变量是通过virtualenvwrapper建立虚拟环境时，该虚拟环境的所在目录。(之所以设置WORKON_HOME环境变量是虚拟环境会自动找到该环境变量的目录，例如：注：因为前一步设置了WORK_HOME，所有虚拟环境将安装到 D:\VirEnv)设置环境变量 接下来我们使用virtualenvwrapper新建虚拟环境 新建命令为: mkvirtualenv 虚拟环境名称 查看安装的所有虚拟环境 进入(切换)虚拟环境 退出虚拟环境 更多的参考：Django开发个人博客网站——2、通过virtualenv与virtualenvwrapper创建虚拟环境 Python为什么要使用虚拟环境-Python虚拟环境的安装和配置-virtualenv python虚拟环境–virtualenv]]></content>
      <categories>
        <category>平时</category>
      </categories>
      <tags>
        <tag>python</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[python的那些小技巧]]></title>
    <url>%2F%2F2018%2F12%2F02%2Fpython%E7%9A%84%E9%82%A3%E4%BA%9B%E5%B0%8F%E6%8A%80%E5%B7%A7.html</url>
    <content type="text"><![CDATA[python常用的小技巧 1、Unpacking(拆箱)&gt;&gt;&gt; a,b,c = 1,2,3 &gt;&gt;&gt; a,b,c (1, 2, 3) &gt;&gt;&gt; a,b,c = [1,2,3] &gt;&gt;&gt; a,b,c (1, 2, 3) &gt;&gt;&gt; a,b,c = (2 * i + 1 for i in range(3)) &gt;&gt;&gt; a,b,c (1, 3, 5) &gt;&gt;&gt; a,(b,c),d = [1,(2,3),4] &gt;&gt;&gt; a,b,c,d (1, 2, 3, 4) 更多参考： python3特性一：高级拆箱 Packing and Unpacking Arguments in Python 有没有想过你在Python函数中看到的*args和**kwargs是什么意思？ *和**运算符都根据它们的使用位置执行两种不同但互补的操作。在方法定义中使用时，如下所示： def __init__(self, *args, **kwargs): pass 他们执行一项名为“packing(打包)”的操作。它的作用是将这个方法调用的所有参数打包(pack)成一个单独的变量，一个名为args的元组。当然，你可以使用你想要的任何变量名，但是args似乎是最常见和更加Pythonic way。 一旦你有了这个’packed’变量，就可以使用普通元组(normal tuple)来做一些事情。比如args[0]和args[1]分别会给你第一个和第二个参数。如果将args元组转换为列表，您还可以修改，删除和重新排列其中的items。 那么如何将这些packed的参数传递给另一个方法呢？这就是我们的unpacking了： def __init__(self, *args, **kwargs): # you can do something super(AwesomeClass, self).__init__(self, *args, **kwargs) # ^ # LOOK HERE! 所以再次使用相同的*运算符，但这次是在方法调用的前后中。它现在所做的就是分解args数组并调用该方法，就像您已经分别输入每个变量一样。 请看下面： def func1(x, y, z): print x print y print z def func2(*args): # Convert args tuple to a list so we can modify it args = list(args) args[0] = &apos;Hello&apos; args[1] = &apos;awesome&apos; func1(*args) func2(&apos;Goodbye&apos;, &apos;cruel&apos;, &apos;world!&apos;) # Will print # &gt; Hello # &gt; awesome # &gt; world! 之所以会出现上面的结果，是因为我们在将它们传递给func1之前更改了前两个参数。 管理方法定义的常规规则适用于此……调用func2(&#39;a&#39;、&#39;b&#39;、&#39;c&#39;、&#39;d&#39;)将引发一个错误，因为它将调用func1，并带有四个参数，这是func1意料不到的。 同样的原则也适用于**kwargs，除了在这种情况下它适用于关键字参数，并且kwargs结果是dict。 结合packing和unpacking可以让你做很多事情，比如： 在传递参数之前验证它们 设置位置参数的默认值 为不同的代码/库创建适配器 等等 2、Unpacking for swapping variables(拆箱变量交换)&gt;&gt;&gt; a,b = 1,2 &gt;&gt;&gt; a,b = b,a &gt;&gt;&gt; a,b (2, 1) 3、Extended unpacking(Python 3 only)(扩展拆箱)&gt;&gt;&gt; a,*b,c = [1,2,3,4] &gt;&gt;&gt; a 1 &gt;&gt;&gt; b [2, 3] &gt;&gt;&gt; c 4 4、Negative indexing(负数索引)(从-1开始，-1表示最后一个) &gt;&gt;&gt; a = [0,1,2,3,4,5,6,7,8,9] &gt;&gt;&gt; a[-1] 9 &gt;&gt;&gt; a[-3] 7 5、List slices(a[start:end])(切割列表)(不包括end那个点，从0开始索引) &gt;&gt;&gt; a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] &gt;&gt;&gt; a[1:3] [1, 2] 6、List slices with negative indexing(负数索引切割列表)&gt;&gt;&gt; a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] &gt;&gt;&gt; a[-4:-2] [6, 7] 7、List slices with step (a[start:end:step])(指定步长切割列表)[::]表示取所有行所有列 &gt;&gt;&gt; a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] &gt;&gt;&gt; a[::2] [0, 2, 4, 6, 8] &gt;&gt;&gt; a[::3] [0, 3, 6, 9] &gt;&gt;&gt; a[2:8:2] [2, 4, 6] 8、List slices with negative step(负数步长切割列表)&gt;&gt;&gt; a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] &gt;&gt;&gt; a[::-1] [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] &gt;&gt;&gt; a[::-2] [9, 7, 5, 3, 1] 9、List slice assignment(列表切割赋值)&gt;&gt;&gt; a = [1,2,3,4,5] &gt;&gt;&gt; a[2:3] [3] #给这个位置赋值一个列表进去 &gt;&gt;&gt; a[2:3] = [0,0] &gt;&gt;&gt; a [1, 2, 0, 0, 4, 5] &gt;&gt;&gt; a[1:1] [] &gt;&gt;&gt; a[1:1] = [6,7] &gt;&gt;&gt; a [1, 6, 7, 2, 0, 0, 4, 5] &gt;&gt;&gt; a[1:-1] [6, 7, 2, 0, 0, 4] &gt;&gt;&gt; a[1:-1] = [] &gt;&gt;&gt; a [1, 5] 10、Naming slices (slice(start, end, step))(命名列表切割方式)&gt;&gt;&gt; a = [0,1,2,3,4,5] &gt;&gt;&gt; lastThree = slice(-3,None) &gt;&gt;&gt; lastThree slice(-3, None, None) &gt;&gt;&gt; a[lastThree] [3, 4, 5] 11、Iterating over list index and value pairs (enumerate)&gt;&gt;&gt; a = [&quot;hello&quot;,&quot;world&quot;,&quot;!&quot;] &gt;&gt;&gt; for (i,x) in enumerate(a): ... print(&quot;{}:{}&quot;.format(i,x)) ... 0:hello 1:world 2:! 12、Iterating over dictionary key and value pairs (dict.iteritems)&gt;&gt;&gt; m = {&apos;a&apos;:1,&apos;b&apos;:2,&apos;c&apos;:3,&apos;d&apos;:4} &gt;&gt;&gt; for (k, v) in m.items(): ... print(&quot;{} : {}&quot;.format(k,v)) ... a : 1 b : 2 c : 3 d : 4 注意：在python3.x中，iteritems方法已经被废除了，使用items方法替代。 Python字典中items()和iteritems()区别 13、Zipping and unzipping lists and iterables(列表以及迭代器的压缩和解压缩)&gt;&gt;&gt; a = [1,2,3] &gt;&gt;&gt; m = [&apos;a&apos;,&apos;b&apos;,&apos;c&apos;] &gt;&gt;&gt; zipped = zip(a,b) #返回一个对象 &gt;&gt;&gt; zipped &lt;zip object at 0x0000023116597D08&gt; &gt;&gt;&gt; list(zipped) #list()转换为列表 [(1, &apos;a&apos;), (2, &apos;b&apos;), (3, &apos;c&apos;)] &gt;&gt;&gt; a1,a2 = zip(*zipped) # 与 zip 相反，zip(*) 可理解为解压，返回二维矩阵 &gt;&gt;&gt; a1 (1, 2, 3) &gt;&gt;&gt; a2 (&apos;a&apos;, &apos;b&apos;, &apos;c&apos;) Python3 zip() 函数 14、Grouping adjacent list items using zip(列表相邻元素压缩器)&gt;&gt;&gt; a = [1,2,3,4,5,6] &gt;&gt;&gt; # Using iterators &gt;&gt;&gt; groupAdjacent = lambda a,k : zip(*([iter(a)] * k)) &gt;&gt;&gt; groupAdjacent(a,3) &lt;zip object at 0x0000023116599F88&gt; &gt;&gt;&gt; list(groupAdjacent(a,3)) [(1, 2, 3), (4, 5, 6)] &gt;&gt;&gt; list(groupAdjacent(a,2)) [(1, 2), (3, 4), (5, 6)] &gt;&gt;&gt; list(groupAdjacent(a,1)) [(1,), (2,), (3,), (4,), (5,), (6,)] 上面代码的理解： 首先关于python的迭代器。iter()能把一个序列生成为一个迭代器，迭代器的特点是可以用for in语句迭代，原理是迭代器对象有一个next方法，可以每次移动迭代的指针，一旦迭代完，没有下一个元素的时候，会应发一个StopIteration异常。 在我们迭代了一次之后，指针就移动了，不会自动回溯。比如： &gt;&gt;&gt; a = [1,2,3] &gt;&gt;&gt; for m in a: ... print(m) ... 1 2 3 &gt;&gt;&gt; x = iter(a) &gt;&gt;&gt; for i in x: ... print(i) ... 1 2 3 &gt;&gt;&gt; for i in x: ... print(x) # x 已经被迭代过了，是迭代的指针没有回去，理解为被清空了。 ... 我们可以用for in循环列表a无数次，但是对于x，我们只能for in一次，因为迭代指针到达了迭代器的尾部。 第二个就是关于zip函数，它可以将两个序列对应着打包，而我们提过关于*的用法，它是python函数可变参数的一种表示方式，加入*表示传入一个元组对象进行解包。 最后是lambda函数， lambda express: express 返回一个可以调用的对象，可以理解为函数 double = lambda x : return x * 2 上面等价于 def double(x): retrun x * 2 所以关于 group_adjacent = lambda a, k: zip(*([iter(a)] * k)) 我们首先看(k取3)： &gt;&gt;&gt;[iter(x)]*3 [&lt;list_iterator object at 0x0000026899671D68&gt;, &lt;list_iterator object at 0x0000026899671D68&gt;, &lt;list_iterator object at 0x0000026899671D68&gt;] 可以看到，列表中的3个迭代器实际上是同一个迭代器！！！ 怎么解释呢？首先看下面的一段代码： &gt;&gt;&gt; a = [1,2,3,4,5,6] &gt;&gt;&gt; x = iter(a) &gt;&gt;&gt; t = [a,a] &gt;&gt;&gt; t [[1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6]] &gt;&gt;&gt; zip(*t) #这是一 &lt;zip object at 0x0000026899677888&gt; &gt;&gt;&gt; list(zip(*t)) [(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)] &gt;&gt;&gt; tx = [x,x] &gt;&gt;&gt; list(zip(*tx)) #这是二 [(1, 2), (3, 4), (5, 6)] 上面的一：这里的含义表示zip传了两个参数a，a1，a2都是a，所以我们执行了打包操作，打包了这两个序列。 上面的二：这里因为x是迭代器对象，迭代就调用next方法。也就是 zip执行打包的过程先调用第一个参数的x的next方法得1，然后调用第二个参数的x的next，因为这两个x对象实际上是一样的，调用第二个x的next方法的时候，迭代的指针已经移动，实际得到的时2，以次类推，过程模拟如下表示 1 x.next -&gt; 1 2 x.next -&gt; 2 3 zip(x.next(), x.next()) ---&gt; zip(1, 2) 4 x.next -&gt; 3 5 x.next -&gt; 4 6 zip(x.next(), x.next()) ---&gt; zip(3, 4) .... 等价于下面的方式： zip([1, 3, 5], [2, 4, 6]) 所以上面的也就明了了。我们有三个一样的迭代器，也就是会打包成元组，每个元组包含3个元素，就是我们的结果了。 group_adjacent = lambda a, k: zip(*([iter(a)] * k)) 所以上面的代码表示：定义一个匿名函数，参数是 a和k，并绑定变量 group_adjacent。匿名函数的主体内容是，用iter将序列迭代化，然后用zip打包这个迭代器对象。 &gt;&gt;&gt; groupAdjacent = lambda a, k: zip(*(a[i::k] for i in range(k))) &gt;&gt;&gt; groupAdjacent(a,3) &lt;zip object at 0x0000026899679FC8&gt; &gt;&gt;&gt; list(groupAdjacent(a,3)) [(1, 2, 3), (4, 5, 6)] 首先关于a[i::k]。我们知道a[::k]，表示每k个值取一个，所以我们的a[i::k]就表示在我们划分的新的列表中，取第几个，比如： &gt;&gt;&gt; a = [1,2,3,4,5,6] &gt;&gt;&gt; a[0::3] #取第一个 [1, 4] &gt;&gt;&gt; a[1::3] #取第二个 [2, 5] &gt;&gt;&gt; a[2::3] #取第三个 [3, 6] &gt;&gt;&gt; &gt; 所以zip(*(a[i::k] for i in range(k)))的意思就很明了了。如果我们的k为3，那么我们便得到了([1,4],[2,5],[3,6])，然后用zip函数打包。 更多参考： python列表相邻元素压缩器 python 使用zip合并相邻的列表项 15、Sliding windows (nn -grams) using zip and iterators(在列表中用压缩器和迭代器滑动取值窗口)&gt;&gt;&gt; def n_grams(a, n): ... z = [iter(a[i:]) for i in range(n)] ... return zip(*z) ... &gt;&gt;&gt; n_grams(a,3) &lt;zip object at 0x000002689967C388&gt; &gt;&gt;&gt; list(n_grams(a,3)) [(1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6)] &gt;&gt;&gt; list(n_grams(a,2)) [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)] 16、Inverting a dictionary using zip(用压缩器反转字典)&gt;&gt;&gt; m = {&apos;a&apos;: 1, &apos;b&apos;: 2, &apos;c&apos;: 3, &apos;d&apos;: 4} &gt;&gt;&gt; m.items() dict_items([(&apos;a&apos;, 1), (&apos;b&apos;, 2), (&apos;c&apos;, 3), (&apos;d&apos;, 4)]) &gt;&gt;&gt; zip(m.values(),m.keys()) &lt;zip object at 0x000002689967C548&gt; &gt;&gt;&gt; list(zip(m.values(),m.keys())) [(1, &apos;a&apos;), (2, &apos;b&apos;), (3, &apos;c&apos;), (4, &apos;d&apos;)] &gt;&gt;&gt; mi = dict(zip(m.values(),m.keys())) &gt;&gt;&gt; mi {1: &apos;a&apos;, 2: &apos;b&apos;, 3: &apos;c&apos;, 4: &apos;d&apos;} 17、Flattening lists(列表展开)&gt;&gt;&gt;import itertools &gt;&gt;&gt; a = [[1, 2], [3, 4], [5, 6]] &gt;&gt;&gt; list(itertools.chain.from_iterable(a)) [1, 2, 3, 4, 5, 6] &gt;&gt;&gt; sum(a, []) [1, 2, 3, 4, 5, 6] &gt;&gt;&gt; [x for l in a for x in l] [1, 2, 3, 4, 5, 6] &gt;&gt;&gt; a = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] &gt;&gt;&gt; [x for l1 in a for l2 in l1 for x in l2] [1, 2, 3, 4, 5, 6, 7, 8] &gt;&gt;&gt; a = [1, 2, [3, 4], [[5, 6], [7, 8]]] &gt;&gt;&gt; flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x] &gt;&gt;&gt; flatten(a) [1, 2, 3, 4, 5, 6, 7, 8] 注意：根据Python关于sum的文档，itertools.chain.from_iterable是首选方法。 18、Generator expressions(生成器表达式)&gt;&gt;&gt; g = (x ** 2 for x in range(10)) &gt;&gt;&gt; g &lt;generator object &lt;genexpr&gt; at 0x00000268994B6150&gt; &gt;&gt;&gt; next(g) 0 &gt;&gt;&gt; next(g) 1 &gt;&gt;&gt; sum(x ** 2 for x in range(10)) 285 &gt;&gt;&gt; 19、Dictionary comprehensions(字典推导)&gt;&gt;&gt; m = {x: x ** 2 for x in range(5)} &gt;&gt;&gt; m {0: 0, 1: 1, 2: 4, 3: 9, 4: 16} &gt;&gt;&gt; m = {x: &apos;A&apos; + str(x) for x in range(10)} &gt;&gt;&gt; m {0: &apos;A0&apos;, 1: &apos;A1&apos;, 2: &apos;A2&apos;, 3: &apos;A3&apos;, 4: &apos;A4&apos;, 5: &apos;A5&apos;, 6: &apos;A6&apos;, 7: &apos;A7&apos;, 8: &apos;A8&apos;, 9: &apos;A9&apos;} 20、 Inverting a dictionary using a dictionary comprehension(用字典推导反转字典)&gt;&gt;&gt; m = {&apos;a&apos;: 1, &apos;b&apos;: 2, &apos;c&apos;: 3, &apos;d&apos;: 4} &gt;&gt;&gt; m {&apos;a&apos;: 1, &apos;b&apos;: 2, &apos;c&apos;: 3, &apos;d&apos;: 4} &gt;&gt;&gt; {v : k for k,v in m.items()} {1: &apos;a&apos;, 2: &apos;b&apos;, 3: &apos;c&apos;, 4: &apos;d&apos;} &gt;&gt;&gt; 21、Named tuples (collections.namedtuple)(命名元组)&gt;&gt;&gt; import collections &gt;&gt;&gt; Point = collections.namedtuple(&apos;Point&apos;,[&apos;x&apos;,&apos;y&apos;]) &gt;&gt;&gt; p = Point(x=1.0,y=2.0) &gt;&gt;&gt; p Point(x=1.0, y=2.0) &gt;&gt;&gt; p.x 1.0 &gt;&gt;&gt; p.y 2.0 22、Inheriting from named tuples(继承命名元组)import collections &gt;&gt;&gt; class Point(collections.namedtuple(&apos;PointBase&apos;, [&apos;x&apos;, &apos;y&apos;])): ... __slots__ = () ... def __add__(self, other): ... return Point(x=self.x + other.x, y=self.y + other.y) ... &gt;&gt;&gt; p = Point(x=1.0, y=2.0) &gt;&gt;&gt; q = Point(x=2.0, y=3.0) &gt;&gt;&gt; p + q Point(x=3.0, y=5.0) 23、Sets and set operations(操作集合)&gt;&gt;&gt; A = {1,2,3,3} &gt;&gt;&gt; A {1, 2, 3} &gt;&gt;&gt; B = {3,4,5,6,7} &gt;&gt;&gt; A | B {1, 2, 3, 4, 5, 6, 7} &gt;&gt;&gt; A &amp; B {3} &gt;&gt;&gt; A - B {1, 2} &gt;&gt;&gt; B - A {4, 5, 6, 7} &gt;&gt;&gt; A ^ B {1, 2, 4, 5, 6, 7} 小补充： 临时性变量名称： _ 作为临时性的名称使用。这样，当其他人阅读你的代码时将会知道，你分配了一个特定的名称，但是并不会在后面再次用到该名称。例如，下面的例子中，你可能对循环计数的实际值并不感兴趣，此时就可以使用_。 n = 3 &gt;&gt;&gt; for _ in range(n): ... print(&quot;hello world&quot;) ... hello world hello world hello world 更多的参考：30个有关Python的小技巧 30 Python Language Features and Tricks You May Not Know About 【变量】关于python中的下划线]]></content>
      <categories>
        <category>平时</category>
      </categories>
      <tags>
        <tag>python</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[计算机视觉环境配置]]></title>
    <url>%2F%2F2018%2F12%2F02%2F%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89%E7%8E%AF%E5%A2%83%E9%85%8D%E7%BD%AE.html</url>
    <content type="text"><![CDATA[PyCharm, virtual environments, and OpenCV 本文的首先将假设您已经在系统上安装了OpenCV和相应的Python绑定。同时假设你也安装了virtualenv和virtualenvwrapper(下一篇文章介绍)。 接下来我将演示如何用python3.x以及opencv 3.x来设置我的Windows系统。 Step1:Create your virtual environment我们要做的第一件事是设置我们的虚拟环境。打开终端并创建虚拟环境。在本例中，我们将虚拟环境命名为mycvlearn: mkvirtualenv mycvlearn 现在我们已经设置了虚拟环境，让我们安装NumPy，Scipy，matplotlib，scikit-learn和scikit-image，这些都是计算机视觉开发中常用的： pip install numpy pip install scipy pip install matplotlib pip install scikit-learn pip install -U scikit-image 如果安装不了，请移步下面这个网址下载whl进行安装。 http://www.lfd.uci.edu/~gohlke/pythonlibs/ Step 2: 安装opencv首先我们从下面这个网址下载python对应的opencv版本 http://www.lfd.uci.edu/~gohlke/pythonlibs/ 然后安装 Step 3: Configure PyCharm我们的虚拟环境已经设置好了，让我们将它连接到PyCharm(关于专业版激活看前面的文章)项目。 打开PyCharm并创建一个新的“Pure Python”项目： 首先，我们设置项目的路径，然后选择已经存在的python解释器。在大多数情况下，此位置将指向您的Python系统安装。但是，我们不想使用Python系统，我们想要使用属于mycvlearn虚拟环境的Python，因此请单击…图标来选择。 最终结果 测试： 更多的参考：The perfect computer vision environment: PyCharm, OpenCV, and Python virtual environments]]></content>
      <categories>
        <category>平时</category>
      </categories>
      <tags>
        <tag>环境配置</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[C++调用python函数]]></title>
    <url>%2F%2F2018%2F11%2F30%2FC%2B%2B%E8%B0%83%E7%94%A8python%E5%87%BD%E6%95%B0.html</url>
    <content type="text"><![CDATA[C++调用python函数(cr2转jpg) 最近帮朋友处理一个.CR2图片格式转jpg。因为他是用C++编程的，所以会涉及到C++调用python函数，下面给大家分享一下过程。 首先我们写一个CR2CVTJPG.py的文件 import numpy as np from PIL import Image from rawkit.raw import Raw from rawkit.options import WhiteBalance import os.path import glob def cr2cvtjpg(): basepath = os.path.dirname(os.path.abspath(r&apos;C:\Users\Leowen\Anaconda3\Lib\site-packages\LibRaw-0.18.13\bin\libraw.dll&apos;)) os.environ[&apos;PATH&apos;] = basepath +os.pathsep + os.environ[&apos;PATH&apos;] # 获取图片 imagePaths = glob.glob(&quot;C:\\Users\\Leowen\\Desktop\\image&quot;+&quot;\\*.CR2&quot;) # 循环处理单个图片 for (i,imagePath) in enumerate(imagePaths): with Raw(filename=imagePath) as raw: raw.options.white_balance = WhiteBalance(camera=False, auto=True) outputpath = &quot;C:\\Users\\Leowen\\Desktop\\&quot; +str(i) + &quot;.ppm&quot; raw.save(filename=outputpath) img = Image.open(outputpath) jpgoutputpath = &quot;C:\\Users\\Leowen\\Desktop\\&quot; +str(i) + &quot;.jpg&quot; img.save(jpgoutputpath) 这个python程序就不解释了，这是python的知识，我们这里主要将C++调用python函数。 我用anaconda3安装的python36。VS编辑器用的是VS2015. 首先我们新建一个项目 然后新建一个main.cpp文件。在我们属性管理器中，修改我们的项目的解决方案平台x64。 main.cpp内容为： #include &lt;Python.h&gt; #include &lt;iostream&gt; int main(int argc, char* argv[]) { // init python Py_SetPythonHome(L&quot;C:\\Users\\Leowen\\Anaconda3&quot;); Py_Initialize(); if (!Py_IsInitialized()) return -1; const char *scriptDirectoryName = &quot;C:\\Users\\Leowen\\Desktop\\20181130test\\ConsoleApplication1\\ConsoleApplication1&quot;; Py_Initialize(); PyObject *sysPath = PySys_GetObject(&quot;path&quot;); PyObject *path = PyUnicode_FromString(scriptDirectoryName); int result = PyList_Insert(sysPath, 0, path); PyObject *pModule = PyImport_ImportModule(&quot;CR2CVTJPG&quot;); // load python script if (!pModule) { std::cout &lt;&lt; &quot;can&apos;t find CR2CVTJPG.py&quot; &lt;&lt; std::endl; return -1; } // PyObject* pDict = PyModule_GetDict(pModule); if (!pDict) { return -1; } // get &quot;add&quot; function PyObject* pFunc = PyDict_GetItemString(pDict, &quot;cr2cvtjpg&quot;); if (!pFunc || !PyCallable_Check(pFunc)) { std::cout &lt;&lt; &quot;can&apos;t find function [cr2cvtjpg]&quot; &lt;&lt; std::endl; return -1; } // parameter //PyObject *pArgs = PyTuple_New(2); //两个参数 //PyTuple_SetItem(pArgs, 0, Py_BuildValue(&quot;l&quot;, 3)); //PyTuple_SetItem(pArgs, 1, Py_BuildValue(&quot;l&quot;, 4)); // call python script //PyObject_CallObject(pFunc, pArgs);//调用函数 PyObject_CallObject(pFunc,NULL);//调用函数 // //Py_DECREF(pName); //Py_DECREF(pArgs);//打印调用信息 Py_DECREF(pModule); // close python Py_Finalize(); return 0; } 接着我们配置项目属性 将python的头文件(比如C:\Users\Leowen\Anaconda3\include)添加到， 项目属性页-&gt;VC++目录-&gt; 包含目录。 将python.lib文件(比如C:\Users\Leowen\Anaconda3\libs)添加到，配置-&gt;链接器-&gt;输入-&gt;附加依赖项。 接下来修改pyconfig.h文件。 找到C:\Users\Leowen\Anaconda3\include\pyconfig.h文件，打开文件，搜索python36_d.lib，将 # if defined(_DEBUG) # pragma comment(lib,&quot;python36_d.lib&quot;) 修改为： # if defined(_DEBUG) # pragma comment(lib,&quot;python36.lib&quot;) 再搜索Py_DEBUG，将 #ifdef _DEBUG # define Py_DEBUG #endif 修改为： #ifdef _DEBUG //# define Py_DEBUG #endif 接下来，将我们的python脚本拷贝到我们项目文件中 生成解决方案： 运行结果 更多的参考：VS2015 C++调用Python3.5环境搭建 从C调用Python脚本unableto load the file system codec ImportError]]></content>
      <categories>
        <category>平时</category>
      </categories>
      <tags>
        <tag>C++AndPython</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[case study_building an amazon.com cover search]]></title>
    <url>%2F%2F2018%2F11%2F30%2Fcase%20study_building%20an%20amazon.com%20cover%20search.html</url>
    <content type="text"><![CDATA[Building an amazon.com cover search 新建一个coverdescriptor.py import numpy as np import cv2 class CoverDescriptor: def __init__(self,useSIFT = False): self.useSIFT = useSIFT 我们首先定义了我们的CoverDescriptor类，该类封装了在图像中查找关键点的方法，然后使用局部不变描述符描述每个关键点周围的区域。 定义init构造函数，需要一个可选参数：useSIFT，一个布尔值，指示是否应使用SIFT关键点检测器和描述符。 keypoints，features，and opencv 3现在，在我们深入之前，让我们简要讨论一下OpenCV库组织的一个重要变化。 在v3.0版本中，OpenCV已将SIFT，SURF，FREAK以及其他关键点检测器和本地不变描述符实现移至可选的opencv_contrib包中。 这一举措是为了巩固(1)算法的实验性实现，以及(2)OpenCV将“非自由”（即专利）算法（包括许多流行的关键点检测器和局部不变描述符）称为100％可选模块， OpenCV不需要安装和运行。简而言之，如果您曾经使用过OpenCV 2.4.X中的cv2.FeatureDetector_create或cv2.DescriptorExtractor_创建函数，它们就不再是OpenCV的一部分。 您仍然可以访问免费的非专利方法，例如ORB和BRISK，但如果您需要SIFT和SURF，则必须在编译和安装时明确启用它们。有关OpenCV的这一更改及其对关键点检测器，本地不变描述符以及哪些Python版本可以访问哪些功能的影响的更多信息，请参考：https://www.pyimagesearch.com/2015/07/16/where-did-sift-and-surf-go-in-opencv-3/ 由于OpenCV不再随自动启用SIFT模块一起提供，我们现在为名为useSIFT的init方法提供一个布尔值，默认值为False，表示只有在程序员明确要求的情况下才能使用SIFT。 接下来让我们继续： def describe(self,image): # initialize the BRISK detector and feature extractor(the # standard openCV 3 install includes BRISK by default) descriptor = cv2.BRISK_create() # check if SIFT should be utilized to detect and extract # features (this this will cause an error if you are using # OpenCV 3.0+ and do not have the `opencv_contrib` module # installed and use the `xfeatures2d` package) if self.useSIFT: descriptor = cv2.xfeatures2d.SIFT_create() # detect keypoints in the image, describing the region # surrounding each keypoint, then convert the keypoints # to a NumPy array (kps,descs) = descriptor.detectAndCompute(image,None) kps = np.float32([kp.pt for kp in kps]) # return a tuple of keypoints and descriptor return (kps,descs) 为了从图像中提取关键点和描述符，我们定义了describe方法，该方法接收单个参数 ——要从中提取关键点和描述符的图像。 接着，我们使用BRISK初始化我们的描述符方法。如果我们设置useSIFT=True，那么我们就使用SIFT重新初始化我们的描述符了。 现在我们已经初始化了我们的描述符，接着我们调用detectAndCompute方法。正如这个名字所暗示的，这个方法既检测关键点(即图像的“interesting”区域)，然后描述和量化每个关键点周围的区域。因此，关键点检测是“检测”阶段，而区域的实际描述是“计算”阶段。 关键点列表包含多个由OpenCV定义的keyPoint对象。这些对象包含关键点的位置(x,y)，关键点的大小和旋转角度以及其他属性等信息。 对于我们现在这个应用程序，我们只需要包含在pt属性中的关键点的(x，y)坐标。 我们获取关键点的(x，y)坐标，丢弃其他属性，并将这些点存储为NumPy数组。 最后，将关键点和相应描述符以元组形式返回给调用函数。 目前，我们可以从书籍的封面中提取关键点和描述符。但是如何比较它们呢？ 让我们新建一个covermatcher.py文件 import numpy as np import cv2 class CoverMatcher: def __init__(self,descriptor,coverPaths,ratio=0.7, minMatches=40,useHamming=True): # store the descriptor, book cover paths, ratio and minimum # number of matches for the homography calculation, then # initialize the distance metric to be used when computing # the distance between features self.descriptor = descriptor self.coverPaths = coverPaths self.ratio = ratio self.minMatches = minMatches self.distanceMethod = &quot;BruteForce&quot; # if the Hamming distance should be used, then update the # distance method if useHamming: self.distanceMethod += &quot;-Hamming&quot; 我们首先定义了CoverMatcher类以及构造函数。构造函数接收两个必须参数和三个可选参数。两个必须参数是我们的描述符，假设它是上面定义的CoverDescriptor的实例，以及封面图片路径存储的路径。 三个可选参数解析如下： ratio：Lowe建议的最近邻距离的比率，以减少需要计算单应性的关键点的数量 minMatches：要计算单应性所需的最小匹配数。 useHamming：一个布尔值，指示是否应使用汉明或欧几里德距离来比较特征向量。 前两个参数,ratio和minMatches，我们将在match函数中详细讨论。第三个参数useHamming我们将现在进行探讨。 值得注意的是，SIFT和SURF产生real-valued特征向量，而ORB，BRISK以及AKAZE则产生binary特征向量。在比较real-valued描述符的时候，比如，SIFT或者SURF，我们希望使用欧氏距离(Euclidean distance)。然而，如果我们使用BRISK特征(产生binary feature)，我们应该使用Hamming距离。你所选择的特征向量描述符(SIFT vs BISK)将会影响你的distance method。由于我们默认使用BRISK features，因此我们将使用Hamming method。 接下来，我们定义search方法，看看关键点和描述符将如何匹配： def search(self,queryKps,queryDescs): # initialize the dictionary of results results = {} # loop over the book cover images for coverPath in self.coverPaths: # load the query image,convert it to grayscale,and # extract keypoints and descriptors cover = cv2.imread(coverPath) gray = cv2.cvtColor(cover,cv2.COLOR_BGR2GRAY) (kps,descs) = self.descriptor.describe(gray) # determine the number of matched , inlier keypoints, # then update the results score = self.match(queryKps,queryDescs,kps,descs) results[coverPath] = score # if matches were found,sort them if len(results) &gt; 0: results = sorted([(v,k) for (v,k) in results.items() if v &gt; 0],reverse = True) # return the results return results 我们首先定义了我们的search方法，该方法需要两个参数——从查询图像(query image)中提取的关键点和描述符集。此方法的目标是从查询图像中获取关键点和描述符，然后与关键点数据库进行匹配 数据库中具有最佳“匹配”的条目将被选为书籍封面的标识。 为了存储我们的匹配准确度结果，我们定义了一个results字典。字典的key是覆盖唯一的书籍封面文件名，balue将是关键点(keypoints)的匹配百分比。 然后，我们开始循环遍历列表封面路径。书籍封面从磁盘加载，接着被转换为灰度，然后使用CoverDescriptor从中提取关键点和描述符。 然后使用match方法(下面定义)确定匹配关键点的数量，并更新results字典 接着，我们做一个快速检查，以确保至少存在一些结果。然后结果按降序排序，书籍封面和更多关键点匹配位于列表顶部。 然后，排序的结果将返回给调用者。 接下来让我们定义match方法： def match(self,kpsA,featuresA,kpsB,featuresB): # compute the raw matches and initialize the list of actual # matches matcher = cv2.DescriptorMatcher_create(self.distanceMethod) rawMatches = matcher.knnMatch(featuresB,featuresA,2) matches = [] # loop over the raw matches for m in rawMatches: # ensure the distance is within a certain ratio of each # other if len(m) == 2 and m[0].distance &lt; m[1].distance * self.ratio: matches.append((m[0].trainIdx,m[1].queryIdx)) # check to see if there are enough matches to process if len(matches) &gt; self.minMatches: # construct the two sets of points ptsA = np.float32([kpsA[i] for (i,_) in matches]) ptsB = np.float32([kpsB[j] for (_,j) in matches]) # conpute the homography between the two sets of points # and compute the ratio of matched points (_,status) = cv2.findHomography(ptsA,ptsB,cv2.RANSAC,4.0) # return the ratio of the number of matched keypoints # to the total number of keypoints return float(status.sum()) / status.size # no matches were found return -1.0 我们定义了我们的match方法。这个方法有四个参数，详述如下： kpsA：与要匹配的第一个图像关联的关键点列表。 featuresA：与要匹配的第一图像相关联的特征向量的列表 kpsB：与要匹配的第二个图像关联的关键点列表 featuresB：与要匹配的第二图像相关联的特征向量的列表。 然后我们使用cv2.DescriptorMatcher_create这个函数定义了我们的匹配器(matcher)。这个值将是BruteForce或BruteForce-Hamming，表明我们将使用Euclidean或Hamming距离将特征A中的每个描述符与特征B中的每个描述符进行比较。将具有最小距离的特征向量作为“匹配”。 我们使用matcher的knnMatch方法进行我们的匹配。函数的“kNN”部分代表“k-最近邻”，其中“最近邻居”由特征向量之间的最小欧几里德距离定义。具有最小欧几里德距离的两个特征向量被认为是“邻居(Neighbors)”。特征A和特征B都被传递给`knnMatch1函数，第三个参数为2，表示我们想要为每个特征向量找到两个最近邻。 knnMatch方法的输出结果赋值给rawMatches变量。但是着并不是实际的mapped keypoints。我们还要采取一些步骤。 首先初始化实际匹配列表。接着，我们循环我们的rawMatches。 然后确保下面两个情况成立(make a check to ensure two cases hold)。第一个是首先确保确实有two matches。第二个是应用David Lowe ratio进行测试，确保the first match的距离小于the second match 乘以 ratio的距离。 假设比率测试成立，则使用第一个关键点的索引和第二个关键点的索引的元组更新匹配列表(matches list)。 然后我们做了第二个重要的check。我们确保匹配数量至少是最小匹配数(minimum matches)。如果没有足够的匹配，则不值得计算单应性(homography)，因为两个图像(可能)不会包含相同的书籍封面。 同样，假设上面测试成立，我们接着定义了两个列表ptsA和ptsB，以存储每组匹配关键点的(x，y)坐标。 最后，我们可以计算单应性，这是两个关键点平面(具有相同的投影中心)之间的映射。 实际上，该算法将采用wine吧的匹配和确定哪些关键点确实是“匹配”以及哪些是误报。 为实现这一目标，我们使用cv2.findHomography函数和RANSAC算法，它代表随机样本共识 RANSAC从我们的match列表中随机抽样。然后，RANSAC尝试将这些样本匹配在一起并验证关键点是否为内点(inliers)的假设.RANSAC继续这样做，直到足够大的匹配集被认为是内点。接着，RANSAC采用一系列内部函数并寻找更多匹配。 重要的是RANSAC算法是迭代的。它继续这个过程，直到达到停止标准。 RANSAC算法由cv2.findHomography函数实现，该函数接收四个参数。前面两个是ptsA和ptsB(潜在匹配的(x，y)坐标)。 第三个参数是单应性方法。我们传递cv2.RANSAC表示我们想使用RANSAC算法。当然，我们也可以使用cv2.LMEDS方法，这是Least-Median robust方法。 最后一个参数是RANSAC重新投影阈值，它允许关键点之间存在一些“摆动空间”。假设ptsA和ptsB的(x，y)坐标是以像素为单位测量的，我们传递的值为4.0表示 任何一对关键点被视为内部的容差将被容忍4.0像素的误差。 在cv2.RANSAC和cv2.LMEDS之间进行选择通常取决于问题的范围。虽然cv2.LM-EDS方法的好处是不必明确定义重新投影阈值，但缺点是它通常只能在当至少50％的关键点是内点时起作用。 cv2.findHomograpy函数返回一个包含两个值的元组。第一个是转换矩阵，我们忽略了。 我们对第二个返回值,the status,更感兴趣，状态(the status)变量是布尔值列表，如果匹配的是ptsA和ptsB中的相应关键点，则值为1，如果不匹配，则值为0。 我们计算内部数量与潜在匹配总数的比率，并将其返回给调用者。高分表示两个图像之间更好的“匹配”。 最后，如果最小匹配数测试失败，则返回值-1.0，表示无法计算内部数。 新建一个search.py文件 from __future__ import print_function from preprocess.coverdescriptor import CoverDescriptor from preprocess.covermatcher import CoverMatcher import argparse import glob import csv import cv2 # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&quot;-d&quot;, &quot;--db&quot;, required = True, help = &quot;path to the book database&quot;) ap.add_argument(&quot;-c&quot;, &quot;--covers&quot;, required = True, help = &quot;path to the directory that contains our book covers&quot;) ap.add_argument(&quot;-q&quot;, &quot;--query&quot;, required = True, help = &quot;path to the query book cover&quot;) ap.add_argument(&quot;-s&quot;, &quot;--sift&quot;, type = int, default = 0, help = &quot;whether or not SIFT should be used&quot;) args = vars(ap.parse_args()) # initialize the database dictionary of covers db = {} # loop over the database for l in csv.reader(open(args[&quot;db&quot;]): # update the database using image ID as the key db[l[0]] = l[1:] 首先，我们导入我们将使用的包。CoverDescriptor将从图像中提取关键点和局部不变描述符，而CoverMatcher将确定两本书籍封面的“匹配程度”。 argparse包将用于解析命令行参数，glob用于获取书籍封面图像的路径，csv用于解析书籍的.csv数据库，cv2用于OpenCV绑定。 从左到右的属性是书籍封面的唯一文件名，书籍的作者和书的标题。 接着我们解析命令行参数。–db指向书籍数据库CSV文件的位置，而–covers是包含书籍封面图像的目录的路径。–query开关 是我们查询图像的路径。最后，可选的–sift 开关 用于指示是否应该使用SIFT方法而不是BRISK算法(默认情况下将使用BRISK)。这里的目标是获取查询图像并在数据库中找到具有最佳匹配的书籍封面。 然后我们构建图书信息数据库。首先，定义db字典。然后，打开书籍数据库CSV文件并循环每一行。数据库字典使用书籍的唯一文件名作为关键字以及书籍和作者的标题作为值进行更新(，得到的结果，比如：’cover001.png’: [‘Michael Crichton’, ‘Next’])。 # initialize the default parameters using BRISK is being used useSIFT = args[&quot;sift&quot;] &gt; 0 useHamming = args[&quot;sift&quot;] == 0 ratio = 0.7 minMatches = 40 # if SIFT is to be used, then update the parameters if useSIFT: minMatches = 50 在上面，我们做的第一件事是确定是否应该使用SIFT算法代替(默认)BRISK算法。如果–sift命令行参数的值&gt;0，我们将使用SIFT;否则，我们将使用默认的BRISK。 现在已经确定了BRISK和SIFT之间的选择，我们还可以确定是否应该使用汉明距离(Hamming distance)。如果我们使用SIFT算法，那么我们将提取实值特征向量——因此 应使用欧几里德距离。但是，如果我们使用BRISK算法，那么我们将计算二进制特征向量，而应该使用汉明距离。 然后初始化Lowe’s ratio test的默认值和最小化匹配数。 在我们使用SIFT算法的情况下，我们将添加额外约束，我们应找到更多匹配，以确保更准确的书籍封面识别(设置minMatches=50)。 # initialize the cover descriptor and cover matcher cd = CoverDescriptor(useSIFT = useSIFT) cv = CoverMatcher(cd,glob.glob(args[&quot;covers&quot;] + &quot;\\*.png&quot;), ratio=ratio,minMatches=minMatches,useHamming = useHamming) # load the query image, convert it to grayscale,and extract # keypoints and descriptors queryImage = cv2.imread(args[&quot;query&quot;]) gray = cv2.cvtColor(queryImage,cv2.COLOR_BGR2GRAY) (queryKps,queryDescs) = cd.describe(gray) # try to match the book cover to a know database of images results = cv.search(queryKps,queryDescs) # show the query cover cv2.imshow(&quot;Query&quot;, queryImage) 在这里，我们首先实例化我们的CoverDescriptor，然后实例化我们的CoverMatcher，将我们的CoverDescriptor和书籍封面路径列表作为参数传递。 然后加载查询图像并转换为灰度。 接下来，我们从查询图像中提取我们的关键点和局部不变描述符。 为了执行实际匹配，调用CoverMatcher类的搜索方法，其中我们提供查询关键点和查询描述符。返回排序的结果列表，最佳书籍封面匹配位于列表顶部。 最后，我们向用户显示查询图像。 # check to see if no results were found if len(results) == 0: print(&quot;I could not find a match for that cover!&quot;) cv2.waitKey(0) # otherwise , matches were found else: # loop over the results for (i,(score,coverPath)) in enumerate(results): # grab the book information (author,title) = db[coverPath[coverPath.rfind(&quot;\\&quot;) + 1:]] print(&quot;{} . {:.2f}% : {} -- {}&quot;.format(i+1,score * 100, author,title)) # load the result image and show it result = cv2.imread(coverPath) cv2.imshow(&quot;Result&quot;,result) cv2.waitKey(0) 首先，我们检查以确保找到至少一本书籍封面匹配。如果找不到匹配项，打印出信息让用户知道。 如果找到匹配，则我们开始循环results。 提取书籍的唯一文件名，并从书籍数据库中抓取作者和书名，并显示给用户。 最后，实际的书籍封面本身从磁盘上加载并显示给用户。 执行我们的脚本文件： python search.py --db books.csv --covers covers --query queries\query01.png 执行结果： 1 . 98.72% : Preston and Child -- Dance of Death 可以看到： 封面成功匹配，超过97％的关键点也匹配。 完整代码： 链接：https://pan.baidu.com/s/1JALPDEE_bq0ytEY4Y2jv_Q 提取码：wozo 更多的参考：Case Studies – Recognizing Book Covers]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>case study</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[case study_plant classification]]></title>
    <url>%2F%2F2018%2F11%2F29%2Fcase%20study_plant%20classification.html</url>
    <content type="text"><![CDATA[Plant classification 新建一个rbghistogram.py文件 import cv2 class RGBHistogram: def __init__(self,bins): # store the number of bins the histogram will use self.bins = bins def describe(self,image,mask=None): # compute a 3D histogram in the RGB colorspace, # then normalize the histogram so that images # with the same content,but either scaled larger # or smaller will have(roughly) the same histogram hist = cv2.calcHist([image],[0,1,2], mask,self.bins,[0,256,0,256,0,256]) cv2.normalize(hist,hist) # return out 3D histogram as a flattened array return hist.flatten() 我们首先导入cv2，这是我们需要创建图像描述符的唯一的package。 然后，我们定义了RGBHistogram类，用于封装花卉图像的量化方式。__init__方法只接受一个参数——一个包含3D直方图的bins的列表。 描述图像将由describe方法处理，该方法接收两个参数，一个将构建颜色直方图的图像，以及一个可选的mask。如果我们提供mask，则只有与mask区域相关联的像素将用于构造直方图。这允许我们仅描述图像的花瓣，忽略图像的其余部分(即，背景，其与花本身无关)。 接着，构造直方图。calcHist函数的第一个参数是我们想要描述的图像的列表，该函数具体的参数请参考前面的文章。然后将生成的图像进行标准化，并以特征向量返回。 注意：cv2.normalize函数在OpenCV 2.4.X和OpenCV 3.0之间略有不同。 在OpenCV 2.4.X中，cv2.normalize函数实际上会返回规范化的直方图。 但是，在OpenCV 3.0+中，cv2.normalize实际上对函数内的直方图进行了规范化，并更新了传入的第二个参数（即“输出”）。这是一个微妙但重要的区别，在使用这两个参数时要记住 OpenCV版本. 现在已经定义了图像描述符，我们可以创建代码来对给定花朵的物种进行分类： from __future__ import print_function from preprocess import RGBHistogram from sklearn.preprocessing import LabelEncoder from sklearn.ensemble import RandomForestClassifier from sklearn.cross_validation import train_test_split from sklearn.metrics import classification_report import numpy as np import argparse import glob import cv2 # construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&quot;-i&quot;, &quot;--images&quot;, required = True, help = &quot;path to the image dataset&quot;) ap.add_argument(&quot;-m&quot;, &quot;--masks&quot;, required = True, help = &quot;path to the image masks&quot;) args = vars(ap.parse_args()) 我们首先导入必要的package。我们首先导入了RGBHistogram用于描述我们的每个图像。 然后导入scikit-learn库的LabelEncoder类。为了构建机器学习分类器以区分花种，我们首先需要一种方法来编码与每个花类相关联的“类标签”。我们希望将向日葵，番红花，雏菊和三色紫罗兰区分开来，但为了构建机器学习模型，这些种类(以字符串表示的)需要转换为整数。LabelEncoder类就是用来干这件事的。 我们使用的实际分类模型是RandomForestClassifier。随机森林是用于分类的集成学习方法，由多个决策树组成。 对于随机森林中的每棵树，构建一个自举(替换采样)样本，通常由66％的数据集组成。然后，基于自举样本构建决策树。在树中的每个节点处，仅采用预测变量的样本来计算节点分割标准。通常使用sqrt(n)预测变量，其中n是特征空间中预测变量的数量。然后重复该过程以训练森林中的多棵树(关于随机森林分类器的详细内容超出了本文的范围，有兴趣的可以参考机器学习方法)。 但是，如果您是使用机器学习的新手，随机森林是一个很好的起点，特别是在计算机视觉领域，他们只需很少的努力即可获得更高的精度。同样，虽然这不适用于所有计算机视觉分类问题，但随机森林是获得基线准确度的良好起点。 然后我们从scikit-learn导入train_test_split函数。在构建机器学习模型时，我们需要两组数据：训练集(training set)和测试(testing set)(或验证(validation set)）集。 我们使用training data对机器学习模型进行训练(在这种情况下，我们使用随机森林学习模型)。然后使用testing data对模型进行评估。 保持这两组是独一无二的非常重要，因为它允许在尚未看到的数据点上评估模型。如果模型已经看到了数据点，那么结果是有偏见的，因为它具有不公平的优势！ 最后，我们使用NumPy进行数值处理，使用argparse来解析命令行参数，使用glob来抓取磁盘上的图像路径，使用cv2进行OpenCV绑定。 接着我们需要两个命令行参数：–images，指向包含其花图像的目录，–mask，指向包含mask鲜花的目录。这些mask使我们只能专注于我们想要描述的花朵部分(即花瓣)，忽略背景和其他杂乱，否则会扭曲特征向量并插入不需要的噪音。 更多关于此数据集请参考Flowers # grab the image and mask paths imagePaths = sorted(glob.glob(args[&quot;images&quot;] + &quot;\\*.png&quot;)) maskPaths = sorted(glob.glob(args[&quot;masks&quot;] + &quot;\\*.png&quot;)) # initialize the list of data and class label targets data = [] target = [] # initialize the image descriptor desc = RGBHistogram([8,8,8]) # loop over the image and mask paths for (imagePath,maskPath) in zip(imagePaths,maskPaths): # load the image and mask image = cv2.imread(imagePath) mask = cv2.imread(maskPath) mask = cv2.cvtColor(mask,cv2.COLOR_BGR2GRAY) # describe the image features = desc.describe(image,mask) # update the list of data and targets data.append(features) target.append(imagePath.split(&quot;_&quot;)[-2]) 我们使用glob来分别抓住我们的图像和mask的路径。通过传入包含图像的目录，然后是通配符*.png，我们能够快速构建图像路径列表。 接着，我们简单地初始化数据矩阵和类标签列表(即花的种类)。 然后实例化我们的图像描述符——每个通道有8个bins的3D RGB颜色直方图。该图像描述符将产生用于表征花的颜色的8×8×8=512维特征向量。 接着我们开始在我们的图像和mask上循环。我们将图像和mask从磁盘中加载进来，然后在、将mask转换为灰度。 接着应用我们的3D RGB颜色直方图产生我们的特征向量，然后将其存储在数据矩阵中。 然后解析花的种类，并更新target列表。 现在我们可以应用我们的机器学习方法了。 # grab the unique target names and encode the labels targetNames = np.unique(target) le = LabelEncoder() target = le.fit_transform(target) # construct the training and testing splits (trainData,testData,trainTarget,testTarget) = train_test_split(data,target, test_size=0.3,random_state = 42) # train the classifier model = RandomForestClassifier(n_estimators=25,random_state=84) model.fit(trainData,trainTarget) # evaluate the classifier print(classification_report(testTarget,model.predict(testData), target_names=targetNames)) 首先，我们给我们的类标签进行编码。NumPy的unique方法用于查找唯一的species名称，然后将其输入LabelEncoder。调用fit_transform将“唯一”物种名称“拟合(fits)”为整数，一个species对应于一个category，然后将字符串“转换(transform)”为相应的整数类。target变量现在包含一个整数列表，每个数据点对应一个整数列表，其中每个整数映射到一个花种名称。 接着，我们开始构建我们的训练和测试集。我们将使用train_test_split函数。我们需要传递我们数据矩阵和target列表，指定测试数据集是整个数据集大小的30％。使用伪随机状态42，以便我们可以在以后的运行中重现我们的结果。 调用RandomForestClassifier函数，使用森林中的25个决策树进行训练。同样，明确使用伪随机状态，以便我们的结果是可重复的。 然后使用classification_report函数打印出我们的模型的准确性。我们将实际testing targets作为第一个参数传递，然后让模型预测它认为花种对测试数据的影响。然后，classification_report函数将预测与真实targets进行比较，并为整个系统和每个单独的类别标签打印准确度报告。 为了进一步研究分类，我们定义了以下代码： # loop over a sample of the images for i in np.random.choice(np.arange(0,len(imagePaths)),10): # grab the image and mask paths imagePath = imagePaths[i] maskPath = maskPaths[i] # load the image and mask image = cv2.imread(imagePath) mask = cv2.imread(maskPath) mask = cv2.cvtColor(mask,cv2.COLOR_BGR2GRAY) # describe the image features = desc.describe(image,mask) # predict what type of flower the image is flower = le.inverse_transform(model.predict([features]))[0] print(imagePath) print(&quot;I think this flower is a {}&quot;.format(flower.upper())) cv2.imshow(&quot;image&quot;,image) cv2.waitKey(0) 我们首先从所有图片里面随机挑选10张不同的图像进行调查，然后获取对应的图片和mask的路径。 然后我们将图片和mask的路径加载进来。并将mask图片转换为灰度图像。 然后，我们利用describe提取特征向量，以表征花的颜色。 我们查询我们的随机森林分类器以确定花的种类，然后将其打印到控制台并屏幕上显示。 最后执行我们的脚本程序： python classify.py --image dataset\images --mask dataset\masks 运行结果 完整代码： 链接：https://pan.baidu.com/s/12S873RoH_a-02KLYslTqaw 提取码：i53z 更多的参考：Interactive Foreground Extraction using GrabCut Algorithm Case Studies – Plant Classification]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>case study</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[case study_eye tracking]]></title>
    <url>%2F%2F2018%2F11%2F28%2Fcase%20study_eye%20tracking.html</url>
    <content type="text"><![CDATA[Eye tracking 新建一个文件夹，命名为preprocess，然后在里面新建文件eyetracker.py文件。键入如下代码： import cv2 class EyeTracker: def __init__(self,faceCascadePath,eyeCascadePath): # load the face and eye detector self.faceCascade = cv2.CascadeClassifier(faceCascadePath) self.eyeCascade = cv2.CascadeClassifier(eyeCascadePath) def track(self,image): # detect faces in the image and initialize the list of # rectangles containing the faces and eyes faceRects = self.faceCascade.detectMultiScale(image, scaleFactor=1.1,minNeighbors=5,minSize=(30,30), flags=cv2.CASCADE_SCALE_IMAGE) rects = [] 我们定义了一个EyeTracker类，然后定义构造函数__int__。我们的EyeTracker类有两个参数：faceCascadePath和eyeCascadePath。第一个是OpenCV中内置面部级联分类器(face cascade classifier)的路径。第二个是眼睛级联分类器(eye cascade classifier)的路径。 然后，我们使用cv2.CascadeClassifier函数从磁盘加载两个分类器。 接着，我们定义了用于在图像中找到眼睛的轨迹方法(track)。此方法仅接收一个参数，即包含要跟踪的面部和眼睛的图像。 然后我们调用faceCascade分类器的detectMultiScale方法。该方法向我们返回图像中每个面部的边界框位置(即，x，y，宽度和高度)。 然后，我们初始化一个矩形列表，用于包含图像中的面部和眼睛矩形。 现在我们的图像中的脸部区域已经找到了，让我们看看如何使用它们来找到眼睛： # loop over the face bounding boxes for (fX,fY,fW,fH) in faceRects: # extract the face ROI and update the list of # bounding boxes faceROI = image[fY:fY + fH,fX:fX + fW] rects.append((fX,fY,fX + fW,fY + fH)) # detect eyes in the face ROI eyeRects = self.eyeCascade.detectMultiScale(faceROI, scaleFactor=1.1,minNeighbors=10, minSize=(20,20), flags=cv2.CASCADE_SCALE_IMAGE) # loop over the eye bounding boxes for (eX,eY,eW,eH) in eyeRects: # update the list of boounding boxes rects.append((fX + eX,fY + eY,fX + eX + eW,fY + eY + eH)) # return the rectangles representing bounding # boxes around the faces and eyes return rects 然后，我们使用NumPy阵列切片从图像中提取面部感兴趣区域(ROI:region of interest)。faceROI变量现在包含面部的边界框区域。 最后，我们将矩形的(x，y)坐标附加到rects列表中供以后使用。 接下来，我们移到眼睛检测。这一次，我们调用了eyeCascade的detectMultiScale方法，该方法返回了出了眼睛出现在图像位置的列表。 我们使用了更大的minNeighbors值，因为眼睛级联(eye cascade)往往比其他分类器产生更多的误报。 注意：这些参数被硬编码到EyeTracker类中。如果您将此脚本应用于自己的图像和视频，则可能需要稍微调整一下以获得最佳效果。从scaleFactor变量开始，然后转到minNeighbors。 接着，我们在眼睛的边界框区域上循环，并更新边界框矩形列表。 最后，我们将边界框列表将返回给调用者。 文件目录 现在，我们将困难的部分已经完成。是时候通过创建eyetracking.py将各个部分粘合在一起了： from preprocess import EyeTracker import imutils import argparse import cv2 # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&quot;-f&quot;, &quot;--face&quot;, required = True, help = &quot;path to where the face cascade resides&quot;) ap.add_argument(&quot;-e&quot;, &quot;--eye&quot;, required = True, help = &quot;path to where the eye cascade resides&quot;) ap.add_argument(&quot;-v&quot;, &quot;--video&quot;, help = &quot;path to the (optional) video file&quot;) args = vars(ap.parse_args()) # construct the eye tracker et = EyeTracker(args[&quot;face&quot;],args[&quot;eye&quot;]) 首先，我们输入必要的包。我们将使用我们自定义EyeTracker类来查找图像中的面部和眼睛。用imutils，一组图像处理便利功能来帮助我们调整图像大小。最后，使用argparse进行命令行解析，并使用cv2进行OpenCV绑定。 然后，我们解析了我们的命令行参数：–face，这是我们的面部级联分类器(face cascade classifier)的路径，–eye，是我们眼睛级联分类器(eye cascade classifier)的路径。 和前面一样，创建一个可选参数–video,它指向磁盘上的视频文件。 最后，我们分别使用脸部和眼睛分类器的路径实例化我们的EyeTracker类。 # if a video path was not supplied, grab the reference # to the gray if not args.get(&quot;video&quot;): camera = cv2.VideoCapture(0) # otherwise, load the video else: camera = cv2.VideoCapture(args[&quot;video&quot;]) # keep looping while True: # grab the current frame (grabbed,frame) = camera.read() # if we are viewing a video and we did not grab a # frame, then we have reached the end of the video if args.get(&quot;video&quot;) and not grabbed: break 如果未提供视频文件，在这种情况下，cv2.VideoCapture函数被告知使用系统的网络摄像头。否则，如果提供了视频文件的路径,则cv2.VideoCapture函数将打开视频文件并返回指向它的指针。 接着，我们开始在视频frame上循环。对相机调用read()会抓取视频中的下一帧。read方法返回一个元组，包含(1)表示frame是否被成功读取的布尔值，以及(2)frame本身。 然后，我们进行检查以确定视频是否到达尾部。我们仅在从文件中读取视频时才执行此检查。 现在我们拥有视频中的当前帧，可以执行面部和眼睛检测： # resize the frame and convert it to grayscale frame = imutils.resize(frame,width=300) gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) # detect faces and eyes in the image rects = et.track(gray) # loop over the face bounding boxes and draw them for rect in rects: cv2.rectangle(frame,(rect[0],rect[1]), (rect[2],rect[3]),(0,255,0),2) # show the tracked eyes and face cv2.imshow(&quot;Tracking&quot;,frame) # if the &apos;q&apos; key is pressed, stop the loop if cv2.waitKey(1) &amp; 0xFF == ord(&quot;q&quot;): break # cleanup the camera and close any open windows camera.release() cv2.destroyAllWindows() 为了使面部和眼睛检测更快，我们首先调整图像的大小，使其具有300像素的宽度。 然后，我们将其转换为灰度。转换为灰度倾向于提高级联分类器的准确性。 使用视频中的当前帧调用EyeTracker的track方法。然后，此方法返回与图像中的面部和眼睛对应的rects列表。 接着，我们开始在边界框矩形上循环，并使用cv2.rectangle函数绘制每个矩形，其中第一个参数是frame，第二个是边界框的起始(x，y)坐标，第三个是结束frame的(x，y)坐标，后面是框的颜色(绿色)和厚度(2个像素)。 然后我们显示检测到的面部和眼睛的frame。我们进行检查以确定用户是否按下了q键。如果用户这样做，那么循环就会终止。 最后，我们执行清理，其中释放相机指针并关闭OpenCV创建的所有窗口。 python eyetracking.py --face cascades\haarcascade_frontalface_default.xml --eye cascades\haarcascade_eye.xml 完整代码： 链接：https://pan.baidu.com/s/1nBBrbk0GD8pl2aF_sscbuA 提取码：wwpy 更多的参考：Case Studies – Eye Tracking Simple, accurate eye center tracking in OpenCV]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>case study</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[case study_handwriting recognition with hog]]></title>
    <url>%2F%2F2018%2F11%2F28%2Fcase%20study_handwriting%20recognition%20with%20hog.html</url>
    <content type="text"><![CDATA[Handwriting recognition with HOG HOG：Histogram of Oriented Gradients。类似于边缘方向直方图和局部不变描述符(例如SIFT)，HOG对图像的梯度幅度进行操作。 然而，与SIFT不同，SIFT计算图像的小的局部区域中的边缘方向上的直方图，HOG在均匀间隔的单元的密集网格上计算这些直方图。此外，这些单元也可以重叠并进行对比度归一化，以提高描述符的准确性。 在这种情况下，我们将应用HOG图像描述符和线性支持向量机(SVM)来学习图像数字的表示。 幸运的是，scikit-image库已经实现了HOG描述符，因此在计算其特征表示时我们可以直接使用它。 from skimage import feature class HOG: def __init__(self,orientations = 9,pixelsPerCell=(8,8), cellsPerBlock=(3,3),transform=False): # store the number of orientations, pixels per cell, # cells per block, and whether or not power law # compression should be applied self.orienations = orientations self.pixelsPerCell = pixelsPerCell self.cellsPerBlock = cellsPerBlock self.transform = transform def describe(self,image): # compute HOG for the image hist = feature.hog(image,orientations=self.orienations, pixels_per_cell=self.pixelsPerCell, cells_per_block=self.cellsPerBlock, transform_sqrt=self.transform) # return the HOG features return hist 我们首先导入scikit-image的feature子包。该包包含许多从图像中提取特征的方法。 接着，我们设置__init__构造函数，需要四个参数。第一个orientations定义每个直方图中将有多少个梯度方向(即，bins的数量)。pixelsPerCell参数定义将落入每个单元格的像素数。当在图像上计算HOG描述符时，图像将被划分为多个单元，每个单元的大小为pixelsPerCell × pixelsPerCell。然后将为每个单元计算梯度幅度的直方图。 然后，HOG将根据cellsPerBlock参数将落入每个块的单元格数来标准化每个直方图。 可选地，HOG可以应用幂律压缩(获取输入图像的对数/平方根)，这可以导致描述符的更好准确性。 在存储了构造函数的参数之后，我们定义了describe方法，只需要一个参数——要计算HOG描述符的图像。 计算HOG描述符由scikit-image的feature子包的hog方法处理。我们传递orientations的数量，每个单元的像素数，每个块的单元格，以及在计算HOG描述符之前是否应该将平方根变换应用于图像。 最后我们将计算的HOG特征向量返回给调用者。 接下来，我们需要一个数字数据集，他可以用来从中提取特征并训练我们的机器学习模型。我们决定使用MNIST数字识别数据集的样本，这是计算机视觉和机器学习文献中的经典数据集。 完整的数据集 数据集的样本由5000个数据点组成，每个数据点具有长度为784的特征向量，对应于图像的28×28灰度像素强度。 但首先，我们需要定义一些方法来帮助我们操作和准备数据集以进行特征提取和训练我们的模型。我们将这些数据集操作函数存储在dataset.py中： from . import imutils import numpy as np import mahotas import cv2 def load_digits(datasetPath): # build the dataset and then split it into data # and labels data = np.genfromtxt(datasetPath,delimiter=&quot;,&quot;,dtype=&quot;uint8&quot;) target = data[:,0] data = data[:,1:].reshape(data.shape[0],28,28) # return a tuple of the data and targets return (data,target) 我们首先导入我们需要的包。我们将使用numpy进行数字处理，mahotas是另一个计算机视觉库来辅助cv2，最后是imutils，其中包含执行常见图像处理任务(如调整大小和旋转图像)的便利功能。 为了将我们的数据集加载到磁盘上，我们定义了load_digits方法。该方法只需要一个参数，即datasetPath，它是MNIST样本数据集驻留在磁盘上的路径。 从那里，NumPy的genfromtext函数将数据集加载到磁盘上并将其存储为无符号的8位NumPy数组。请记住，此数据集由图像的像素强度组成。这些像素强度永远不会小于0且绝不会大于255，因此我们能够使用8位无符号整数数据类型。 数据矩阵的第一列包含我们的target，它是图像包含的数字。target将落在[0,9]范围内。 同样，第一个之后的所有列都包含图像的像素强度。同样，这些是尺寸为M×N的数字图像的灰度像素，并且将始终落在[0,255]的范围内。 最后，我们将data和target以元组的形式返回给调用者。 接下来，我们需要对数字图像执行一些预处理： def deskew(image,width): # grab the width and height of the image and compute # moments for the image (h,w) = image.shape[:2] moments = cv2.moments(image) # deskew the image by applying an affine transformation skew = moments[&quot;mu11&quot;] / moments[&quot;mu02&quot;] M = np.float32([ [1,skew,-0.5 * w * skew], [0,1,0] ]) image = cv2.warpAffine(image,M,(w,h), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR) # resize the image to have a constant width image = imutils.resize(image,width = width) # return the deskewed image return image 每个人都有不同的写作风格。虽然我们大多数人写的数字“向左倾斜”，但有些数字向右倾斜。我们中的一些人以不同的角度写数字。这些变化的角度可能导致试图学习各种数字表示的机器学习模型的混淆。 为了帮助修复一些“lean”数字，我们定义了deskew(倾斜)方法。这个函数有两个参数。第一个是要被歪斜的数字图像。第二个是图像要调整大小的宽度。 我们首先获取图像的高度和宽度，然后计算图像的moment。这些moment包含有关图像中白色像素位置分布的统计信息 根据前面的moments，我们计算出了skew。接着我们构造了warping matrix M。该矩阵M将用于对图像进行去歪斜。 图像的实际偏斜校是调用cv2.warpAffine函数。第一个参数是将要倾斜的图像，第二个参数是定义图像将被歪斜的“方向”的矩阵M，第三个参数是偏斜图像的最终宽度和高度。最后，flags参数控制图像的校正方式。 在这种情况下，我们使用线性插值。 最后我们调整偏斜图像的大小并返回给调用者。 为了获得一致的数字表示，其中所有图像具有相同的宽度和高度，数字位于图像的中心，然后我们需要定义图像的范围： def center_extent(image,size): # grab the extent width and height (eW,eH) = size # handle when the width is greater than the height if image.shape[1] &gt; image.shape[0]: image = imutils.resize(image,width = eW) # otherwise , the height is greater than the width else: image = imutils.resize(image,height = eH) # allocate memory for the extent of the image and # grab it extent = np.zeros((eH,eW),dtype=&quot;uint8&quot;) offsetX = (eW - image.shape[1]) // 2 offsetY = (eH - image.shape[0]) // 2 extent[offsetY:offsetY + image.shape[0],offsetX:offsetX + image.shape[1]] = image # compute the center of mass of the image and then # move the center of mass to the center of the image (cY,cX) = np.round(mahotas.center_of_mass(extent)).astype(&quot;int32&quot;) (dX,dY) = ((size[0] // 2) - cX,(size[1] // 2) - cY) M = np.float32([[1,0,dX],[0,1,dY]]) extent = cv2.warpAffine(extent,M,size) # return the extent of the image return extent 我们首先定义了center_extent函数，该函数有两个参数。第一个是偏斜校正的图像，第二个是图像的输出尺寸(即输出宽度和高)。 然后检查宽度是否大于图像的高度。如果是这种情况，则会根据图像的宽度调整图像大小。否则，高度大于宽度，因此必须根据图像的高度调整图像大小。 这些都是重要的检查。如果没有进行这些检查并且总是根据图像的宽度调整大小，那么高度可能会大于宽度，因此不适合图像的“extent”。 然后，我们使用相同的维度，给这个extnet的图像分配空间。 接着，我们计算offsetX和offsetY。这些偏移表示图像放置在extent(扩展后)的图像的起始(x，y)坐标(以y，x顺序放置)。 我们使用NumPy数组切片设置实际的extent。 下一步是translate the digit，使其位于图像的中心。 我们使用mahotas包的center_of_mass函数计算图像中白色像素的加权平均值。此函数返回图像中心的加权(x，y)坐标。然后，将这些(x，y)坐标转换为整数而不是浮点数。 然后，我们translates the digit，使其位于图像的中心。 M是我们的平移矩阵，该矩阵告诉我们的图像要进行平移多少像素(从左到右，从上到下)。该矩阵被定义为float32类型的数组，因为OpenCV希望该矩阵是一个float类型。[1,0,tx]，其中tx是the number of pixels we will shift the image left or right，而负值则表示图像将向左平移，正值表示图像将向右平移。然后[0,1,ty]，其中，ty是the number of pixels we will shift the image up or down。其中，负值表示图像向上平移，正值表示图像向下平移。我们定义好了平移矩阵之后，图像的实际平移是使用了cv2.warpAffine函数来执行，该函数的第一个参数是我们要进行平移的图像，第二个参数是我们的平移矩阵M，最后我们需要手动地提供图像的尺寸(width and height)作为第三个参数。 最后，我们将居中图像返回给调用者。 接下来训练我们的机器模型，编写train.py文件 # import the necessary packages from sklearn.externals import joblib from sklearn.svm import LinearSVC from preprocess.hog import HOG from preprocess import dataset import argparse # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&quot;-d&quot;, &quot;--dataset&quot;, required = True, help = &quot;path to the dataset file&quot;) ap.add_argument(&quot;-m&quot;, &quot;--model&quot;, required = True, help = &quot;path to where the model will be stored&quot;) args = vars(ap.parse_args()) 首先导入需要的包。我们将使用scikit-learn中的LinearSVC模型来训练线性支持向量机(SVM)。同时还将导入HOG图像描述符和dataset utility functions。最后，argparse将用于解析命令行参数，而joblib将用于将训练过的模型转储到文件中。 我们的脚本需要两个命令行参数，第一个是 –dataset，它是磁盘上的MNIST样本数据集的路径。第二个参数是 –model，是我们训练过的LinearSVC的输出路径。 # load the dataset and initialize the data matrix (digits,target) = dataset.load_digits(args[&quot;dataset&quot;]) data = [] # initialize the HOG descriptor hog = HOG(orientations=18,pixelsPerCell=(10,10), cellsPerBlock=(1,1),transform=True) #loop over the images for image in digits: # deskew the image, center it image = dataset.deskew(image,20) image = dataset.center_extent(image,(20,20)) # describe the image and update the data matrix hist = hog.describe(image) data.append(hist) 首先我们从磁盘加载由images和targets组成的数据集。然后初始化用于保存每个图像的HOG描述符的数据列表. 接下来，实例化HOG描述符，使用18个orientations作为梯度幅度直方图，每个单元10个像素，每个块1个单元。最后，通过设置transform=True，表示在创建直方图之前将计算像素强度的平方根。 然后开始循环我们的digit images。图像接着被校正并被转换到图像中心。 通过调用describe方法，为预处理图像计算HOG特征向量。最后，使用HOG特征向量更新数据矩阵。 # train the model model = LinearSVC(random_state=42) model.fit(data,target) # dump the model to file joblib.dump(model,args[&quot;model&quot;]) 最后训练我们的模型。使用伪随机状态42实例化我们的LinearSVC，以确保我们的结果是可重复的。然后使用数据矩阵和target训练模型。最后将我们的模型dump到磁盘里面。 接下来我们可以使用训练好的模型来进行分类，新建classify.py文件 from __future__ import print_function from sklearn.externals import joblib from preprocess.hog import HOG from preprocess import dataset import argparse import mahotas import cv2 # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&quot;-m&quot;, &quot;--model&quot;, required = True, help = &quot;path to where the model will be stored&quot;) ap.add_argument(&quot;-i&quot;, &quot;--image&quot;, required = True, help = &quot;path to the image file&quot;) args = vars(ap.parse_args()) model = joblib.load(args[&quot;model&quot;]) # initialize the HOG descriptor hog = HOG(orientations = 18, pixelsPerCell = (10, 10), cellsPerBlock = (1, 1), transform = True) 首先导入必要的包，然后我们将两个命令行参数传递给classify.py。第一个是–model，即存储cPickle’d模型的路径。第二个–image，是包含我们想要分类和识别的数字的图像的路径。 接着将经过训练的LinearSVC从磁盘加载。 然后，使用与训练阶段期间完全相同的参数来实例化HOG描述符。 现在我们已准备好找到图像中的数字，以便对它们进行分类： # load the image and convert it to grayscale image = cv2.imread(args[&quot;image&quot;]) gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # blur the image, find edges, and then find contours along # the edged regions blurred = cv2.GaussianBlur(gray,(5,5),0) edged = cv2.Canny(blurred,30,150) (_,cnts,_) = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # sort the contours by their x-axis position, ensuring # that we read the numbers from left to right cnts = sorted([(c,cv2.boundingRect(c)[0]) for c in cnts],key=lambda x:x[1]) 第一步是将查询图像加载到磁盘上，并将其转换为灰度。 接着，使用高斯模糊来模糊图像，并使用Canny边缘检测器在图像中找到边缘。 最后，我们在边缘图像中找到轮廓并从左到右对它们进行排序。这些轮廓中的每一个都代表图像中需要分类的数字。 接下来，我们现在需要处理这些数字中的每一个： # loop over the contours for (c,_) in cnts: # compute the bouding box for the rectangle (x,y,w,h) = cv2.boundingRect(c) # if the width is at least 7 pixels and the height # is at least 20 pixels, the contour is likely a digit if w&gt;=7 and h&gt;=20: # crop the ROI and then threshold the grayscale # ROI to reveal the digit roi = gray[y:y + h,x:x + w] thresh = roi.copy() T = mahotas.thresholding.otsu(roi) thresh[thresh &gt; T] = 255 thresh = cv2.bitwise_not(thresh) # deskew the image center its extent thresh = dataset.deskew(thresh, 20) thresh = dataset.center_extent(thresh, (20, 20)) cv2.imshow(&quot;thresh&quot;, thresh) 接下来我们开始循环我们的轮廓图。使用cv2.boundingRect函数每个轮廓的边界框，该函数返回边界框的起始(x，y)坐标，后跟框的宽度和高度。 然后我们边界框的宽度和高度，以确保它至少有七个像素宽，二十个像素高(视情况而定，比如1的话可能宽度没有那么宽)。如果边界框区域不满足这些尺寸，则认为它太小而不是数字。如果尺寸检查成立，则使用NumPy阵列切片从灰度图像中提取感兴趣区域(ROI)。 此ROI现在保留将被分类的数字。但首先，我们需要应用一些预处理步骤。 首先是应用Otsu的阈值处理方法来分割背景中的前景(数字)(数字写在纸上)。正如在训练阶段一样，数字然后被去偏斜并转换到图像中心。 现在，我们可以对数字进行分类： # extract features from the image and classify it hist = hog.describe(thresh) digit = model.predict([hist])[0] print(&quot;I think thath number is : {}&quot;.format(digit)) # draw a rectangle around the digit, the show what # digit was classified as cv2.rectangle(image,(x,y),(x + w,y + h),(0,255,0),1) cv2.putText(image,str(digit),(x-10,y-10), cv2.FONT_HERSHEY_SIMPLEX,1.2,(0,255,0),2) cv2.imshow(&quot;Image&quot;,image) cv2.waitKey(0) 首先，我们通过调用HOG描述符的describe方法来计算阈值ROI的HOG特征向量。 HOG特征向量被馈入LinearSVC的预测方法，该方法根据HOG特征向量对ROI进行分类。 然后将分类的数字打印出来。最后在原始图片上显示预测的数字。 我们使用cv2.putText方法在原始图像上绘制数字。cv2.putText函数的第一个参数是我们想要绘制的图像，第二个参数是包含我们想要绘制的字符串。在这种情况下就是我们的数字了。接下来，我们提供将绘制文本的位置的(x，y)坐标。我们希望这个文本在ROI边界框的左边十个像素和上方十个像素。第四个参数是一个内置的OpenCV常量，用于定义将用于绘制文本的字体。第五个参数是文本的相对大小，第六个参数是文本的颜色(绿色)，最后一个参数是文本的粗细(两个像素)。 最后执行我们的脚本程序。 python classify.py --model model\svm.cpickle --image images\test1.png 预测结果： I think thath number is : 8 I think thath number is : 6 I think thath number is : 7 I think thath number is : 4 I think thath number is : 1 完整代码： 链接：https://pan.baidu.com/s/1NBxhzWAcEZDOxX7w99K-kg 提取码：ss6s 更多的参考：关于Python中的lambda Python的sort函数和sorted、lambda和cmp Case Studies – Handwriting Recognition Getting Started with Deep Learning and Python LeNet – Convolutional Neural Network in Python]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>case study</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[case study_object tracking in video]]></title>
    <url>%2F%2F2018%2F11%2F27%2Fcase%20study_object%20tracking%20in%20video.html</url>
    <content type="text"><![CDATA[Object tracking in video import numpy as np import argparse import time import cv2 # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&quot;-v&quot;, &quot;--video&quot;, help = &quot;path to the (optional) video file&quot;) args = vars(ap.parse_args()) # define the upper and lower boundaries for a color # to be considered &quot;blue&quot; blueLower = np.array([100,67,0],dtype=&quot;uint8&quot;) blueUpper = np.array([255,128,50],dtype=&quot;uint8&quot;) # load the video if not args.get(&quot;video&quot;): camera = cv2.VideoCapture(0) else: camera = cv2.VideoCapture(args[&quot;video&quot;]) 我们将使用NumPy进行数值处理，使用argparse解析命令行参数，使用cv2进行OpenCV绑定。time包是可选的。 我们只需要一个命令行参数，–video，也就是我们视频的路径。 我们将在视频中追踪的对象是蓝色物体。由于除了该物体外，蓝色在视频中的任何其他位置都不常见，因此我们希望跟踪蓝色阴影。为了完成这种颜色跟踪，我们定义了蓝色阴影的下限和上限。请记住，OpenCV表示RGB颜色空间中的像素，但顺序相反。 在这种情况下，如果大于R=0，G=67，B=100且小于R=50，G=128，B=255，则将颜色定义为“蓝色”。 最后，我们打开视频文件并使用cv2.VideoCapture函数获取对它的引用。我们将此引用赋值给变量camera。 # keep looping while True: # grab the current frame (grabbed,frame) = camera.read() # check to see if we have reached the end of the video if args.get(&quot;video&quot;) and not grabbed: break # determine which pixels fall within the blue boundaries # and then blur the binary image blue = cv2.inRange(frame,blueLower,blueUpper) blue = cv2.GaussianBlur(blue,(3,3),0) 现在我们有了对视频的引用，便可以开始处理帧。 我们开始循环遍历帧，一次一个。调用read()方法的调用抓取视频中的下一帧，返回具有两个值的元组。第一个是grabbed，是一个布尔值，表示是否从视频文件中成功读取了帧。第二个frame，是帧本身。 然后，我们检查frame是否成功读取。如果未读取框架，则表示已到达视频的末尾，我们break掉while循环。 为了在frame中找到蓝色阴影，我们必须使用cv2.inRange函数。该函数有三个参数。第一个是我们想要检查的frame。第二个是RGB像素的lower threshold，第三个是上限阈值(upper threshold)。调用此函数的结果是阈值图像，像素落在上下范围内设置为白色，像素不属于此范围 设为黑色。 最后，我们对阈值图像进行高斯模糊处理，以使查找轮廓更准确。 # find contours in the image (_,cnts,_) = cv2.findContours(blue.copy(),cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # check to see if any contours were found if len(cnts) &gt; 0: # sort the contours and find the largest one -- # we will assume this contour coorespondes to the # area of my phone cnt = sorted(cnts,key=cv2.contourArea,reverse=True)[0] # compute the (rotated) bounding box around then # contour and then draw it rect = np.int32(cv2.boxPoints(cv2.minAreaRect(cnt))) cv2.drawContours(frame,[rect],-1,(0,255,0),2) # show the frame and the binary image cv2.imshow(&quot;Traccking&quot;,frame) cv2.imshow(&quot;Binary&quot;,blue) # if your machine is fast, it may display the frames in # what appears to be &apos;fast forward&apos; since more than 32 # frames per second are being displayed -- a simple hack # is just to sleep for a tiny bit in between frames; # however, if your computer is slow, you probably want to # comment out this line time.sleep(0.025) # if the &apos;q&apos; key is pressed, stop the loop if cv2.waitKey(1) &amp; 0xFF == ord(&quot;q&quot;): break # cleanup the camera and close any open windows camera.release() cv2.destroyAllWindows() 现在我们有了阈值图像，那么我们需要找到图像中最大的轮廓，假设最大轮廓对应于我们想要跟踪的蓝色物体轮廓。 我们调用cv2.findContours会在阈值图像中找到轮廓。我们使用copy()方法克隆阈值图像，因为cv2.findContour函数对传入的NumPy数组具有破坏性。 然后检查以确保实际发现轮廓。如果轮廓列表的长度为零，则没有找到蓝色区域。如果轮廓列表的长度大于零，那么我们需要找到最大的轮廓。这里，轮廓按相反的顺序排序(最大的第一个)，使用cv2.contourArea函数来 计算轮廓的面积。具有较大区域的轮廓存储在列表的前面。在这种情况下，抓住具有最大面积的轮廓，再次假设该轮廓对应于蓝色物体的轮廓。 现在我们有了蓝色的轮廓，但我们需要在它周围绘制一个边界框。 调用cv2.minAreaRect计算轮廓周围的最小边界框。然后，cv2.boxPoints将边界框重新定义为点列表。 注意：在OpenCV 2.4.X中，我们将使用cv2.BoxPoints函数来计算轮廓的旋转边界框。但是，在OpenCV 3.0+中，此函数已移至cv2.boxPoints。两个函数执行相同的任务，只是略有不同的命名空间。 最后，我们使用cv2.drawContours函数绘制边界框。 具有检测到的蓝色物体的frame显示在第一个imshow，并且阈值图像(落入蓝色像素的下/上范围的像素)显示在第二个imshow。 上面，time.sleep(0.025)可选的。在许多较新型号的机器上，系统可能足够快以处理&gt;32帧/秒。如果是这种情况，找到可接受的睡眠时间将减慢处理速度并将其降低到更正常的速度。 执行我们的脚本 python track.py 结果： 或者指定视频路径 python track.py --video &quot;video\2018-11-27 18-38-15-927.mp4&quot; 也是可以的。 完整代码： 链接：https://pan.baidu.com/s/1jvnoV_StHRTXlzK5Zvc3dw 提取码：q9bl 更多的参考：Case Studies – Object Tracking in Video imutils library OpenCV Track Object Movement Histogram of Oriented Gradients and Object Detection]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>case study</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[case study_webcam face detection]]></title>
    <url>%2F%2F2018%2F11%2F27%2Fcase%20study_webcam%20face%20detection.html</url>
    <content type="text"><![CDATA[Webcam face detection from preprocess import FaceDetector from preprocess import imutils import argparse import cv2 # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&quot;-f&quot;, &quot;--face&quot;, required = True, help = &quot;path to where the face cascade resides&quot;) ap.add_argument(&quot;-v&quot;, &quot;--video&quot;, help = &quot;path to the (optional) video file&quot;) args = vars(ap.parse_args()) # construct the face detector fd = FaceDetector(args[&quot;face&quot;]) 这里出现了一个新的包，imutils，这个包主要包含用于执行基本图像操作的便捷功能，例如调整大小。前面我们已经实现了imutils这个包了，如果没看的话，那么我们直接安装这个包也是可以的。执行pip install imutils就可以了，然后直接导入这个包即可(import imutils)。 我们同样需要一个haar级联分类器才能找到图像中的脸部。分类器被序列化为XML文件，可以由OpenCV加载。我们的face参数指向磁盘上的序列化XML级联分类器。 出于调试目的(或者系统没有网络摄像头)，我们创建了一个可选的命令行参数–video，它指向磁盘上的视频文件。为了防止无法使用网络摄像头，使用视频文件测试和调试他的实时系统仍然是一件好事。在这种情况下，我们只需要提供我们的视频文件目录给video参数即可。 最后我们通过传递cascade classifier的路径实例化我们的FaceDetector。 # if a video path was not supplied, grab the reference # to the gray if not args.get(&quot;video&quot;): camera = cv2.VideoCapture(0) else: camera = cv2.VideoCapture(args[&quot;video&quot;]) 如果我们未提供视频路径。在这种情况下，OpenCV将尝试从笔记本电脑的内置(或USB)网络摄像头读取视频。否则，OpenCV将打开video参数指向的视频文件。 在任何一种情况下，都使用cv2.VideoCapture函数。提供整数值0指示OpenCV从网络摄像头设备读取，而提供字符串表示OpenCV应打开路径指向的视频。提供无效路径将导致空指针，如果没有有效的视频文件，我们显然无法进行任何面部检测。 假设抓取对视频的引用成功，我们将此指针存储在camera变量中。 # keep looping while True: # grab the current frame (grabbed,frame) = camera.read() # if we are viewing a video and we did not grab a # frame, then we have reached the end of the video if args.get(&quot;video&quot;) and not grabbed: break # resize the frame and convert it to grayscale frame = imutils.resize(frame,width=300) gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY) 下一步是开始循环视频中的所有帧 在最基本的层面上，视频只是放在一起的一系列图像，这意味着我们实际上可以一次读取这些帧。我们首先用while循环将保持循环遍历帧，直到满足以下两种情况之一：(1)视频已到达其结束且没有更多帧，或(2)用户过早地停止执行脚本。 在while循环里，我们首先通过调用相机的read()方法抓取视频中的下一帧。read方法返回两个值的元组：第一个是抓取的，用布尔值表示读取帧是否成功的true或false，第二个就是抓取的帧本身。 如果我们正在从文件中读取视频，但是并没有抓取到帧，则说明视频结束，这个时候应该用break退出循环。 否则，我们对帧进行一些预处理。首先就是调整帧的大小，使其宽度为300像素，以便更快地实时进行人脸检测。然后将帧转换为灰度。 # detect faces in the image and then clone the frame # so that we can draw on it faceRects = fd.detect(gray,scaleFactor=1.1,minNeighbors=5, minSize=(30,30)) frameClone = frame.copy() # loop over the face bounding boxes and draw them for (fX,fY,fW,fH) in faceRects: cv2.rectangle(frameClone,(fX,fY),(fX + fW,fY + fH),(0,255,0),2) # show our detected faces cv2.imshow(&quot;Face&quot;,frameClone) # if the &apos;q&apos; key is pressed, stop the loop if cv2.waitKey(1) &amp; 0xFF == ord(&quot;q&quot;): break # cleanup the camera and close any open windows camera.release() cv2.destroyAllWindows() 我们首先传递了灰度帧并应用了FaceDetector的detect方法。 但是为了在我们的图像上绘制一个边界框，我们决定首先创建一个框架的克隆，以防万一我们需要原始框架进行进一步的预处理。帧的克隆存储在frameClone中。 然后我们遍历图像中面部的边界框，并使用cv2.rectangle函数绘制它们。然后显示我们的结果。 当然，用户可能希望停止执行脚本。我们不用强制输入ctrl+c，而是检查用户是否在键盘上按了q键。 最后，我们release了对相机的引用，并且关闭了由OpenCV创建的任何打开的窗口。 执行脚本文件 提供video视频文件： python cam.py --face cascades\haarcascade_frontalface_default.xml --video video\myvideo.mp4 不提供video文件 python cam.py --face cascades\haarcascade_frontalface_default.xml 执行结果： 完整代码： 链接：https://pan.baidu.com/s/1MMrksgrEw5Xr-F2R6lDTpw 提取码：lk0j 更多的参考：Case Studies – Webcam Face Detection Increasing webcam FPS with Python and OpenCV Increasing Raspberry Pi FPS with Python and OpenCV Unifying picamera and cv2.VideoCapture into a single class with OpenCV Multiple cameras with the Raspberry Pi and OpenCV]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>case study</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[case study_face detection]]></title>
    <url>%2F%2F2018%2F11%2F26%2Fcase%20study_face%20detection.html</url>
    <content type="text"><![CDATA[Face Detection from __future__ import print_function from preprocess import FaceDetector import argparse import cv2 ap = argparse.ArgumentParser() ap.add_argument(&quot;-f&quot;, &quot;--face&quot;, required = True, help = &quot;path to where the face cascade resides&quot;) ap.add_argument(&quot;-i&quot;, &quot;--image&quot;, required = True, help = &quot;path to where the image file resides&quot;) args = vars(ap.parse_args()) # load the image and convert it to grayscale image = cv2.imread(args[&quot;image&quot;]) gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) 首先导入必要的包，为了保持代码的整洁，在同目录下新建了一个preprocess文件夹，然后新建一个init.py文件，使得python解释器将该文件夹解释为包。在该文件夹下还有个facedetector.py文件。不懂init.py的参考前面的文章。然后我们解析参数，读取图片并将图片转化为灰度图像。 但是，让我们不要走得太远。在我们考虑在图像中寻找面部之前，我们首先需要定义一个类来处理我们如何在图像中找到面部。 新建一个facedetector.py文件(该文件就是上面导入的包) import cv2 class FaceDetector: def __init__(self,faceCascadepath): # load the face detector self.faceCascade = cv2.CascadeClassifier(faceCascadepath) def detect(self,image,scaleFactor = 1.1,minNeighbors=5,minSize=(30,30)): # detect faces in the image rects = self.faceCascade.detectMultiScale(image, scaleFactor=scaleFactor,minNeighbors=minNeighbors, minSize=minSize,flags=cv2.CASCADE_SCALE_IMAGE) # return the rectangles representing bounding # boxes around the faces return rects 为了构建人脸识别软件，我们采用在OpenCV中内置的Haar级联分类器。这些分类器已经过预先训练以识别面孔！ 构建我们自己的分类器当然不在本案例研究的范围之内。但如果我们想，我们需要很多“积极”和“消极”的图像。正图像将包含具有面部的图像，而负面图像将包含没有面部的图像。基于此数据集，我们可以提取特征来表征图像中的面部(或没有面部)并构建我们自己的分类器。这将是很多工作，而且非常耗时。 无论如何，这些分类器通过以不同的比例尺寸从左到右，从上到下扫描图像来工作。从左到右，从上到下扫描图像称为“滑动窗口(sliding window)”方法。 当窗口从左向右和从上到下移动时，一次一个像素，根据我们提供给分类器的参数，询问分类器是否“认为”在当前窗口中存在面部。 在类里面，我们定义了构造函数，该函数需要一个参数——我们级联分类器所在的路径。此分类器被序列化为XML文件。对cv2.CascadeClassifier的调用将对分类器进行反序列化，将其加载到内存中，并允许我们检测图像中的面部。 为了在图像中实际找到面部，我们接着定义了检测方法。该函数需要一个必需参数，即想要找到面部的图像，后跟三个可选参数。让我们来看看这些参数意味着什么： scaleFactor:图像大小在每个图像尺度上减少了多少。这个值用于创建缩放金字塔，以便检测图像中多个缩放的面部(一些面可能更接近前景，因此更大；其他面可能更小并且在背景中，因此使用不同的缩放)。比如设置值为1.05，则表明我们在金字塔中的每个级别将图像的大小减小了5％。 minNeighbors:每个窗口应该有多少个neighbors才能将窗口中的区域视为一个脸。级联分类器将检测面部周围的多个窗口。此参数控制需要检测多少矩形(Neighbors)才能将窗口标记为面部。 minSize:宽度和高度(以像素为单位)的元组，表示窗口的最小尺寸。小于此大小的边界框将被忽略。从(30,30)开始并从那里进行微调是一个好主意。 通过调用在FaceDetector类的构造函数中创建的分类器的detectMultiScale方法，处理检测图像中的实际面部。我们使用我们默认的scaleFactor，minNeighbors和minSize，然后该方法为我们完成了整个人脸检测过程！ 然后，detectMultiScale方法返回rects，这是一个包含图像中面部边界框的元组列表。这些边界框只是面部的(x，y)位置，以及框的宽度和高度。 接着，让我们继续detect_faces.py文件的编写，来实现我们自己的图像面部识别。 文件detect_faces.py # find faces in the image fd = FaceDetector(args[&quot;face&quot;]) faceRects = fd.detect(gray,scaleFactor=1.1,minNeighbors=5, minSize=(30,30)) print(&quot;I found {} face(s)&quot;.format(len(faceRects))) # loop over the faces and draw a rectangle around each for (x,y,w,h) in faceRects: cv2.rectangle(image,(x,y),(x + w,y + h),(0,255,0),2) # show the detected faces cv2.imshow(&quot;Faces&quot;,image) cv2.waitKey(0) 我们首先实例化我们的FaceDetector类，提供XML分类器的路径作为唯一参数。通过调用detect方法检测传入图像中的实际面部。然后打印我们在图像中一共找到了几个faces。 但是为了在图像周围实际绘制一个边界框，我们需要单独循环它们。同样，每个边界框只是一个有四个值的元组：在图像中，x和y为起始位置，然后是脸部的宽度和高度。 对cv2.rectangle的调用会在face上绘制一个绿色框。最后执行我们的脚本。 文件目录 执行脚本文件 F:\20181116\Case Studies, 3nd Edition\face detect&gt;python detect_faces.py --image &quot;My Snapshot.jpg&quot; --face cascades\haarcascade_frontalface_default.xml I found 1 face(s) 显示结果： 需要注意的是，图片最好不要有中文路径，不然报错 F:\20181116\Case Studies, 3nd Edition\face detect&gt;python detect_faces.py -image GDP组合.jpg --face cascades\haarcascade_frontalface_default.xml l usage: detect_faces.py [-h] -f FACE -i IMAGE detect_faces.py: error: unrecognized arguments: GDP组合.jpg 也许这看上去好像很完美了，但是当我们多测试几张图片的时候，发现了如下的结果 F:\20181116\Case Studies, 3nd Edition\face detect&gt;python detect_faces.py --i spurs.jpg --face cascades\haarcascade_frontalface_default.xml I found 3 face(s) 明明没有Tim的脸，为啥检测到了三个脸呢？ 答案在于我们上面讨论过的cv2.detectMutliScale函数的参数。这些参数往往是敏感的，一组图像的某些参数选择不适用于另一组图像。 在大多数情况下，罪魁祸首将是scaleFactor参数。在其他情况下，它可能是minNeighbors。但作为调试规则，从scaleFactor开始，根据需要进行调整，然后转到minNeighbors。 考虑到这个调试规则，我们首先改变了对FaceDetector检测方法的调用： faceRects = fd.detect(gray,scaleFactor=1.3,minNeighbors=5, minSize=(30,30)) 唯一的变化是scaleFactor参数，将其从1.1更改为1.3。 我们在看一次结果： 很显然，这一次结果是正确的，只有两个面部(虽然石佛和妖刀退役了，跑车也远离了圣城)，但无论如何我们的结果是正确的。 完整代码： 链接：https://pan.baidu.com/s/16rPsfhpV8pbSd-fKvAJ07w 密码：d4c9 更多的参考：Case Studies – Face Detection Rapid Object Detection using a Boosted Cascade of Simple Features HOG + Linear SVM framework, image pyramid sliding windows haarcascades pre-trained HOG detector to detect pedestrians in images]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>case study</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_14]]></title>
    <url>%2F%2F2018%2F11%2F24%2FOpenCV_python3_14.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 14 ContoursOpenCV提供了在图像中查找“曲线”的方法，称为轮廓。轮廓是点的曲线，曲线中没有间隙。轮廓对形状近似和分析等方面非常有用。 为了在图像中找到轮廓，您需要首先使用边缘检测方法或阈值处理来获得图像的二值化。在下面的例子中，我们将使用Canny边缘检测器找到硬币的轮廓，然后找到硬币的实际轮廓。 新建一个counting_coins.py文件 from __future__ import print_function import numpy as np import argparse import cv2 # Construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&quot;-i&quot;, &quot;--image&quot;, required = True, help = &quot;Path to the image&quot;) args = vars(ap.parse_args()) # Load the image, convert it to grayscale, and blur it slightly image = cv2.imread(args[&quot;image&quot;]) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (11, 11), 0) cv2.imshow(&quot;Image&quot;, image) # The first thing we are going to do is apply edge detection to # the image to reveal the outlines of the coins edged = cv2.Canny(blurred, 30, 150) cv2.imshow(&quot;Edges&quot;, edged) 正如前一章讨论的边缘检测方法一样，我们将把图像转换为灰度，然后应用高斯模糊，使边缘检测器更容易找到硬币的轮廓。我们这次使用了更大的模糊大小，σ=11. 然后，我们通过应用Canny边缘检测器来获得边缘图像。再次，正如在先前的边缘检测示例中，任何低于30的梯度值被认为是非边缘，而高于150的任何值被认为是确定的边缘。 # Find contours in the edged image. # NOTE: The cv2.findContours method is DESTRUCTIVE to the image # you pass in. If you intend on reusing your edged image, be # sure to copy it before calling cv2.findContours (_,cnts,_) = cv2.findContours(edged.copy(),cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # How many contours did we find? print(&quot;I count {} coins in this image&quot;.format(len(cnts))) # Let&apos;s highlight the coins in the original image by drawing a # green circle around them coins = image.copy() cv2.drawContours(coins,cnts,-1,(0,255,0),2) cv2.imshow(&quot;Coins&quot;,coins) cv2.waitKey(0) 现在我们有了硬币的轮廓，我们可以找到the contours of the outlines。我们使用cv2.findContours函数来实现。此方法返回一个3元组：(1)应用轮廓检测后的图像(经过修改并基本上被破坏)，(2)轮廓本身，cnts以及(3)轮廓的层次结构(见下文)。 Note: The return tuple of cv2. findContours has changed in OpenCV 3.0. Originally in OpenCV 2.4.X, this tuple was only a 2-tuple, consisting of just the contours themselves and the associated hierarchy. However, in OpenCV 3.0, we have a third value added to the return tuple: the image itself after applying the contour detection algorithm. This is a small, minor change (and one that I’m personally not crazy about since it breaks backwards compatibility with so many scripts), but something that can definitely trip you up when working with both OpenCV 2.4.X and OpenCV 3.0. Be sure to take special care when using the cv2. findContours function if you intend for your code to be cross-version portable. cv2.findContours的第一个参数是我们的边缘图像。重要的是要注意，此函数对您传入的图像具有破坏性。如果您打算稍后在代码中使用该图像，最好使用NumPy复制方法复制它。 第二个参数是我们想要的轮廓类型。我们使用cv2.RETR_EXTERNAL来仅检索最外面的轮廓(即，跟随硬币轮廓的轮廓)。我们也可以通过cv2.RETR_LIST来获取所有轮廓。其他方法包括使用cv2.RETR_COMP和cv2.RETR_TREE的层次轮廓，但层次结构轮廓不在本文的范围。 我们的最后一个参数是我们想要近似轮廓。我们使用cv2.CHAIN_APPROX_SIMPLE仅将水平，垂直和对角线段压缩到其端点。 这节省了计算和内存。如果我们想要轮廓上的所有点，没有压缩，我们可以传入cv2.CHAIN_APPROX_NONE; 但是，使用此功能时要非常谨慎。 沿轮廓检索所有点通常是不必要的并且浪费资源。 我们的轮廓cnts只是一个Python列表。我们可以使用它上面的len函数来计算返回的轮廓数。 当我们执行我们的脚本时，我们将输出“我在此图像中计算9个硬币”打印到我们的控制台 F:\20181116\Practical Python and OpenCV, 3rd Edition&gt;python counting_coins.py -i coins.png I count 9 coins in this image 现在，我们可以绘制轮廓。为了不在我们的原始图像上绘制，我们制作原始图像的副本，赋值给coins。 对cv2.drawContours的调用会在我们的图像上绘制实际轮廓。 函数的第一个参数是我们想要绘制的图像。第二个是我们的轮廓列表。 接下来，我们有轮廓指数。通过指定负值-1，我们指示我们要绘制所有轮廓。但是，我们也会提供一个索引i，这将是cnts中的第i个轮廓。这将允许我们只绘制一个轮廓而不是所有轮廓。 例如，以下是分别绘制第一，第二和第三轮廓的一些代码： cv2.drawContours(coins, cnts, 0, (0, 255, 0), 2) cv2.drawContours(coins, cnts, 1, (0, 255, 0), 2) cv2.drawContours(coins, cnts, 2, (0, 255, 0), 2) cv2.drawContours函数的第四个参数是我们要绘制的线的颜色。在这里，我们使用绿色。 最后，我们的最后一个参数是我们绘制的线的粗细。我们将绘制厚度为两个像素的轮廓。 让我们从图像中裁剪每个硬币： # Now, let&apos;s loop over each contour for (i,c) in enumerate(cnts): # We can compute the &apos;bounding box&apos; for each contour, which is # the rectangle that encloses the contour (x,y,w,h) = cv2.boundingRect(c) # Now that we have the contour, let&apos;s extract it using array # slices print(&quot;Coin #{}&quot;.format(i+1)) coin = image[y:y + h,x:x + w] cv2.imshow(&quot;Coin&quot;,coin) # Just for fun, let&apos;s construct a mask for the coin by finding # The minumum enclosing circle of the contour mask = np.zeros(image.shape[:2],dtype=&quot;uint8&quot;) ((centerX,centerY),radius) = cv2.minEnclosingCircle(c) cv2.circle(mask,(int(centerX),int(centerY)),int(radius),255,-1) mask = mask[y:y + h,x:x + w] cv2.imshow(&quot;Masked Coin&quot;,cv2.bitwise_and(coin,coin,mask=mask)) cv2.waitKey(0) 然后，我们在当前轮廓上使用cv2.boundingRect函数。此方法找到我们的轮廓适合的“封闭框”，允许我们从图像中裁剪它。该函数采用单个参数，一个轮廓，然后返回矩形开始的x和y位置的元组，后跟矩形的宽度和高度。 然后我们使用我们的边界框坐标和NumPy阵列切片从图像中裁剪硬币。 如果我们能找到轮廓的边界框，为什么不在轮廓上加一个圆？毕竟，硬币是圆圈。 我们首先将mask初始化为NumPy零数组，其原始图像的宽度和高度相同。 我们调用cv2.minEnclosingCircle方法来fit我们轮廓的圆。 我们传入一个圆形变量，即当前轮廓，并给出圆的x和y坐标及其半径。 使用(x，y)坐标和半径，我们可以在我们的mask上画一个圆圈，代表硬币。 然后我们将与裁剪硬币完全相同的方式裁剪mask。 为了仅显示硬币的前景并忽略背景，我们使用硬币图像和硬币的mask来调用我们可靠的按位AND函数。 用到的函数 cv2.findContours np.uint8 cv2.drawContours cv2.Canny 更多的参考：PPaO Chapter 11 – Contours Target acquired: Finding targets in drone and quadcopter video streams using Python and OpenCV Measuring size of objects in an image with OpenCV OpenCV shape detection Detecting machine-readable zones in passport images Detecting Barcodes in Images with Python and OpenCV How to Build a Kick-Ass Mobile Document Scanner in Just 5 Minutes]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Opencv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[case study 3rd——说在前面的话]]></title>
    <url>%2F%2F2018%2Fcase_study_00.html</url>
    <content type="text"><![CDATA[case study 00 获取额外的补充材料，请在下面的网址中注册获取 补充材料 关于字体： Italic: 表示您应注意的关键术语和重要信息。 也可以基于内涵表示数学方程或公式。 **Bold: 你应该注意的重要信息。 用到的函数 cv2.findContours np.uint8 cv2.drawContours cv2.Canny 更多的参考：PPaO Chapter 11 – Contours Target acquired: Finding targets in drone and quadcopter video streams using Python and OpenCV Measuring size of objects in an image with OpenCV OpenCV shape detection Detecting machine-readable zones in passport images Detecting Barcodes in Images with Python and OpenCV How to Build a Kick-Ass Mobile Document Scanner in Just 5 Minutes]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>case study</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_13]]></title>
    <url>%2F%2F2018%2F11%2F23%2FOpenCV_python3_13.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 13 Gradients and Edge detection本章主要涉及渐变和边缘检测。形式上，边缘检测体现了数学方法，以在图像中找到像素强度的亮度明显变化的点。 我们要做的第一件事是找到灰度图像的“渐变”，允许我们在x和y方向找到类似边缘的区域。 然后，我们将应用Canny边缘检测，多级降噪（模糊）过程，找到图像的梯度（利用水平和垂直方向上的Sobel核）、非最大抑制和滞后阈值。听起来似乎很难懂。这并不妨碍我们继续前进。但是，如果您对梯度和边缘检测背后的数学感兴趣，我鼓励您阅读算法。总的来说，它们并不复杂，并且可以深入了解OpenCV的幕后操作。 laplacian and sobelimport numpy as np import argparse import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) cv2.imshow(&quot;orginal&quot;,image) # Compute the Laplacian of the image lap = cv2.Laplacian(image,cv2.CV_64F) lap = np.uint8(np.absolute(lap)) cv2.imshow(&quot;Laplacian&quot;,lap) cv2.waitKey(0) 当计算梯度和边缘时，我们（通常）在单个通道上计算它们——在这种情况下，我们使用灰度图像；然而，我们也可以为RGB图像的每个通道计算梯度。为了简单起见，让我们继续使用灰度图像，因为这是大多数情况下将使用的。 我们使用拉普拉斯算法通过调用cv2.Laplacian函数来计算梯度幅度图像。第一个参数是我们的灰度图像——我们想要计算梯度幅度表示的图像。第二个参数是输出图像的数据类型。 前面，我们主要使用8位无符号整数。为什么我们现在使用64位浮点数？ 原因涉及图像中黑色到白色和白色到黑色的转换。 从黑色到白色的转变被认为是正斜率，而从白色到黑色的转变是负斜率。如果您还记得前面我们对图像算术的讨论，您就会知道8位无符号整数不代表负值。如果使用OpenCV，它将被剪裁为零，或者将使用NumPy执行模数运算。 这里简短的回答是，如果在计算梯度幅度图像时不使用浮点数据类型，则会丢失边缘，特别是白色到黑色的过渡。 为了确保捕获所有边，使用浮点数据类型，然后获取渐变图像的绝对值并将其转换回8位无符号整数。这绝对是一项重要的技术需要注意——否则你会丢失图像中的边缘！ 让我们继续计算Sobel梯度表示 # Compute gradients along the X and Y axis, respectively sobelX = cv2.Sobel(image,cv2.CV_64F,1,0) sobelY = cv2.Sobel(image,cv2.CV_64F,0,1) # The sobelX and sobelY images are now of the floating # point data type -- we need to take care when converting # back to an 8-bit unsigned integer that we do not miss # any images due to clipping values outside the range # of [0, 255]. First, we take the absolute value of the # graident magnitude images, THEN we convert them back # to 8-bit unsigned integers sobelX = np.uint8(np.absolute(sobelX)) sobelY = np.uint8(np.absolute(sobelY)) # We can combine our Sobel gradient images using our # bitwise OR sobelCombined = cv2.bitwise_or(sobelX,sobelY) # Show our Sobel images cv2.imshow(&quot;Sobel X&quot;,sobelX) cv2.imshow(&quot;Sobel Y&quot;,sobelY) cv2.imshow(&quot;Sobel Combined&quot;,sobelCombined) cv2.waitKey(0) 使用Sobel算子，我们可以计算沿x和y轴的梯度幅度表示，使我们能够找到水平和垂直边缘区域。 Sobel算子的第一个参数是我们想要计算梯度表示的图像。然后，就像上面的拉普拉斯算例一样，我们使用浮点数据类型。最后两个参数分别是x和y方向上导数的顺序。指定值1和0(y方向上的导数为0，平行于y轴？)以查找垂直边缘状区域，指定0和1以查找水平边缘状区域。 我们确保通过获取浮点图像的绝对值然后将其转换为8位无符号整数来找到所有边。 为了在x和y方向上组合梯度图像，我们可以应用按位OR。请记住，当任一像素大于零时，OR运算为真。因此，如果存在水平或垂直边缘，则给定像素将为True。 左上角：原始硬币图像。右上：沿x轴计算Sobel梯度大小（找到垂直边缘）。左下：沿y轴计算Sobel梯度（找到水平边缘）。右下：应用按位OR来组合两个Sobel表示。 canny edge detectorCanny边缘检测器是一个多步骤的过程。它涉及模糊图像以消除噪声，计算x和y方向上的Sobel梯度图像，抑制边缘，以及最后确定像素是否是“边缘样”的滞后阈值阶段。 新建一个canny.py文件 import numpy as np import argparse import cv2 # Construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&quot;-i&quot;, &quot;--image&quot;, required = True, help = &quot;Path to the image&quot;) args = vars(ap.parse_args()) # Load the image, convert it to grayscale, and blur it # slightly to remove high frequency edges that we aren&apos;t # interested in image = cv2.imread(args[&quot;image&quot;]) image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) image = cv2.GaussianBlur(image, (5, 5), 0) cv2.imshow(&quot;Blurred&quot;, image) # When performing Canny edge detection we need two values # for hysteresis: threshold1 and threshold2. Any gradient # value larger than threshold2 are considered to be an # edge. Any value below threshold1 are considered not to # ben an edge. Values in between threshold1 and threshold2 # are either classified as edges or non-edges based on how # the intensities are &quot;connected&quot;. In this case, any gradient # values below 30 are considered non-edges whereas any value # above 150 are considered edges. canny = cv2.Canny(image, 30, 150) cv2.imshow(&quot;Canny&quot;, canny) cv2.waitKey(0) 我们要做的第一件事是导入我们的包并解析我们的参数。然后我们加载图像，将其转换为灰度，并使用高斯模糊方法对其进行模糊处理。通过在边缘检测之前应用模糊，我们将帮助消除图像中我们不感兴趣的“嘈杂”边缘。我们这里的目标是只找到硬币的轮廓。 应用Canny边缘检测器通过使用cv2.Canny函数执行。我们提供的第一个参数是模糊的灰度图像。然后，我们需要提供两个值：threshold1和threshold2。 任何大于threshold2的梯度值都被认为是边缘。低于threshold1的任何值都被认为不是边缘。阈值1和阈值2之间的值基于其强度如何“连接”而被分类为边缘或非边缘。在这种情况下，任何低于30的梯度值都被认为是非边缘，而高于150的任何值都被认为是边缘。 请注意边缘是如何“更清晰”。与使用拉普拉斯算子或索贝尔梯度图像时相比，我们的噪声要少得多。此外，我们的硬币轮廓清晰显示。 用到的函数 cv2.Laplacian np.uint8 cv2.Sobel cv2.Canny 更多的参考：PPaO Chapter 9 – Thresholding Zero-parameter, automatic Canny edge detection with Python and OpenCV Histogram of Oriented Gradients and Object Detection]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Opencv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_12]]></title>
    <url>%2F%2F2018%2F11%2F23%2FOpenCV_python3_12.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 12 Thresholding阈值处理是图像的二值化。通常，我们寻求将灰度图像转换为二进制图像，其中像素为0或255 一个简单的阈值处理示例是选择像素值p，然后将小于p的所有像素强度设置为零，并将所有大于p像素值设置为255.这样，我们就能够创建图像的二进制表示。 通常，我们使用阈值处理来关注图像中特别感兴趣的对象或区域。使用阈值方法，我们将能够在图像中找到硬币。 simple thresholding应用简单的阈值方法需要人为干预。我们必须指定阈值T.所有低于T的像素强度都设置为0.并且所有大于T的像素强度都设置为255 我们还可以通过将低于T的所有像素设置为255并且将所有大于T的像素强度设置为0来应用该二值化的逆。 新建一个simple_threholding.py文件 import numpy as np import argparse import cv2 # Construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&quot;-i&quot;, &quot;--image&quot;, required = True, help = &quot;Path to the image&quot;) args = vars(ap.parse_args()) # Load the image, convert it to grayscale, and blur it slightly image = cv2.imread(args[&quot;image&quot;]) image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(image,(5,5),0) cv2.imshow(&quot;Image&quot;,image) 我们导入我们的包，解析我们的参数，并加载我们的图像。然后我们将图像从RGB颜色空间转换为灰度。此时，我们应用高斯模糊，σ=5半径。应用高斯模糊有助于消除图像中我们不关心的一些高频边缘。 (T,thresh) = cv2.threshold(blurred,155,255,cv2.THRESH_BINARY) cv2.imshow(&quot;Threshold Binary&quot;,thresh) (T,threshInv) = cv2.threshold(blurred,155,255,cv2.THRESH_BINARY_INV) cv2.imshow(&quot;Threshold Binary Inverse&quot;,threshInv) cv2.imshow(&quot;Coins&quot;,cv2.bitwise_and(image,image,mask=threshInv)) cv2.waitKey(0) 在图像模糊后，我们使用cv2.threshold函数计算阈值图像。 此方法需要四个参数。第一个是我们希望阈值的灰度图像。我们在这里提供模糊图像。 然后，我们手动提供T阈值。我们使用T=155的值。 我们的第三个参数是在阈值处理期间应用的最大值。任何大于T的像素强度p都设置为该值。在我们的示例中，任何大于155的像素值都设置为255.任何小于155的值都设置为零。 最后，我们必须提供一种阈值方法。我们使用cv2.THRESH_BINARY方法，该方法指示大于T的像素值p被设置为最大值（第三个参数）。 cv2.threshold函数返回两个值。第一个是T，我们为阈值手动指定的值。第二个是我们实际的阈值图像。 然后我们在上面左下中显示我们的阈值图像。我们可以看到我们的硬币现在是黑色像素，白色像素是背景。 接着，我们使用cv2.THRESH_BINARY_INV作为我们的阈值方法，应用逆阈值而不是正常的阈值。正如我们在上图右下角，我们的硬币现在是白色的，背景是黑色的。 我们要执行的最后一项任务是显示图像中的硬币并隐藏其他所有内容。 我们使用cv2.bitwise_and函数执行屏蔽。我们提供原始硬币图像作为前两个参数，然后我们的反转阈值图像作为我们的mask。请记住，mask仅考虑原始图像中mask大于零的像素。由于我们前面反转阈值图像在近似硬币所包含的区域方面做得很好，我们可以使用这个反转阈值图像作为我们的蒙版。 左上角，显示了应用我们mask的结果——硬币清晰显示，而其余图像被隐藏。 adaptive thresholding使用简单阈值法的缺点之一是我们需要手动提供阈值T。寻找一个好的T值不仅需要大量的手动实验和参数调整，如果图像在像素强度方面显示出很多范围，则没有太大帮助。 为了克服这个问题，我们可以使用自适应阈值处理，它考虑像素的小邻域，然后为每个邻域找到最佳阈值T. 该方法允许我们处理可能存在显着范围的像素强度的情况，并且T的最佳值可以针对图像的不同部分而改变。 新建一个adaptive_thresholding.py文件 import numpy as np import argparse import cv2 ap = argparse.ArgumentParser() ap.add_argument(&quot;-i&quot;,&quot;--image&quot;,required=True, help=&quot;path to the image&quot;) args = vars(ap.parse_args()) # Load the image, convert it to grayscale, and blur it slightly image = cv2.imread(args[&quot;image&quot;]) image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(image,(5,5,),0) cv2.imshow(&quot;Image&quot;,image) # In our previous example, we had to use manually specify a # pixel value to globally threshold the image. In this example # we are going to examine a neighborbood of pixels and adaptively # apply thresholding to each neighborbood. In this example, we&apos;ll # calculate the mean value of the neighborhood area of 11 pixels # and threshold based on that value. Finally, our constant C is # subtracted from the mean calculation (in this case 4) thresh = cv2.adaptiveThreshold(blurred,255, cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY_INV,11,4) cv2.imshow(&quot;Mean Thresh&quot;,thresh) # We can also apply Gaussian thresholding in the same manner thresh = cv2.adaptiveThreshold(blurred,255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY_INV,15,3) cv2.imshow(&quot;Gaussian Thresh&quot;,thresh) cv2.waitKey(0) 然后，我们使用cv2.adaptiveThreshold函数对我们的模糊图像应用自适应阈值。我们提供的第一个参数是我们想要阈值的图像。然后，我们提供最大值255，类似于上面提到的简单阈值。 第三个参数是我们计算当前像素邻域的阈值的方法。 通过提供cv2.ADAPTIVE_THRESH_MEAN_C，我们指出我们想要计算像素邻域的平均值并将其视为我们的T值。 接下来，我们需要我们的阈值方法。同样，该参数的描述与上述简单的阈值处理方法相同。 我们使用cv2.THRESH_BINARY_INV来指示邻域中任何大于T的像素强度应该设置为255，否则应该设置为0。 下一个参数是我们的邻域大小。此整数值必须为奇数，表示我们的像素邻域将有多大。我们提供的值为11，表明我们将检查图像的11×11像素区域，而不是像在简单的阈值方法中那样尝试全局阈值图像。 最后，我们提供一个简单称为C的参数。这个值是一个从均值中减去的整数，允许我们微调我们的阈值。我们在这个例子中使用C=4。 应用均值加权自适应阈值的结果可以在上图中间图像中看到。 除了应用标准平均阈值，我们也可以应用高斯(加权平均)阈值处理。但现在我们调整了一些值。我们使用cv2.ADAPTIVE_THRESH_GAUSSIAN_C来表示我们想要使用加权平均值，而不是提供cv2.ADAPTIVE_THRESH_ MEAN_C的值。我们还使用了15×15像素的邻域大小，而不是前面示例中的11×11邻域大小。我们还稍微改变了我们的C值（我们从平均值中减去的值）并使用3而不是4。 通常，在平均自适应阈值处理和高斯自适应阈值处理之间进行选择需要在您的最终进行一些实验。要改变的最重要的参数是邻域大小和C，您从平均值中减去的值。通过试验此值，您将能够显着更改阈值的结果。 otsu and riddler-calvard我们可以自动计算T的阈值的另一种方法是使用Otsu的方法。 Otsu的方法假设图像的灰度直方图中有两个峰值。然后它试图找到一个最佳值来分隔这两个峰值——也就是我们的T值。 虽然OpenCV为Otsu的方法提供了支持，但我更喜欢Luis Pedro Coelho在mahotas包中的实现，因为它更像是Pythonic。 新建一个otsu_and_riddler.py文件 from __future__ import print_function import numpy as np import argparse import mahotas import cv2 ap = argparse.ArgumentParser() ap.add_argument(&quot;-i&quot;,&quot;--image&quot;,required=True, help=&quot;path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(image,(5,5),0) cv2.imshow(&quot;Image&quot;,image) # OpenCV provides methods to use Otsu&apos;s thresholding, but I find # the mahotas implementation is more &apos;Pythonic&apos;. Otsu&apos;s method # assumes that are two &apos;peaks&apos; in the grayscale histogram. It finds # these peaks, and then returns a value we should threshold on. T = mahotas.thresholding.otsu(blurred) print(&quot;Otsu&apos;s threshold:{}&quot;.format(T)) 我们这里引入了mahotas，另一个图像处理包。然后处理我们解析参数和加载图像的标准做法。 为了计算T的最佳值，我们在mahotas.thresholding包中使用otsu函数。 正如我们的输出稍后将向我们展示的那样，Otsu的方法找到了我们将用于阈值处理的T=137的值。 # Applying the threshold can be done using NumPy, where values # smaller than the threshold are set to zero, and values above # the threshold are set to 255 (white). thresh = image.copy() thresh[thresh &gt; T] = 255 thresh[thresh &lt; 255] = 0 thresh = cv2.bitwise_not(thresh) cv2.imshow(&quot;Otsu&quot;,thresh) # An alternative is to use the Riddler-Calvard method T = mahotas.thresholding.rc(blurred) print(&quot;Riddler-Calvard:{}&quot;.format(T)) thresh = image.copy() thresh[thresh &gt; T] = 255 thresh[thresh &lt; 255] = 0 thresh = cv2.bitwise_not(thresh) cv2.imshow(&quot;Riddler-Calvard&quot;,thresh) cv2.waitKey(0) 应用阈值处理。首先，我们制作灰度图像的副本，以便我们有图像进行阈值。 然后，使任何值大于T的值都是white，接着将剩下的所有非白色的像素转换为黑色像素。然后我们使用cv2.bitwise_not来反转我们的阈值。这相当于应用cv2.THRESH_BINARY_INV阈值类型，如本章前面的例子。 Otsu方法的结果可以在下图的中间图像中看到。 我们可以清楚地看到图像中的硬币已经突出显示。 在找到T的最佳值时要记住的另一种方法是Riddler-Calvard方法。就像在Otsu的方法中一样，Riddler-Calvard方法也为T计算最佳值137.我们使用mahotas.thresholding中的rc函数应用此方法。如前面的例子中所示。鉴于Otsu和Riddler-Calvard的T值相同，下图右图中的阈值图像与中间的阈值图像相同。 用到的函数 cv2.threshold cv2.THRESH_BINARY cv2.adaptiveThreshold cv2.THRESH_BINARY_INV 更多的参考：PPaO Chapter 9 – Thresholding Thresholding: Simple Image Segmentation using OpenCV Watershed OpenCV]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Opencv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_11]]></title>
    <url>%2F%2F2018%2F11%2F23%2FOpenCV_python3_11.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 11 smoothing and blurring我很确定我们都知道模糊是什么。当您的相机拍摄失焦时会发生这种情况。图像中较清晰的区域会丢失其细节，通常为圆盘/圆形。 实际上，这意味着图像中的每个像素与其周围的像素强度混合在一起。邻域中像素的这种“混合”成为我们模糊的像素。 虽然这种效果在我们的照片中通常是不受欢迎的，但在执行图像处理任务时它实际上非常有用。 新建一个blurring.py文件 averaging我们要探索的第一个模糊方法是平均。 顾名思义，我们将在图像顶部定义一个k×k滑动窗口，其中k始终为奇数。此窗口将从左到右，从上到下滑动。然后将该矩阵中心的像素（我们必须使用奇数，否则不会有真正的“中心”）设置为围绕它的所有其他像素的平均值。 我们将此滑动窗口称为“卷积内核”或仅称为“内核”。我们将在本章中继续使用这个术语。 正如我们将看到的，随着内核大小的增加，我们的图像变得越模糊。 # Let&apos;s apply standard &quot;averaging&quot; blurring first. Average # blurring (as the name suggests), takes the average of all # pixels in the surrounding area and replaces the centeral # element of the output image with the average. Thus, in # order to have a central element, the area surrounding the # central must be odd. Here are a few examples with varying # kernel sizes. Notice how the larger the kernel gets, the # more blurred the image becomes blurred = np.hstack([ cv2.blur(image,(3,3)), cv2.blur(image,(5,5)), cv2.blur(image,(7,7)) ]) cv2.imshow(&quot;Averaged&quot;,blurred) cv2.waitKey(0) 使用3×3内核(左)，5×5内核(中)和7×7内核(右)执行平均模糊 为了平均模糊图像，我们使用cv2.blur函数。此函数需要两个参数：我们想要模糊的图像和内核的大小。我们使用不同大小的内核来模糊我们的图像。我们的内核越大，我们的图像就越模糊 我们使用np.hstack函数将输出图像堆叠在一起。此方法将我们的三个图像“水平堆叠”成一行。这很有用，因为我们不想使用cv2.imshow函数创建三个单独的窗口。 Gaussian接下来，我们将回顾高斯模糊。高斯模糊类似于平均模糊，但是我们现在使用加权平均值而不是使用简单均值，其中更接近中心像素的邻域像素对平均值贡献更多“权重”。 最终结果是我们的图像比使用上一节中讨论的平均方法更少模糊，但更自然地模糊。 继续前面的blurring.py编写 # We can also apply Gaussian blurring, where the relevant # parameters are the image we want to blur and the standard # deviation in the X and Y direction. Again, as the standard # deviation size increases, the image becomes progressively # more blurred blurred = np.hstack([ cv2.GaussianBlur(image,(3,3),0), cv2.GaussianBlur(image,(5,5),0), cv2.GaussianBlur(image,(7,7),0) ]) cv2.imshow(&quot;Gaussian&quot;,blurred) cv2.waitKey(0) 显示结果： 在这里你可以看到我们正在使用cv2.GaussianBlur函数。函数的第一个参数是我们想要模糊的图像。然后，类似于cv2.blur，我们提供一个表示内核大小的元组。同样，我们从3×3的小内核开始，并开始增加它到7x7。 最后一个参数是我们的σ，即x轴方向的标准偏差。通过将此值设置为0，我们指示OpenCV根据内核大小自动计算它们。 我们可以在上图看到高斯模糊的输出。与使用平均方法相比，我们的图像具有更少的模糊效果; 然而，由于加权平均值的计算，模糊本身更自然，而不是允许内核邻域中的所有像素具有相等的权重。 Median传统上，中值模糊方法在去除椒盐噪声时最有效。这种类型的噪音正是它听起来的样子：想象一下拍照，把它放在餐桌上，然后在上面洒上盐和胡椒。 使用中值模糊方法，您可以从图像中删除盐和胡椒。 在应用中值模糊时，我们首先定义内核大小k。然后，如在平均模糊方法中，我们考虑大小为k×k的邻域中的所有像素。但是，与平均方法不同，我们不是用邻域的平均值替换中心像素，而是用邻域的中值替换中心像素。 中值模糊在去除图像中的盐和胡椒样式噪声方面更有效，因为每个中心像素总是被图像中存在的像素强度替换。 平均和高斯方法可以计算邻域的平均值或加权平均值——该平均像素强度可能存在于邻域中，也可能不存在。但根据定义，中间像素必须存在于我们的邻域中。通过用中值而不是平均值替换我们的中心像素，我们可以大大降低噪声。 # The cv2.medianBlur function is mainly used for removing # what is called &quot;salt-and-pepper&quot; noise. Unlike the Average # method mentioned above, the median method (as the name # suggests), calculates the median pixel value amongst the # surrounding area. blurred = np.hstack([ cv2.medianBlur(image,3), cv2.medianBlur(image,5), cv2.medianBlur(image,7) ]) cv2.imshow(&quot;Median&quot;,blurred) cv2.waitKey(0) 显示效果： 通过调用cv2.medianBlur函数来实现应用中值模糊。此方法有两个参数：我们想要模糊的图像和内核的大小。我们从内核大小3开始，然后将其增加到5和7。 bilateral我们要探索的最后一种方法是双边模糊。 到目前为止，我们的模糊方法的目的是减少图像中的噪声和细节; 但是，我们往往会丢失图像中的边缘。 为了在保持边缘的同时降低噪音，我们可以使用双边模糊。双边模糊通过引入两个高斯分布来实现这一点。 第一高斯函数仅考虑空间邻居，即在图像的(x，y)坐标空间中出现在一起的像素。然后，第二高斯模型对邻域的像素强度进行建模，确保在模糊的实际计算中仅包括具有相似强度的像素。 总的来说，这种方法能够保留图像的边缘，同时还能降低噪点。这种方法的最大缺点是它比平均，高斯和中值模糊对应物慢得多。 # You may have noticed that blurring can help remove noise, # but also makes edge less sharp. In order to keep edges # sharp, we can use bilateral filtering. We need to specify # the diameter of the neighborhood (as in examples above), # along with sigma values for color and coordinate space. # The larger these sigma values, the more pixels will be # considered within the neighborhood. blurred = np.hstack([ cv2.bilateralFilter(image,5,21,21), cv2.bilateralFilter(image,7,31,31), cv2.bilateralFilter(image,9,41,41) ]) cv2.imshow(&quot;Bilateral&quot;,blurred) cv2.waitKey(0) 显示效果： 我们通过调用cv2.bilateralFilter函数来应用双边模糊。我们提供的第一个参数是我们想要模糊的图像。然后，我们需要定义像素邻域的直径。第三个参数是我们的颜色σ。颜色σ的值越大意味着在计算模糊时将考虑邻域中的更多颜色。最后，我们需要提供空间σ。较大的空间值σ意味着距离中心像素较远的像素将影响模糊计算，前提是它们的颜色足够相似。 用到的函数 np.hstack cv2.blur cv2.GaussianBlur cv2.medianBlur cv2.bilateralFilter 更多的参考：Numpy中stack()，hstack()，vstack()函数详解 PPaO Chapter 8——Smoothing and Blurring]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Opencv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_10]]></title>
    <url>%2F%2F2018%2F11%2F22%2FOpenCV_python3_10.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 10 直方图(historams)那么，直方图到底是什么？直方图表示图像中像素强度（无论是彩色还是灰度）的分布。它可以被可视化为给出强度（像素值）分布的高级直观的图表(graph)(或绘图(plot)) 在本例中，我们将假设RGB颜色空间，因此这些像素值将在0到255的范围内。 绘制直方图时，X轴作为我们的“箱(bins)”。如果我们构造一个有256个bins的直方图，那么我们就可以有效地计算每个像素值出现的次数。相反，如果我们仅使用2个(等间隔)二进制位，那么我们计算一个像素在[0,128]或[128,255]范围内的次数。The number of pixels binnedto the x-axis value is then plotted on the y-axis. 通过简单地检查图像的直方图，您可以大致了解对比度，亮度和强度分布。 using opencv to compute histograms我们将使用cv2.calcHist函数来构建直方图。 在我们进入任何代码示例之前，让我们快速回顾一下这个函数： cv2.calcHist(images,channels,mask,histSize,ranges) images: 我们想要计算直方图的图像，wrap it as a list:[myImage]. channels: 这是索引列表，我们在其中指定要为其计算直方图的通道的索引。要计算灰度图像的直方图，列表将为[0]。要计算所有三个红色，绿色和蓝色通道的直方图，通道列表将为[0,1,2]。 mask：如果提供mask，则仅计算mask像素的直方图。如果我们没有mask或者不想应用mask，我们可以提供None值。 histSize：这是我们在计算直方图时要使用的bins。同样，这是一个列表，我们正在为每个通道计算一个直方图。bins尺寸并非都必须相同。以下是每个通道分配32个bins的示例：[32,32,32]。 ranges：这里我们指定可能的像素值的范围。通常，对于每个通道，这是[0,256]，但如果您使用RGB以外的颜色空间(例如HSV)，则范围可能不同。 grayscale histogramsfrom matplotlib import pyplot as plt import argparse import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) cv2.imshow(&quot;Original&quot;,image) 解释：导入必要的库，显示原始图像并转为灰度图 hist = cv2.calcHist([image],[0],None,[256],[0,256]) plt.figure() plt.title(&quot;Grayscale Histogram&quot;) plt.xlabel(&quot;Bins&quot;) plt.ylabel(&quot;# of Pixels&quot;) plt.plot(hist) plt.xlim([0,256]) plt.show() cv2.waitKey(0) 解释：现在事情变得有趣了。我们计算实际直方图。参考前面的参数解释，这里，我们可以看到我们的第一个参数是灰度图像。灰度图像只有一个通道，因此通道的值为[0]。我们没有mask，因此我们将掩码值设置为None。我们将在直方图中使用256个bin，可能的值范围为0到256。 显示结果: 解释：我们如何解释这个直方图？bins(0-255)绘制在x轴上。并且y轴计算每个bin中的像素数。大多数像素落在大约60到120的范围内。查看直方图的右尾，我们看到200到255范围内的像素非常少。这意味着图像中只有很少的“白色”像素。 color histogramsfrom __future__ import print_function from matplotlib import pyplot as plt import numpy as np import argparse import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) cv2.imshow(&quot;Original&quot;,image) #Grab the image channels, initialize the tuple of colors #and the figure chans = cv2.split(image) colors = (&quot;b&quot;,&quot;g&quot;,&quot;r&quot;) plt.figure() plt.title(&quot;&apos;Flattened&apos; Color Histogram&quot;) plt.xlabel(&quot;Bins&quot;) plt.ylabel(&quot;# of Pixels&quot;) #Loop over the image channels for (chan,color) in zip(chans,colors): #Create a histogram for the current channel and plot it hist = cv2.calcHist([chan],[0],None,[256],[0,256]) plt.plot(hist,color=color) plt.xlim([0,256]) plt.show() 我们要做的第一件事是将图像分成三个通道：蓝色，绿色和红色。通常，我们读到这是红色，绿色，蓝色(RGB)。但是，OpenCV以相反的顺序将图像存储为NumPy数组：BGR。这一点很重要 然后我们初始化一个代表颜色的字符串元组。接着我们设置了PyPlot图。我们将在x轴上绘制bins，并在y轴上绘制放置在每个bin中的像素数量。 然后我们进行for循环，在循环里，我们开始循环遍历图像中的每个通道。然后，对于每个通道，我们计算直方图。该代码与计算灰度图像的直方图的代码相同; 但是，我们正在为每个红色，绿色和蓝色通道执行此操作，使我们能够表征像素强度的分布。 我们可以在上图中检查我们的颜色直方图。我们看到在bin 100周围的绿色直方图中存在尖峰。这表示在beach图像中的树木以及绿色植被有一个 darker green value。 我们还看到170到225范围内有很多蓝色像素。考虑到这些像素要lighter得多，我们知道它们来自我们海滩图像中的蓝天。同样地，我们看到25到50范围内的蓝色像素范围要小得多——这些像素更暗，因此是图像左下角的海洋像素。 到目前为止，我们一次只计算了一个通道的直方图。 现在我们继续进行多维直方图，并一次考虑两个通道。我喜欢用单词AND来解释多维直方图。 例如，我们可以提出一个问题，例如“有多少像素的红色值为10，蓝色值为30？”。有多少像素的绿色值为200，红色值为130？通过使用连接AND，我们能够构建多维直方图。 #Let&apos;s move on to 2D histograms -- I am reducing the #number of bins in the histogram from 256 to 32 so we #can better visualize the results fig = plt.figure() #Plot a 2D color histogram for green and blue ax = fig.add_subplot(131) hist = cv2.calcHist([chans[1],chans[0]],[0,1],None, [32,32],[0,256,0,256]) p = ax.imshow(hist,interpolation=&quot;nearest&quot;) ax.set_title(&quot;2D Color Histogram for G and B&quot;) plt.colorbar(p) #Plot a 2D color histogram for green and red ax = fig.add_subplot(132) hist = cv2.calcHist([chans[1],chans[2]],[0,1],None, [32,32],[0,256,0,256]) p = ax.imshow(hist,interpolation=&quot;nearest&quot;) ax.set_title(&quot;2D Color Histogram for G and R&quot;) plt.colorbar(p) #Plot a 2D color histogram for blue and red ax = fig.add_subplot(133) hist = cv2.calcHist([chans[0],chans[2]],[0,1],None, [32,32],[0,256,0,256]) p = ax.imshow(hist,interpolation=&quot;nearest&quot;) ax.set_title(&quot;2D Color Histogram for B and R&quot;) plt.colorbar(p) print(&quot;2D histogram shape : {}, with {} values&quot;.format( hist.shape,hist.flatten().shape[0])) 现在我们正在使用多维直方图，我们需要记住我们正在使用的bins的数量。在前面的例子中，我使用了256个bins来进行演示。但是，如果我们在2D直方图中为每个维度使用256个二进制位，则我们得到的直方图将具有256×256=65,536个单独的像素计数。这不仅浪费资源，而且不实用。 在计算多维直方图时，大多数应用程序使用8到64个区间。上面使用32个bins而不是256个bins。 通过检查cv2.calcHist函数的第一个参数，可以看出此代码中最重要的内容。在这里，我们看到我们传递了两个通道的列表：绿色和蓝色通道。 那么，如何在OpenCV中存储2D直方图？它实际上是一个2D NumPy数组。 由于我为每个通道使用了32个bin，我现在有一个32×32的直方图。 我们如何可视化2D直方图？如下图所示，其中我们看到三个图。第一个是绿色和蓝色通道的2D颜色直方图，第二个是绿色和红色，第三个是蓝色和红色。蓝色阴影表示低像素计数，而红色阴影表示大像素计数(即，2D直方图中的峰值)。我们倾向于在绿色和蓝色直方图中看到许多峰，其中x=22且y=12.这对应于植被和树木的绿色像素以及天空和海洋的蓝色。 print输出： 2D histogram shape : (32, 32), with 1024 values 使用2D直方图一次考虑两个通道。但是，如果我们想要考虑所有三个RGB通道呢？ # Our 2D histogram could only take into account 2 out # of the 3 channels in the image so now let&apos;s build a # 3D color histogram (utilizing all channels) with 8 bins # in each direction -- we can&apos;t plot the 3D histogram, but # the theory is exactly like that of a 2D histogram, so # we&apos;ll just show the shape of the histogram hist = cv2.calcHist([image], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256]) print(&quot;3D histogram shape: {}, with {} values&quot;.format( hist.shape, hist.flatten().shape[0])) # Show our plots plt.show() print输出： 3D histogram shape: (8, 8, 8), with 512 values 这里的代码非常简单——它只是上面代码的扩展。我们现在为每个RGB通道计算8×8×8直方图。我们无法想象这个直方图，但我们可以看到形状确实是(8,8,8)，有512个值。 histogram equalization(直方图均衡化)直方图均衡化通过“拉伸”像素的分布来改善图像的对比度。考虑一个在直方图中心有一个大峰值的。应用直方图均衡会将峰值拉伸到图像的角落，从而改善图像的整体对比度。直方图均衡应用于灰度图像。 当图像包含前景和背景都是dark或两者都是light时，此方法很有用。它往往会在照片中产生不切实际的影响; 但是，在增强医学或卫星图像的对比度时通常很有用。 新建一个equalize.py文本 import numpy as np import argparse import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;path to the image&quot;) args = vars(ap.parse_args()) # Load the image and convert it to grayscale image = cv2.imread(args[&quot;image&quot;]) image = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) # Apply histogram equalization to stretch the constrast # of our image eq = cv2.equalizeHist(image) # Show our images -- notice how the constrast of the second # image has been stretched cv2.imshow(&quot;Histogram Equalization&quot;,np.hstack([image,eq])) cv2.waitKey(0) 解释：使用单个函数执行直方图均衡：cv2.equalizeHist，它接受单个参数，即我们想要执行直方图均衡的灰度图像。最后几行代码显示我们的直方图均衡图像。 在左边，我们有原始的海滩图像 然后，在右边，我们有直方图均衡的海滩图像。注意图像的对比度是如何彻底改变的，现在跨越[0,255]的整个范围。 histograms and masks我们现在将构建一个mask并仅计算mask区域的颜色直方图。 新建一个histogram_with_mask.py文件 from matplotlib import pyplot as plt import numpy as np import argparse import cv2 def plot_histogram(image,title,mask = None): chans = cv2.split(image) colors = (&quot;b&quot;,&quot;g&quot;,&quot;r&quot;) plt.figure() plt.title(title) plt.xlabel(&quot;Bins&quot;) plt.ylabel(&quot;# of Pixels&quot;) # Grab the image channels, initialize the tuple of colors # and the figure for (chan,color) in zip(chans,colors): hist = cv2.calcHist([chan],[0],mask,[256],[0,256]) plt.plot(hist,color=color) plt.xlim([0,256]) 我们定义plot_histogram。此函数接受三个参数：图像，绘图标题和掩码。如果我们没有图像mask，则mask默认为None。 plot_histogram函数的主体只是为图像中的每个通道计算直方图并绘制它。既然我们有一个函数来帮助我们轻松绘制直方图，那么让我们进入大部分代码： ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) cv2.imshow(&quot;Original&quot;,image) plot_histogram(image,&quot;Histogram for Original Image&quot;) # Construct a mask for our image -- our mask will be BLACK for # regions we want to IGNORE and WHITE for regions we want to # EXAMINE. In this example we will be examining the foliage # of the image, so we&apos;ll draw a white rectangle where the foliage # is mask = np.zeros(image.shape[:2],dtype=&quot;uint8&quot;) cv2.rectangle(mask,(15,15),(130,100),255,-1) cv2.imshow(&quot;Mask&quot;,mask) masked = cv2.bitwise_and(image,image,mask=mask) cv2.imshow(&quot;Applying the Mask&quot;,masked) cv2.waitKey(0) 结果： 现在我们准备为图像构建一个mask。我们将mask定义为NumPy数组，其宽度和高度与海滩图像相同。然后，我们从点（15,15）到点（130,100）绘制一个白色矩形。这个矩形将用作我们的蒙版——在直方图计算中仅将属于蒙版区域的像素进行直方图计算。 为了可视化我们的蒙版，我们对海滩图像应用按位AND，其结果可以上图中看到。注意中间的图像只是一个白色矩形，但是当我们将mask应用到海滩图像时，我们只看到蓝天(最右)。 # Let&apos;s compute a histogram for our image, but we&apos;ll only include # pixels in the masked region plot_histogram(image, &quot;Histogram for Masked Image&quot;, mask = mask) # Show our plots plt.show() 最后，我们使用plot_histogram函数为我们的蒙版图像计算直方图并显示我们的结果。 结果： 我们可以在上图中看到我们的蒙版直方图。大多数红色像素落在[0,80]范围内，表明红色像素对我们的图像贡献很小。这是有道理的，因为我们的天空是蓝色的。然后存在绿色像素，但是再次朝向RGB光谱的较暗端。最后，我们的蓝色像素落在更亮的范围内，显然是我们的蓝天。 最重要的是，将上图的蒙版颜色直方图与上面没有应用mask的颜色直方图进行比较。注意颜色直方图有多么不同。通过使用mask，我们只能将计算应用于我们感兴趣的图像的特定区域——在这个例子中，我们只是想检查蓝天的分布。 用到的函数 cv2.calcHist add_subplot colorbar cv2.equalizeHist 更多的参考：PPaO Chapter 7 – Histograms matplotlib How-To: 3 Ways to Compare Histograms using OpenCV and Python The complete guide to building an image search engine with Python and OpenCV OpenCV Shape Descriptor: Hu Moments Example Building a Pokedex in Python: Indexing our Sprites using Shape Descriptors (Step 3 of 6)]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Opencv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_09]]></title>
    <url>%2F%2F2018%2F11%2F22%2FOpenCV_python3_09.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 09 颜色空间(color spaces)前面我们探讨了RGB颜色空间，但是还有许多其他的颜色空间我们可以利用。 Hue-Saturation-Value(色调-饱和度-值)(HSV)色彩空间更类似于人类思考和设想色彩的方式。然后是Lab颜色空间，它更适合人类感知颜色的方式。 新建一个colorspaces.py # Construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;path ot the image&quot;) args = vars(ap.parse_args()) # Load the image and show it image = cv2.imread(args[&quot;image&quot;]) cv2.imshow(&quot;Original&quot;,image) 读取输入参数，显示原始图片。 # Convert the image to grayscale gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY) cv2.imshow(&quot;Gray&quot;,gray) # Convert the image to the HSV (Hue, Saturation, Value) # color spaces hsv = cv2.cvtColor(image,cv2.COLOR_BGR2HSV) cv2.imshow(&quot;HSV&quot;,hsv) lab = cv2.cvtColor(image,cv2.COLOR_BGR2LAB) cv2.imshow(&quot;L*a*b&quot;,lab) cv2.waitKey(0) 我们通过指定cv2.COLOR_BGR2GRAY 标志将图像从RGB颜色空间转换为灰度。通过指定cv2.COLOR_BGR2HSV标志，将图像转换为HSV颜色空间。最后，我们使用cv2.COLOR_BGR2LAB标志转换为Lab颜色空间。 显示效果： 用到的函数 cv2.cvtColor 更多的参考：PPaO Chapter 6 – Image Processing Basic Image Manipulations in Python and OpenCV OpenCV and Python K-Means Color Clustering Color Quantization with OpenCV using K-Means Clustering Super fast color transfer between images Ball Tracking with OpenCV The complete guide to building an image search engine with Python and OpenCV Skin Detection: A Step-by-Step Example using Python and OpenCV]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Opencv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_08]]></title>
    <url>%2F%2F2018%2F11%2F21%2FOpenCV_python3_08.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 08 拆分和合并通道(splitting and merging channels)彩色图像由多个通道组成：红色，绿色和蓝色成分。我们已经看到我们可以通过索引到NumPy数组来访问这些成分。但是，如果我们想将图像分割成各自的成分呢？ 我们将使用cv2.split函数。如下图所示，我们有下面一个图像， 这张图片非常的蓝，编写代码，实现通道的提取 新建一个splitting_and_merging.py import numpy as np import argparse import cv2 # Construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;path to the image&quot;) args = vars(ap.parse_args()) 上面读取输入参数。 # Load the image and grab each channel: Red, Green, and Blue # NOTE: OpenCV stores an image as NumPy array with its # channels in reverse order! When we call cv2.split, we are # actually getting the channels as Blue, Green, Red! image = cv2.imread(args[&quot;image&quot;]) (B,G,R) = cv2.split(image) # Show each channel individually cv2.imshow(&quot;Red&quot;,R) cv2.imshow(&quot;Green&quot;,G) cv2.imshow(&quot;Blue&quot;,B) cv2.waitKey(0) # Merge the image back together again merged = cv2.merge([B,G,R]) cv2.imshow(&quot;Merged&quot;,merged) cv2.waitKey(0) cv2.destroyAllWindows() 通常，我们会想到RGB颜色空间中的图像——首先是红色，第二个是绿色，第三个是蓝色。但是，OpenCV以反向通道顺序将RGB图像存储为NumPy阵列。 它不是以RGB顺序存储图像，而是以BGR顺序存储图像; 因此我们以相反的顺序解包元组。然后将每个通道的图像显示出来。最后我们再将各个通道的图像合并成原图。 显示图像： 红色通道非常暗。这是有道理的，因为海洋场景中的红色很少。存在的红色要么非常暗，要么没有代表，要么非常light，并且可能是波浪崩溃时白色泡沫的一部分。 绿色通道在图像中更具代表性，因为海水确实包含绿色色调。 最后，蓝色通道非常light，在某些位置接近纯白色。这是因为我们的图像中大量呈现蓝色阴影。 # Now, let&apos;s visualize each channel in color zeros = np.zeros(image.shape[:2],dtype=&quot;uint8&quot;) cv2.imshow(&quot;Red&quot;,cv2.merge([zeros,zeros,R])) cv2.imshow(&quot;Green&quot;,cv2.merge([zeros,G,zeros])) cv2.imshow(&quot;Blue&quot;,cv2.merge([B,zeros,zeros])) cv2.waitKey(0) 显示效果： 上面代码可以看到另一种可视化图像通道的方法。为了显示通道的实际“颜色”，我们首先需要使用cv2.split拆分图像。然后，我们需要重新构建图像，但这次设置所有像素，但当前通道为零。 我们首先构造了一个零的NumPy数组，其宽度和高度与原始图像相同。然后，为了构造图像的红色通道表示，我们调用cv2.merge，但为绿色和蓝色通道指定我们的零数组。后面采取类似的方法。 用到的函数 cv2.split cv2.merge cv2.destroyAllWindows 更多的参考：PPaO Chapter 6 – Image Processing]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Opencv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_07]]></title>
    <url>%2F%2F2018%2F11%2F21%2FOpenCV_python3_07.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 07 MASKING接下来我们看一下masking technique。 使用mask可以让我们只关注我们感兴趣的图像部分。 例如，假设我们正在建立一个识别面部的计算机视觉系统。我们有兴趣查找和描述的图像的唯一部分是包含面部的图像部分——我们根本不关心图像的其余内容。如果我们可以在图像中找到面部，我们可以构造一个mask来仅显示图像中的面部。 新建一个masking.py # Import the necessary packages import numpy as np import argparse import cv2 # Construct the argument parser and parse the arguments ap = argparse.ArgumentParser() ap.add_argument(&quot;-i&quot;, &quot;--image&quot;, required = True, help = &quot;Path to the image&quot;) args = vars(ap.parse_args()) # Load the image and show it image = cv2.imread(args[&quot;image&quot;]) cv2.imshow(&quot;Original&quot;, image) 上面读取并显示原始图片，接下来我们构造一个NumPy数组，填充零，与我们的图像具有相同的宽度和高度。为了绘制白色矩形，我们首先通过划分宽度和高度来计算图像的中心，使用//运算符表示整数除法。最后，我们使用cv2.rectangle函数画出白色矩形。 # Masking allows us to focus only on parts of an image that # interest us. A mask is the same size as our image, but has # only two pixel values, 0 and 255. Pixels with a value of 0 # are ignored in the orignal image, and mask pixels with a # value of 255 are allowed to be kept. For example, let&apos;s # construct a mask with a 150x150 square at the center of it # and mask our image. mask = np.zeros(image.shape[:2], dtype = &quot;uint8&quot;) (cX, cY) = (image.shape[1] // 2, image.shape[0] // 2) cv2.rectangle(mask, (cX - 75, cY - 75), (cX + 75 , cY + 75), 255, -1) cv2.imshow(&quot;Mask&quot;, mask) 我们使用cv2.bitwise_and函数应用我们的mask。前两个参数是图像本身。显然，对于图像中的所有像素，AND功能将为True。但是，此函数的重要部分是mask关键字参数。通过提供mask，cv2.bitwise_and函数仅检查mask中“on”的像素。在这种情况下，只有矩形白色区域是显示出来的。 # Apply out mask -- notice how only the center rectangular # region of the pill is shown masked = cv2.bitwise_and(image, image, mask = mask) cv2.imshow(&quot;Mask Applied to Image&quot;, masked) cv2.waitKey(0) 接下来，我们重新初始化我们的蒙版，用零填充与图像相同的尺寸。然后，我们在mask image上绘制一个白色圆圈，从图像的中心开始，半径为100像素。然后再次使用cv2.bitwise_and函数应用圆形mask。 # Now, let&apos;s make a circular mask with a radius of 100 pixels mask = np.zeros(image.shape[:2],dtype=&quot;uint8&quot;) cv2.circle(mask,(cX,cY),100,255,-1) masked = cv2.bitwise_and(image,image,mask=mask) cv2.imshow(&quot;Mask&quot;,mask) cv2.imshow(&quot;Mask Applied to Image&quot;,masked) cv2.waitKey(0) 显示效果： 用到的函数 cv2.bitwise_and cv2.ractangle cv2.circle 更多的参考：PPaO Chapter 6 – Image Processing]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Opencv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_06]]></title>
    <url>%2F%2F2018%2F11%2F21%2FOpenCV_python3_06.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 06 按位运算(bitwise operations)现在我们将检查四个按位运算：AND，OR，XOR和NOT。 这四个操作虽然非常基础和低级，但对图像处理至关重要。 新建一个bitwise.py import numpy as np import cv2 rectangle = np.zeros((300,300),dtype=&quot;uint8&quot;) cv2.rectangle(rectangle,(25,25),(275,275),255,-1) cv2.imshow(&quot;Rectangle&quot;,rectangle) circle = np.zeros((300,300),dtype=&quot;uint8&quot;) cv2.circle(circle,(150,150),150,255,-1) cv2.imshow(&quot;Circle&quot;,circle) cv2.waitKey(0) 解释： 我们将矩形图像初始化为0×300NumPy数组。然后在图像的中心绘制一个250×250的白色矩形(实心)。类似地，我们初始化另一个图像以包含我们的圆，再次以图像的中心为中心，半径为150像素。 显示效果： bitwiseAnd = cv2.bitwise_and(rectangle,circle) cv2.imshow(&quot;AND&quot;,bitwiseAnd) cv2.waitKey(0) bitwiseOr = cv2.bitwise_or(rectangle,circle) cv2.imshow(&quot;OR&quot;,bitwiseOr) cv2.waitKey(0) bitwiseXor = cv2.bitwise_xor(rectangle,circle) cv2.imshow(&quot;XOR&quot;,bitwiseXor) cv2.waitKey(0) bitwiseNot = cv2.bitwise_not(circle) cv2.imshow(&quot;NOT&quot;,bitwiseNot) cv2.waitKey(0) 如上所述，一个像素 is turned “on” 如果它有一个大于0的值，否则是”turned off”,如果它有一个0值。Bitwise函数在这些二进制条件下运行。 为了利用按位函数，我们假设(在大多数情况下)我们正在比较两个像素（唯一的例外是NOT函数)。我们将比较每个像素，然后构造我们的按位表示。 AND:当且仅当两个像素都大于零时，按位AND为真 OR:如果两个像素中的任何一个大于零，则按位OR为真。 XOR:异或，取异，也就是两个不一样时才为真 NOT:按位NOT反转图像中的“开”和“关”像素。 首先，我们使用cv2.bitwise_and函数对我们的矩形和圆形图像应用按位AND。如上面的列表所示，当且仅当两个像素都大于零时，按位AND才为真。 我们按位AND的输出可以如下图所示。我们可以看到正方形的边缘丢失了，因为我们的矩形不会覆盖像圆圈那样大的区域，因此两个像素都不会“打开”。 显示效果: 然后，我们使用按位OR，bitwise_or函数。 如果两个像素中的任何一个大于零，则按位OR为真。下图显示了按位OR的输出。在这种情况下，我们的正方形和矩形已合并在一起。 显示效果： 接下来是按位XOR函数，使用cv2.bitwise_xor函数。取两个图形不一样的地方为真。其余为假，就是0，黑色。 显示效果： 最后，我们使用cv2.bitwise_not函数应用NOT函数。实质上，按位NOT函数会翻转像素值。所有大于零的像素都设置为零，所有设置为零的像素都设置为255.下图为我们的白色圆圈翻转为黑色圆圈。 显示效果： 用到的函数 cv2.bitwise_and cv2.bitwise_or cv2.bitwise_xor cv2.bitwise_not 更多的参考：PPaO Chapter 6 – Image Processing]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Opencv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_05]]></title>
    <url>%2F%2F2018%2F11%2F20%2FOpenCV_python3_05.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 05 图像算术(image arithmetic)我们都知道基本的算术运算，如加法和减法。但是在处理图像时，我们需要记住颜色空间和数据类型的限制。 例如，RGB图像具有落在[0,255]范围内的像素。那么如果我们正在检查强度为250的像素并尝试向它添加10，会发生什么？ 在正常的算术规则下，我们最终得到的值为260.但是，由于RGB图像表示为8位无符号整数，因此260不是有效值。 那么，会发生什么？我们是否应该执行某种检查以确保没有像素落在[0,255]范围之外，从而将所有像素剪切为最小值0和最大值255？ 或者我们应用模数运算，并“wrap around(环绕)”？在模数规则下，添加10到250将简单地回绕到值4。 哪种方式是处理超出[0,255]范围的图像添加和减法的“正确”方法？ 答案是没有正确的方法——它只取决于你如何操纵像素以及你想要的结果。 但是，请务必记住OpenCV和NumPy加法之间存在差异。NumPy将执行模运算和“warp around”。另一方面，OpenCV将执行裁剪并确保像素值永远不会超出范围[0,255] 请看代码： from __future__ import print_function import numpy as np import argparse import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) cv2.imshow(&quot;Original&quot;,image) print(&quot;max of 255: {}&quot;.format(cv2.add(np.uint8([200]),np.uint8([100])))) print(&quot;min of 0: {}&quot;.format(cv2.add(np.uint8([50]),np.uint8([100])))) print(&quot;wrap around: {}&quot;.format(np.uint8([200]) + np.uint8([100]))) print(&quot;wrap around: {}&quot;.format(np.uint8([50]) - np.uint8([100]))) 解释： 第一个print函数，我们定义了两个8位无符号整数的NumPy数组。第一个数组有一个元素：值为200.第二个数组也只有一个元素，但值为100.然后我们使用OpenCV的cv2.add方法将值一起添加。那么返回的值到底回事多少呢？那么，根据标准算术规则，我们认为结果应该是300，但是，请记住，我们正在使用8位无符号整数，其范围仅在[0,255]之间。由于我们使用的是cv2.add方法，OpenCV会为我们处理剪切，并确保添加产生的最大值为255.当我们执行此代码时，我们可以看到返回值是255。 第二个print函数，我们使用cv2.subtract执行减法。同样，我们定义了两个NumPy数组，每个数组都有一个元素，以及8位无符号整数数据类型。第一个数组的值为50，第二个数组的值为100。根据算术规则，返回值本该是-50，但是OpenCV再一次为我们进行裁剪，返回值会是0. max of 255: [[255]] min of 0: [[0]] 但是如果我们使用NumPy来执行算术而不是OpenCV会发生什么？ 第三个print函数，首先，我们定义两个NumPy数组，每个数组都有一个元素，以及8位无符号整数数据类型。 第一个数组的值为200，第二个数组的值为100.使用cv2.add函数，我们的添加将被剪切并返回值255。但是Numpy并不会执行裁剪。它会执行模运算并”warps around(环绕)”。一旦值达到255，Numpy将回绕到0并再一次向上技术，直到100 steps reached。 第四个print函数，在减法期间一旦达到0，模运算操作就会回绕并从255开始向后计数。 wrap around: [44] wrap around: [206] 现在我们已经在OpenCV和NumPy中探讨了图像算法的注意事项，让我们对实际图像执行算法并查看结果： M = np.ones(image.shape,dtype=&quot;uint8&quot;) * 100 added = cv2.add(image,M) cv2.imshow(&quot;Added&quot;,added) M = np.ones(image.shape,dtype=&quot;uint8&quot;) * 50 subtracted = cv2.subtract(image,M) cv2.imshow(&quot;Subtracted&quot;,subtracted) cv2.waitKey(0) 显示效果: 解释： 我们首先定义了一个NumPy数组，其大小与我们的图像相同 同样，我们肯定使用8位无符号整数作为我们的数据类型。为了用100的值而不是1来填充我们的矩阵，我们简单地将1的矩阵乘以100.最后，我们使用cv2.add函数将我们的100的矩阵添加到原始图像——从而增加每个像素强度 图像乘以100，但如果它们试图超过255，则确保所有值都被剪切到范围[0,255]。 同样的，我们再定义了一个NumPy数组，并将原始图像减去50个像素，最后，曾经是白色的像素现在看起来是灰色的。这是因为我们从像素中减去50并将它们推向RGB颜色空间的较暗区域。 用到的函数 cv2.add cv2.subtract 更多的参考：PPaO Chapter 6 – Image Processing]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Opencv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_04]]></title>
    <url>%2F%2F2018%2F11%2F16%2FOpenCV_python3_04.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 04 图像转换(image transformations)本节中，我们将介绍基本的图像转换。 这些是您可能应用于图像的常用技术，包括平移，旋转，调整大小，翻转和裁剪。 平移(Translation)我们首先介绍的是Translation。Translatioin是沿x和y轴移动图像。 使用Translation，我们可以向上，向下，向左或向右移动图像，以及上述任意组合！请看下面的代码(translation.py)： import numpy as np import argparse import imutils import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) cv2.imshow(&quot;Original&quot;,image) M = np.float32([[1,0,25],[0,1,50]]) shifted = cv2.warpAffine(image,M,(image.shape[1],image.shape[0])) cv2.imshow(&quot;Shifted Down and Right&quot;,shifted) M = np.float32([[1,0,-50],[0,1,-90]]) shifted = cv2.warpAffine(image,M,(image.shape[1],image.shape[0])) cv2.imshow(&quot;Shifted Up and Left&quot;,shifted) 解释: 主要从M——我们的的translation matrix讲起，该矩阵告诉我们的图像要进行平移多少像素(从左到右，从上到下)。该矩阵被定义为float32类型的数组，因为OpenCV希望该矩阵是一个float类型。The first row of the matrix is [1,0,tx]，其中tx是the number of pixels we will shift the image left or right，而负值则表示图像将向左平移，正值表示图像将向右平移。然后我们定义the second row of the matrix as [0,1,ty]，其中，ty是the number of pixels we will shift the image up or down。其中，负值表示图像向上平移，正值表示图像向下平移。 使用了上面的那个标记，我们看代码中，将tx=25,ty=50则意味着，我们将图像向右平移25个像素，向下平移50个像素。 我们定义好了平移矩阵之后，图像的实际平移是使用了cv2.warpAffine函数来执行，该函数的第一个参数是我们要进行平移的图像，第二个参数是我们的平移矩阵M，最后我们需要手动地提供图像的尺寸(width and height)作为第三个参数。 前面实现了图像的平移，但是代码太过冗余，我们新建一个.py文件来实现平移功能(imutils.py) import numpy as np import cv2 def translate(image,x,y): M = np.float32([[1,0,x],[0,1,y]]) shifted = cv2.warpAffine(image,M,(image.shape[1],image.shape[0])) return shifted 解释: 我们的平移方法有三个参数：我们要平移的图像，我们沿x轴移动的像素数，以及我们将沿y轴移动的像素数。然后，此方法定义我们的平移矩阵M，然后再应用实际移位。最后，我们返回移位后的图像。 修改translation.py的内容 shifted = imutils.translate(image,0,100) cv2.imshow(&quot;Shifted Down&quot;,shifted) cv2.waitKey(0) 运行结果： 旋转(Rotation)在这里，我们将使用θ来表示要旋转多少度。 rotate.py import numpy as np import argparse import imutils import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;Path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) cv2.imshow(&quot;Original&quot;,image) (h,w) = image.shape[:2] center = (w // 2, h // 2) M = cv2.getRotationMatrix2D(center,45,1.0) rotated = cv2.warpAffine(image,M,(w,h)) cv2.imshow(&quot;Rotated by 45 Degrees&quot;,rotated) M = cv2.getRotationMatrix2D(center,-90,1.0) rotated = cv2.warpAffine(image,M,(w,h)) cv2.imshow(&quot;Rotated by -90 Degrees&quot;,rotated) 解释: 前面还是导入必要的包以及解析输入参数和显示原始图像。 当我们旋转图像时，我们需要指定我们想要旋转的点。 在大多数情况下，您需要围绕图像的中心旋转; 我们首先获取图像的宽度和高度，因为OpenCV将图像读取为一个numpy数组，所以和矩阵类似，矩阵的行对应着高，也就是height=image.shape[0]，矩阵的列对应着图像的宽，也就是width=image.shape[1]，然后我们除以2，确定图像的中心位置。这里我们使用和C语言一样的除法，//表示整除法，以确保我们得到的是整数。 就像我们定义一个矩阵来平移图像一样，我们也定义了一个矩阵来旋转图像。 不是使用NumPy手动构造矩阵，而是调用cv2.getRotationMatrix2D方法。 cv2.getRotationMatrix2D函数有三个参数：第一个是我们想要旋转图像的点，在这里是图像的中心位置，然后我们指定需要旋转的角度θ，我们第一次是旋转了45度，最后一个参数图像的比例。我们还没有讨论调整图像的大小，但是在这里你可以指定浮点值，其中1.0表示使用相同的图像尺寸。 但是，如果指定值为2.0，则图像的大小将加倍。 类似地，值0.5将图像的大小减半。 一旦我们从cv2.getRotationMatrix2D函数获得旋转矩阵M，我们就可以使用cv2.warpAffine方法将旋转应用于我们的图像。此函数的第一个参数是我们想要旋转的图像。然后，我们指定旋转矩阵M以及图像的输出尺寸（宽度和高度）。然后，显示旋转了的图像。 显示效果为： 接下来为了让代码更加的pretty and Pythonic，我们在imutils.py中添加一个rotate方法 def rotate(image,angle,center=None,scale=1.0): (h,w) = image.shape[:2] if center is None: center = (w // 2 , h // 2) M = cv2.getRotationMatrix2D(center,angle,scale) rotated = cv2.warpAffine(image,M,(w,h)) return rotated 解释： 我们的rotate方法有四个参数。第一个是你的image。第二个是我们想要旋转图像的角度θ。我们提供两个可选的关键字参数，center和scale。center参数是我们希望旋转图像的点。如果提供了值None，则该方法自动选图像中心为旋转点。最后，scale参数用于处理在旋转期间是否应更改图像的大小。scale参数的默认值为1.0，这意味着不应调整大小。 再次修改rotate.py文件， rotated = imutils.rotate(image,180) cv2.imshow(&quot;Rotated by 180 Degrees&quot;,rotated) cv2.waitKey(0) 运行结果： 确实很pythonic！！！ Resizingimport numpy as np import argparse import imutils import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True,help=&quot;Path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) cv2.imshow(&quot;Original&quot;,image) 解释： 这个和前面一样，解析参数，读取图片并显示图片 r = 150.0 / image.shape[1] dim = (150,int(image.shape[0] * r)) resized = cv2.resize(image,dim,interpolation=cv2.INTER_AREA) cv2.imshow(&quot;Resized (Width)&quot;,resized) 解释： r表示the aspect ratio。这里image.shape[1]表示我们图片的宽度，上面的代码我们设置我们图片的新宽度为150个pixels，为了计算新高度与旧高度的比率，我们简单地将比率r定义为新宽度（150像素）除以旧宽度，接着为了保持宽高比，我们计算出高度(height)的变化为 height / width * 150。接下来调用resize函数，该函数的第一个参数是我们希望调整大小的图像，第二个参数是我们为新图像计算的尺寸。最后一个参数是我们的插值方法，这是在背后工作的算法 处理实际图像的大小调整方式。一般来说，我发现使用cv2.INTER_AREA在调整大小时获得最佳效果; 但是，其他适当的选择包括cv2.INTER_LINEAR，cv2.INTER_CUBIC和cv2.INTER_NEAREST。 r = 50.0 / image.shape[0] dim = (int(image.shape[1] * r),50) resized = cv2.resize(image,dim,interpolation=cv2.INTER_AREA) cv2.imshow(&quot;Resized (Height)&quot;,resized) cv2.waitKey(0) 解释： 这一段代码和前一段类似，只不过这一次是固定高度为50个像素，然后保持宽高比计算出宽度，显示图像。 resized = imutils.resize(image,width = 100) cv2.imshow(&quot;Resized via Function&quot;,resized) cv2.waitKey(0) 解释： 前面都是用了三行代码实现图像的resize，我们可以利用imutils.resize函数只需要一行即可实现一样的功能。 在imutils.py函数中实现： def resize(image,width=None,height=None,inter=cv2.INTER_AREA): dim = None (h,w) = image.shape[:2] if width is None and height is None: return image if width is None: r = height / float(h) dim = (int(w * r),height) else: r = width / float(w) dim = (width,int(h * r)) resized = cv2.resize(image,dim,interpolation=inter) return resized 解释： 第一个参数是我们想要调整大小的图像。然后，我们定义两个关键字参数，宽度和高度。这两个参数都不能为None，否则我们将不知道如何调整图像大小。我们还提供inter，这是我们的插值方法，默认为cv2.INTER_AREA。 显示效果： Flipping接下来我们要探索的图像转换是翻转图像。 我们可以围绕x或者翻转图像y轴，甚至两者。查看下图理解一下水平和垂直翻转的区别。 import argparse import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;Path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) cv2.imshow(&quot;Original&quot;,image) 解释：和前面一样，解析参数，显示原始图片 flipped = cv2.flip(image,1) cv2.imshow(&quot;Flipped Horizontally&quot;,flipped) flipped = cv2.flip(image,0) cv2.imshow(&quot;Flipped Vertically&quot;,flipped) flipped = cv2.flip(image,-1) cv2.imshow(&quot;Flipped Horizontally &amp; Vertically&quot;,flipped) cv2.waitKey(0) 我们通过调用cv2.flip函数来完成图像的翻转。cv2.flip方法需要两个参数：我们要翻转的图像和a flip code，用于确定我们如何翻转图片。 使用翻转代码值1表示我们将围绕y轴水平翻转图像。指定翻转代码为0表示我们想要围绕x轴垂直翻转图像。最后，使用负翻转代码翻转两个轴图像。 显示效果： Cropping当我们裁剪图像时，我们想要删除我们不感兴趣的图像的外部部分。我们可以使用NumPy数组切片来完成图像裁剪。 import numpy as np import argparse import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;Path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) cv2.imshow(&quot;Original&quot;,image) cropped = image[30:120,240:335] cv2.imshow(&quot;T-Rex Face&quot;,cropped) cv2.waitKey(0) 解释： 实际裁剪在一行代码上进行。我们提供NumPy数组切片以提取图像的矩形区域，从（240,30）开始到（335,120）结束。The order in which we supply theindexes to the crop may seem counterintuitive(有悖常理); 但请记住，OpenCV将图像表示为NumPy数组，其高度优先，宽度为次之。这意味着我们需要在x轴之前提供y轴值. 为了执行我们的裁剪，NumPy需要四个索引： 开始y：起始y坐标。在这种情况下，我们从y=30开始。 结束y：结束y坐标。我们将在y=120时结束我们的裁剪。 开始x：切片的起始x坐标。我们在x=240时开始裁剪 结束x：切片的结束x轴坐标。我们的切片在x=335处结束 显示效果: 用到的函数 cv2.warpAffine cv2.getRotationMatrix2D cv2.resize cv2.flip 更多的参考：chapter-5-drawing]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Opencv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_03]]></title>
    <url>%2F%2F2018%2F11%2F09%2FOpenCV_python3_03.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 03 代码import numpy as np import cv2 canvas = np.zeros((300,300,3),dtype=&quot;uint8&quot;) 解释: 前两个是导入python库，接着就构造一个Numpy数组，使用.zeros方法初始化一个300 rows 和 300 columns的矩阵(也就是说画布的大小为300x300=90000个像素大小)，同时还分配了3个channels，one for Red,Green,and Blue,respectively。同时需要注意的是数据类型，dtype。由于我们将图像表示为像素在[0,255]范围内的RGB图像，因此我们使用8位无符号整数或uint8。 green = (0,255,0) cv2.line(canvas,(0,0),(300,300),green) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) red = (0,0,255) cv2.line(canvas,(300,0),(0,300),red,3) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) 解释: 前面我们初始化了我们的画布，接下来我们开始画直线，首先我们定义了一个元组(tuple)来定义画笔的颜色,这里是绿色，然后我们调用.line方法，该方法的第一个参数是我们绘制的画布，第二个参数是该line的起点，我们设置开始点为(0,0)，我们还需要为该line提供一个结束点（第三个参数）。我们将结束点设置为（300,300)，最后一个参数是我们画笔的颜色，我们设置为绿色。接着将我们的图像显示出来，并且wait for a keypress. 第二段代码的.line方法的最后一个参数3就是我们画笔的thickness了，也就是画笔的厚度了。 显示效果为： cv2.rectangle(canvas,(10,10),(60,60),green) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) cv2.rectangle(canvas,(50,200),(200,225),red,5) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) blue = (255,0,0) cv2.rectangle(canvas,(200,50),(225,125),blue,-1) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) 解释: 第一段代码我们使用了.rectangle方法，该方法的第一个参数是我们要画的画布，第二个参数是我们的rectangle的starting(x,y)位置，这里我们设置我们rectangle的开始point为(10,10)，然后我们还得设置我们矩形的结束点为(60,60),这个时候我们定义了一个(60-10,60-10)=(50,50)大小的像素区域。最后一个就是我们矩形的颜色了。第二段代码的第一行的最后一个参数是画笔的厚度，第三段代码的最后一个参数表示绘制一个solid(实心)的矩形。 显示效果为： canvas = np.zeros((300,300,3),dtype=&quot;uint8&quot;) (centerX,centerY) = (canvas.shape[1] // 2 , canvas.shape[0] // 2) white = (255,255,255) for r in range(0,175,25): cv2.circle(canvas,(centerX,centerY),r,white) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) 解释: 我们首先初始化我们的画布，然后我们计算两个变量：centerX和centerY。这两个变量代表图像中心的(x,y)的坐标。我们首先获取图像的宽度(也就是列)，通过.shape[1]获取，然后获取图像的高度(也就是行)，通过.shape[0]获取，最后用//(取C语言的除法，不保留小数)除以2获取中心位置。获取了圆心的位置，接下来，用一个for循环，starting from 0 and ending at 150，每一次增加25的半径来画圆。.circle方法的第一个参数是我们的画布，然后我们将圆心坐标传给第二个参数，第三个参数是我们的半径大小r，最后一个是我们的圆的颜色。 显示效果为： for i in range(0,25): radius = np.random.randint(5, high = 200) color = np.random.randint(0, high = 256 , size = (3,)).tolist() pt = np.random.randint(0, high = 300, size=(2,)) cv2.circle(canvas,tuple(pt),radius,color,-1) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) 解释: 接下来我们画25个圆，用一个for循环来实现。首先是半径，我们使用Numpy的.random.randint方法生成[5,200)范围内的半径值，接着用该方法生成RGB颜色，范围是[0,255],为了得到三个随机的整数，而不是一个整数，我们传递关键字参数size=(3,),数字3表示有3个数据，这样我们就获取了由3个值为[0,255]的随机数组成的元组来表示我们的RGB颜色了。接着哦我们需要一个圆心的point，我们也设置pt为[0,300)的值，但是圆心只需要两个数字，即(x,y)即可，因此，size=(2,)。最后我们调用.circle方法来画我们的圆。 显示效果为： 运行程序 点击图片部分，按键盘任意键结束脚本。 完整的代码 import numpy as np import cv2 canvas = np.zeros((300,300,3),dtype=&quot;uint8&quot;) green = (0,255,0) cv2.line(canvas,(0,0),(300,300),green) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) red = (0,0,255) cv2.line(canvas,(300,0),(0,300),red,3) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) cv2.rectangle(canvas,(10,10),(60,60),green) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) cv2.rectangle(canvas,(50,200),(200,225),red,5) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) blue = (255,0,0) cv2.rectangle(canvas,(200,50),(225,125),blue,-1) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) canvas = np.zeros((300,300,3),dtype=&quot;uint8&quot;) (centerX,centerY) = (canvas.shape[1] // 2 , canvas.shape[0] // 2) white = (255,255,255) for r in range(0,175,25): cv2.circle(canvas,(centerX,centerY),r,white) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) for i in range(0,25): radius = np.random.randint(5, high = 200) color = np.random.randint(0, high = 256 , size = (3,)).tolist() pt = np.random.randint(0, high = 300, size=(2,)) cv2.circle(canvas,tuple(pt),radius,color,-1) cv2.imshow(&quot;Canvas&quot;,canvas) cv2.waitKey(0) 用到的函数 cv2.line cv2.rectangle cv2.circle 更多的参考：chapter-5-drawing 名词： Regions of Interest(ROIs) machine-readable zones(MRZs) 小测试：请实现下图效果，尽可能用少的代码。 答案下载地址]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Oepncv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_02]]></title>
    <url>%2F%2F2018%2F11%2F09%2FOpenCV_python3_02.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 02 基础说明什么是像素每个图像都由一组像素组成。 像素是图像的原始构建块。没有比像素更小的单位了。 通常，我们将像素视为出现在图像中给定位置的光的“颜色”或“强度”。 如果我们将图像视为网格，则网格中的每个方块都包含一个像素。 例如，假设我们有一个分辨率为500×300的图像。这意味着我们的图像表示为像素网格，有500行和300列。总体而言，我们的图像总共有 500×300 = 150,000像素。 大多数像素以两种方式表示：灰度(grayscale)和彩色(color)。在灰度图像中，每个像素具有0到255之间的值，其中0对应于“黑色”而255对应于“白色”。 0到255之间的值是不同的灰色阴影，其中，接近0的更加的darker，接近于255更加的lighter。 彩色通常以RGB颜色空间表示，one value for the Red component,one for Green,and one for Blue。 RGB中的每一种都由0到255范围内的整数表示，这表示颜色的“多少”。像素值只需要在[0,255]范围内，我们通常使用8位无符号整数来表示每种颜色强度。 然后，我们将这些值组合成图形中的RGB(红色，绿色，蓝色）元组(tuple)。 这个元组就代表我们的颜色。 为了构建一个白色，我们将完全填充每个红色，绿色和蓝色 buckets，如下所示:(255,255,255）。为了创建一个黑色，我们将每个bucket都清空：(0,0,0) ，为了创造一种纯红色，我们将完全填满红色的bucket：(255,0,0）。 coordinate system(坐标系统)如上所述，图像表示为像素网格。 想象一下我们的网格作为一张方格纸。 使用该方格纸，点(0,0）对应于图像的左上角。 当我们向下和向右移动时，x和y值都会增加。(Python语言是零索引的，这意味着我们总是从零开始计数。)，看下图： 字母“I”放在一张图纸上.像素是通过他们的(x，y）坐标访问的，我们向右走x列，向下走y行(因为x是横坐标，y是纵坐标)，记住Python是零索引的：我们从零而不是一开始计数。 代码from __future__ import print_function import argparse import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;Path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) cv2.imshow(&quot;Original&quot;,image) 请记住，OpenCV将图像表示为NumPy数组。概念上，我们可以将此表示视为一个矩阵。为了访问像素值，我们只需要提供我们感兴趣的像素的x和y坐标。但是，重要的是要注意OpenCV以相反的顺序存储RGB channels。 虽然我们通常用Red，Green和Blue(RGB)来思考，但OpenCV实际上按Blue，Green和Red的顺序存储它们(BGR)。如下面的代码： (b,g,r) = image[0,0] print(&quot;Pixel at (0,0) - Red:{}, Green： {}， Blue: {}&quot;.format( r,g,b)) image[0,0] = (0,0,255) (b,g,r) = image[0,0] print(&quot;Pixel at (0,0) - Red: {} , Green: {}, Blue: {}&quot;.format(r, g,b)) 解释： 我们首先抓取图像的左上角的像素，即(0,0)的位置。 这个像素表示为元组.同时，OpenCV以相反的顺序存储RGB像素，因此当我们解包并访问元组中的每个元素时，我们实际上是以BGR顺序查看它们。然后我们将像素RGB颜色打印出来。 接下来，我们操纵图像中的左上角像素，该像素位于坐标（0,0）处，并将其设置为(0,0,255)。 如果我们以RGB格式读取这个像素值，我们的红色值为0，绿色值为0，蓝色值为255，因此使其成为纯蓝色。但是，正如我上面提到的，在使用OpenCV时我们需要特别注意。 我们的像素实际上以BGR格式存储，而不是RGB格式。我们实际上将这个像素读为255为红色，0为绿色，0为蓝色，使其成为红色，而不是蓝色。 corner = image[0:100,0:100] cv2.imshow(&quot;Corner&quot;,corner) image[0:100,0:100] = (0,255,0) cv2.imshow(&quot;Updated&quot;,image) cv2.waitKey(0) 解释： 接下来我们使用NumPy的数组切片功能来访问图像的较大矩形部分。为了访问图像较大的部分，Numpy希望我们提供四个索引值，分别是Start y,End y,Start x以及End x。 最后，运行程序即可。 效果图： 点击图片部分，按键盘任意键结束脚本。 完整的代码 from __future__ import print_function import argparse import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;Path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) cv2.imshow(&quot;Original&quot;,image) (b,g,r) = image[0,0] print(&quot;Pixel at (0,0) - Red:{}, Green： {}， Blue: {}&quot;.format( r,g,b)) image[0,0] = (0,0,255) (b,g,r) = image[0,0] print(&quot;Pixel at (0,0) - Red: {} , Green: {}, Blue: {}&quot;.format(r, g,b)) corner = image[0:100,0:100] cv2.imshow(&quot;Corner&quot;,corner) image[0:100,0:100] = (0,255,0) cv2.imshow(&quot;Updated&quot;,image) cv2.waitKey(0) 用到的函数 imread imshow waitKey 更多的参考：chapter-4-image-basics why-does-opencv-use-bgr-color-format 总结起来就是： 有一些技术原因，但总的来说，为什么开发人员选择BGR格式的原因是因为在BGR中指定颜色值而不是RGB在当时更受欢迎 - 这就是全部。]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>Oepncv</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[OpenCV_python3_01]]></title>
    <url>%2F%2F2018%2F11%2F08%2FOpenCV_python3_01.html</url>
    <content type="text"><![CDATA[Practical Python and OpenCV,3rd Edition 01 load、display、savefrom __future__ import print_function import argparse import cv2 解释： 从future package中导入print_function，是因为我们将使用实际的print() function，而不是print statement，这样我们的代码就可以在python2.7以及python3中共同运行。 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;Path to the image&quot;) args = vars(ap.parse_args()) 解释： 使用“–image”参数，也就是我们图像在磁盘的路径，我们将这个路径进行parse，然后将他们存储在一个字典中。 image = cv2.imread(args[&quot;image&quot;]) print(&quot;height: {} pixels&quot;.format(image.shape[0])) print(&quot;width : {} pixels&quot;.format(image.shape[1])) print(&quot;channels : {}&quot;.format(image.shape[2])) cv2.imshow(&quot;Image&quot;,image) cv2.waitKey(0) 解释： cv2.imread函数将返回一个Numpy数据，代表着图像。对于Numpy数组，我们可以使用shape属性来获取图像的width、height以及channels的数量。imshow函数将我们的图像显示在Windows窗口中，它的第一个参数是”name” of our window.第二个参数是我们从磁盘加载的图像了。而waitKey函数会暂停我们的脚本程序，直到我们在键盘上按下一个key之后才继续执行，而参数0则表示我们按键盘上的任意键都可以继续执行脚本程序。 cv2.imwrite(&quot;newimage.jpg&quot;,image) 解释： 最后我们使用imwrite函数将我们的保存为jpg格式的图像，第一个参数是我们要保存的图像的路径名，第二个是我们希望保存的图像。 最后执行脚本程序： 显示效果图片 停止脚本程序很简单，就如前面所说的，在显示的图片的任意地方按键盘上的任意键即可。然后查看脚本目录，你可以看到一个newimage.jpg的图片 完整的代码 from __future__ import print_function import argparse import cv2 ap = argparse.ArgumentParser() ap.add_argument(&apos;-i&apos;,&quot;--image&quot;,required=True, help=&quot;Path to the image&quot;) args = vars(ap.parse_args()) image = cv2.imread(args[&quot;image&quot;]) print(&quot;height: {} pixels&quot;.format(image.shape[0])) print(&quot;width : {} pixels&quot;.format(image.shape[1])) print(&quot;channels : {}&quot;.format(image.shape[2])) cv2.imshow(&quot;Image&quot;,image) cv2.waitKey(0) cv2.imwrite(&quot;newimage.jpg&quot;,image) 用到的函数 imread imshow waitKey imwrite danger no-icon 在上面的代码中，height对应于shape[0]，width对应于shape[1]。也就是Numpy 的shape似乎和自己想的不一样(specifying the height before the width)。但是，就matrix definition而言，这实际上是有意义。因为当我们定义矩阵的时候，我们通常将它们写成(# of rows x # of columns)的形式。这里，我们的图片有height：400 pixels(the number of rows) 以及 width：400 pixels(the number of columns). 更多的参考：loading-displaying-and-saving How-To: OpenCV Load an Image Python Command Line Arguments How to Display a Matplotlib RGB Image Resolved: Matplotlib figures not showing up or displaying]]></content>
      <categories>
        <category>计算机视觉</category>
      </categories>
      <tags>
        <tag>OpenCV</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[git学习之路_2_文件管理]]></title>
    <url>%2F%2F2018%2F10%2F29%2F%E6%96%87%E4%BB%B6%E7%AE%A1%E7%90%86.html</url>
    <content type="text"><![CDATA[时光穿梭前面我们已经添加并提交了一个readme.txt文件，现在我们将文件进行修改，改为下面的内容： 现在，我们运行命令git status看一看会有什么结果： git status命令可以让我们时刻掌握仓库当前的状态，从上图中可以看出来，redme.txt文件显然被修改过了，但是我们并没有准备提交我们的修改。如果我们不知道修改了什么样的内容，我们可以运行命令git diff来查看具体修改了什么样的内容。 diff是英文difference的缩写，上图中显示的格式是Unix通用的diff格式，知道了我们对readme.txt文件做了那些修改，接下来我们把文件提交到仓库中，一样是两个步骤，第一步运行命令git add git add readme.txt 接下来在执行git commit命令之前，我们用命令git status看看当前仓库的状态 上面的git status命令告诉我们，将要被提交的修改包括readme.txt，接下来，我们可以放心的提交了： 进行了提交之后，我们再用git status命令来查看仓库的当前状态 Git告诉我们当前没有需要提交的修改，而且，工作目录是干净的(working tree clean) 本节命令 git status：掌握工作区的状态 ‘git diff：查看修改的内容 参考来源Git 时光穿梭 Git cheat sheet英文版 Git cheat sheet中文版]]></content>
      <categories>
        <category>git</category>
      </categories>
      <tags>
        <tag>git</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[git学习之路_1_安装git以及创建版本库]]></title>
    <url>%2F%2F2018%2F10%2F28%2F%E5%AE%89%E8%A3%85git%E4%BB%A5%E5%8F%8A%E5%88%9B%E5%BB%BA%E7%89%88%E6%9C%AC%E5%BA%93.html</url>
    <content type="text"><![CDATA[windows上安装Git下载git for windows，新手默认安装即可。 官方网址为：https://gitforwindows.org/ 安装完成之后，下一步就应该告诉git你来自哪里。因为Git是分布式版本控制系统，所以每个机器必须自报家门，即告诉git，你的名字和Email地址，命令为： git config --global user.name &quot;your name&quot; git config --global user.email &quot;your email&quot; 注意：(引号内请输入你自己设置的名字和自己的邮箱),此用户名和邮箱是git提交代码时用来显示你的身份和联系方式的，并不是github用户名和邮箱。其中--global参数表示你这台及其上所有的git仓库都会使用这个配置。 创建版本库首先这里再明确一下，所有的版本控制系统，其实只能跟踪文本文件的改动，比如TXT文件，网页，所有的程序代码等等，Git也不例外。版本控制系统可以告诉你每次的改动，比如在第5行加了一个单词“Linux”，在第8行删了一个单词“Windows”。而图片、视频这些二进制文件，虽然也能由版本控制系统管理，但没法跟踪文件的变化，只能把二进制文件每次改动串起来，也就是只知道图片从100KB改成了120KB，但到底改了啥，版本控制系统不知道，也没法知道。 在Windows系统上，为了避免各种莫名其妙的问题，请确保目录名(不管是子目录还是父目录)不要包含中文。 之后使用命令git init来将这个目录变成Git可以管理的仓库。 Git仓库建好了，还是一个空的仓库，同时在该目录下会有一个隐藏的.git目录，这个目录是用来跟踪管理版本库的，没事千万别修改哦！ 把文件添加到版本库中 首先是文本编辑器的编码问题，强烈推荐使用UTF-8编码，而在Windows上不用使用Windows自带的记事本，可以选择notepad++，将默认编码改为：UTF-8 without BOM即可 让我们来新建一个readme.txt文件，输入下面的内容， 注意：这个文件一定要放在init的目录(或者子目录也行)下(否则git不能管理)，接着，把这个文件放到git仓库中只需要两步即可。 用git add告诉Git，把该文件添加到仓库中 git add readme.txt 用git commit告诉Git，把文件提交到仓库中 git commit -m “wrote a readme file” 上述命令的-m表示本次提交的说明(comment),可以输入任何内容，有意义最好，这样你就可以从历史记录里面方便地找到改动的记录。 在执行完git commit命令之后，就会告诉你，1 file changed:1个文件被改动(我们添加了readme.txt)；2 insertions：插入了两行内容(readme.txt有两行内容) 小贴士 为什么Git添加问价需要先add，再commit呢？因为commit命令可以一次提交很多文件(将add进去的文件都提交上去)，所以你可以多次add不同的文件。 比如： git add file1.txt file2.txt git commit -m &quot;add 2 files&quot; 参考来源Git 简介 Git cheat sheet英文版 Git cheat sheet中文版]]></content>
      <categories>
        <category>git</category>
      </categories>
      <tags>
        <tag>git</tag>
      </tags>
  </entry>
  <entry>
    <title><![CDATA[markdown语法学习]]></title>
    <url>%2F%2F2018%2F10%2F26%2Fmarkdown%E8%AF%AD%E6%B3%95%E5%AD%A6%E4%B9%A0.html</url>
    <content type="text"><![CDATA[欢迎使用MarkdownPad编辑阅读器 粗体和斜体使用 一对* 或者 _ 表示包围的字体斜体显示而一对** 或者__ 来表示粗体。 例如： *你好，世界* **你好，世界** 显示效果为： 你好，世界 你好，世界 分级标题Markdown 的标题有Setext和Atx两种语法形式，在Setex中，在文本下面标注=表示最高级标题，在下面标注-表示第二级标题，比如下面的Setext形式输出一级标题和二级标题 Headline 1 ========== Headline 2 -- 当然也可以使用Atx形式输出各级标题 #Headline 1 ##Headline 2 段落Markdown中使用空白行来分割段落，比如下面两端文本，只需要在两端之间加上一行空行，Markdown就会为文本分段 Hello world 我是空白行 你好啊世界 显示效果为： Hello world 你好啊世界 字体Markdown支持HTML嵌套，我们可以利用HTML标记实现更改颜色的需求，更改颜色代码如下： Default Color &lt;font color=&apos;red&apos;&gt;Red Color&lt;/font&gt; &lt;font color=&apos;blue&apos;&gt;Blue Color&lt;/font&gt; &lt;font color=&apos;green&apos;&gt;Green Color&lt;/font&gt; &lt;font color=&apos;yellow&apos;&gt;Yellow Color&lt;/font&gt; &lt;font color=&apos;pink&apos;&gt;Pink Color&lt;/font&gt; &lt;font color=&apos;purple&apos;&gt;Purple Color&lt;/font&gt; &lt;font color=&apos;orange&apos;&gt;Orange Color&lt;/font&gt; 显示效果为： Red Color Blue Color Green Color Yellow Color Pink Color Purple Color Orange Color 更改字号、字体也可以使用HTML轻松实现 &lt;font size=&apos;-2&apos;&gt;Small Size&lt;/font&gt; Normal Size &lt;font size=&apos;+2&apos;&gt;Big Size&lt;/font&gt; &lt;font size=&apos;+2&apos; face=&apos;楷体&apos;&gt;楷体&lt;/font&gt; 输出显示为： Small Size Normal Size Big Size 楷体 引用Markdown使用email的区块引用方式，即右尖括号&gt;后面跟引用的内容，如下 &gt;Hello World &gt;你好，世界 其输出为： Hello World 你好，世界 列表Markdown中使用型号* , 加号+ 以及减号- 来表示无序列表(中间有空格) * 我是列表 + 我也是列表 - 我还是列表 其输出为： 我是列表 我也是列表 我还是列表 有序列表使用一个数字加一个英文句点作为项目标记，比如 1. 我是列表 2. 我也是列表 其输出如下： 我是列表 我也是列表 同时，列表也是可以进行嵌套使用的(中间不用空格)，比如： 1.你好，世界 &gt;你好，世界 2.Hello World &gt;Hello World 输出结果为： 1.你好，世界 你好，世界 2.Hello World Hello World 链接Markdown支持行内和参考两种形式的链接语法，两种都是使用中括号来把文字转成链接，行内形式是中括号包围文字，后面紧跟圆括号包围的链接，其代码如下所示： [我的博客](https://0leo0.github.io/) 其输出为： 我的博客 当然，我们也可以给我们的链接加上一个title属性， [我的博客](https://0leo0.github.io/ &quot;我是一个标题&quot;) 输出如下： 我的博客 参考形式的链接可以在原文中为链接定义一个名称，然后在文章的其他地方定义该链接的内容，其语法格式为 [链接文本][链接名称] 我想搜索关于Python的内容，可以去[Google][1],以及[Yahoo][2]和[Baidu][3] 然后在别的地方定义链接内容，语法格式为[链接名称]:空白符 URL &quot;title&quot; [1]: https://google.com/ &quot;Google&quot; [2]: https://yahoo.com/ &quot;Yahoo&quot; [3]: https://baidu.com/ &quot;Baidu&quot; 显示效果为： 我想搜索关于Python的内容，可以去Google,以及Yahoo和Baidu 另外，使用&lt;&gt; 包括的URL或者邮箱地址会被自动转换为超链接 &lt;https://0leo0.github.io/&gt; &lt;wen_9407@yahoo.com&gt; 效果如下： https://0leo0.github.io/ &#x77;&#101;&#110;&#95;&#57;&#x34;&#48;&#x37;&#x40;&#121;&#97;&#104;&#111;&#x6f;&#46;&#99;&#x6f;&#109; 图片图片的语法格式和链接类似，也分为行内形式和参考形式。 行内形式语法格式为：![alt text](URL title),其中alt,text以及text都可以选择性的加入，但URL必须有 ![我要显示图片](https://imgchr.com/i/iy5Th9) 显示的效果如下： 参考形式分为两部分，声明图片链接名称和定义图片链接 其中声明图片链接语法格式为：![alt text][id] 定义图片链接内容的语法格式为： [id]:URL &quot;title&quot;. 代码在一般段落文字中，可以使用反引号`来标记代码区段。 我喜欢这个世界`&lt;blank&gt;`，哈哈 显示效果 我喜欢这个世界&lt;blank&gt;，哈哈 在Markdown中，如果行开头有4个空格，将被视为代码。但是这种方式，不推荐，我们推荐的方式是代码块的首行用3个反引号`和编程语言名称(C、Python等)标记代码块开始，代码块的结尾用3个反引号来闭合代码块。 比如，将一段python代码插入到Markdown，首行用3个反引号来标记代码块，最后一行再用3个反引号来闭合代码块。 12345678import argparseparser = argparse.ArgumentParser(description="calculate X to the power of Y")parser.add_argument('square',type=int,\ help="display a square of a given number")parser.add_argument('-v',"--verbosity",type=int,choices=[0,1,2],\ default=1,help="increase output verbosity")args = parser.parse_args()answer = args.square ** 2 那么其显示效果为： 1234567891011121314import argparseparser = argparse.ArgumentParser(description="calculate X to the power of Y")parser.add_argument('square',type=int,\ help="display a square of a given number")parser.add_argument('-v',"--verbosity",type=int,choices=[0,1,2],\ default=1,help="increase output verbosity")args = parser.parse_args()answer = args.square ** 2 其他考虑HTML和CSS(使用列表的话，下面的会显示出作用，而不是以代码的形式显示出来) 1.分割线和空行 /*分割线*/ &lt;hr /&gt; /*空行*/ &lt;br /&gt; 2.引用 &lt;blockquote&gt;引用内容&lt;/blockquote&gt; /*如果上下间距很小，可以加个P*/ &lt;p&gt;&lt;blockquote&gt;引用内容&lt;/blockquote&gt;&lt;/p&gt; 3.居中与右对齐 /*居中*/ &lt;center&gt;内容&lt;/center&gt; /*右对齐*/ &lt;p style=&quot;text-align:right&quot;&gt;内容&lt;/p&gt; 4.字体大小和颜色 &lt;font colr=&quot;#xxxxxx&quot; size=&quot;numbr&quot;&gt;内容&lt;/font&gt; //详细请查看W3schcool：https://www.w3school.com.cn/tags/tag_font.asp 5.Todo list &lt;ul&gt; &lt;li&gt;&lt;i class=&quot;fa fa-check-square&quot;&gt;&lt;/i&gt;已完成&lt;/li&gt; &lt;li&gt;&lt;i class=&quot;fa fasquare&quot;&gt;&lt;/i&gt;未完成&lt;/li&gt; &lt;/ul&gt; Markdown 高阶语法内容目录在段落中填写[TOC]以显示全文内容的目录结构 [TOC] 标签分类在编辑区任意行的的列首位置输入以下代码给文稿标签： 标签: 数学 英语 Markdown 或者 Tags: 数学 英语 Markdown 删除线使用~~ 表示删除线。 ~~ 这是一段错误的文本 ~~ 脚注使用[^keyword]表示脚注 这是一个脚注1的样例 LaTex公式$表示行内公式 质能守恒方程 $E=mc2 这里的上标我使用sup / sup，用四个尖括号括起来，同样下标为sub 而$$表示整行公式，具体参考MathJax 参考网址：Cmd Markdown简明语法手册 Markdown入门基础 fontawesome reuixiy 后话 第一篇markdown写的文章弄的我好辛苦，主要是用markdownpad编辑好的和hexo解析出的html不一样，在网页上看到的不是自己想要的，后面希望会好一点吧！！]]></content>
      <categories>
        <category>markdown</category>
      </categories>
      <tags>
        <tag>markdown</tag>
      </tags>
  </entry>
</search>
