2023年3月

leaflet:一个开源并且对移动端友好的交互式地图 JavaScript 库

中文文档:
https://leafletjs.cn/reference.html
官网(英文):
https://iclient.supermap.io/examples/leaflet/examples.html#iServer

该项目基于vue3+ts搭建

项目地址 gitee:
https://gitee.com/philippines-kisses-snow/leaflet-map

地图组成与名词解释

建议在学习之前先了解一些相关名词,以便理解:
高德官网的名词解释:
https://lbs.amap.com/api/javascript-api/guide/abc/components

效果:
image

下载库:

npm i leaflet

引入css:

// main.ts
import "leaflet/dist/leaflet.css"

在组件中使用leaflet:

  1. 引入:
import L from 'leaflet'

若引入时leaflet没有类型文件报错,需在.d.ts文件中加入:

// shims-vue.d.ts
declare module 'leaflet';
  1. 添加HTML地图节点,节点需要有宽高
<div id="map"></div>
  1. 初始化:
import { onMounted } from 'vue';

/*
 * layer: 地图切片地址,用于显示地图,该切片地址为高德地图使用的地址
 * 具体出处在高德官网并未找到,从相关博客推测可能是某个大佬抓包或其他方式获取到的
 * 相关博客:https://blog.csdn.net/fredricen/article/details/77189453
 */
const layer = L.tileLayer('http://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}')
let map: any = {}

onMounted(() => {
  // 'map'为HTML节点id
  map = L.map('map', {
    center: [30.745922638363268, 104.00415658374735],//中心坐标
    zoom: 10,//缩放级别
    zoomControl: true, //缩放组件
    attributionControl: false, //去掉右下角logol
    layers: [layer],//图层
  })
})

效果:
image

  1. 将地图点位到指定点,并放大16倍(可显示街道)
map.setView([30.745922638363268, 104.00415658374735], 16)

效果:
image
5. 添加标记
(1)直接添加,官网当中是直接建立一个标记,并添加到地图(不推荐,不好管理)

L.marker([50.5, 30.5]).addTo(map);

(2)添加一个要素组,将标记添加到要素组里面管理(推荐)

// 添加标记组
let featureGroup: any = {}
featureGroup = L.featureGroup().addTo(map)

// 设置标记点:[纬度, 经度]
const marker = L.marker([30.745922638363268, 104.00415658374735])
featureGroup.addLayer(marker)

若标记后报错:
image
在引入时还需要独引入图片并更改默认Icon:

import L from 'leaflet'

// 图片
import _ICON from 'leaflet/dist/images/marker-icon.png';
import _ICONSHADOW from 'leaflet/dist/images/marker-shadow.png';
/*
 * 测试过几组数据,当使用自定义icon时,若不配置iconSize、iconAnchor,图标会在放大地图时位置发送偏移
 * iconAnchor:图标 "tip" 的坐标(相对于其左上角),该值大致为:[iconSize宽的一半,iconSize高]
 * iconAnchor需要在配置iconSize之后才会生效
 * popupAnchor:标记的弹出框的位置(使用默认弹出框时需要
 * popupAnchor若不配置,则默认为经纬度位置,会遮盖标记图标,-50表示将弹出框相对于经纬度位置向上移动50px
 */
let _L_DEFAULT_ICON = L.icon({
    iconUrl: _ICON,
    shadowUrl: _ICONSHADOW,
    iconSize: [25, 41],
    iconAnchor: [12, 40],
    popupAnchor: [0, -50]
});
L.Marker.prototype.options.icon = _L_DEFAULT_ICON

(3)给标记添加事件与弹出框-bindPopup(只支持简单添加)

// 给标记添加事件
marker.on('click', () => { })
// 给标记添加弹出框
marker.bindPopup('弹出内容').openPopup();

(4)给标记添加事件与弹出框-popup(高级用法)

marker.on('click', () => {
    // 可在点击标记后发起请求,请求成功后弹出框显示请求内容
    // 创建弹出框:弹出框默认从经纬度位置弹出,会遮盖图标,可使用offset设置偏移量:[x轴偏移量, y轴偏移量]
    L.popup({ offset: [0, -50] })
      .setLatLng(marker.getLatLng()) //设置弹出框弹出位置
      .setContent('请求内容')
      .openOn(map);
})
  1. 清除标记
if(featureGroup) featureGroup.clearLayers();
  1. 添加连线
// 再添加一个要素组,要素组可存在多个
let lineFeatureGroup: any = {}
lineFeatureGroup = L.featureGroup().addTo(map)
const locations = [
  [30.745922638363268, 104.00415658374735],
  [30.725309888823382, 104.03297424316408]
]

var polyline = L.polyline(locations, {color: 'red'}).addTo(map);
lineFeatureGroup.addLayer(polyline)
  1. 清除连线
if(lineFeatureGroup) lineFeatureGroup.clearLayers();

完整demo代码(代码已上传gitee)

<template>
  <div class="hello">
    <div id="map"></div>
    <div class="controls">
      <div class="fc">
        地图点击:
        <input type="radio" name="mapclick" :value="1" v-model="mapClick">开
        <input class="ml15" type="radio" name="mapclick" :value="0" v-model="mapClick">关
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import L from 'leaflet'
import { onMounted, ref } from 'vue';

import _ICON from 'leaflet/dist/images/marker-icon.png';
import _ICONSHADOW from 'leaflet/dist/images/marker-shadow.png';
/*
 * 测试过几组数据,当使用自定义icon时,若不配置iconSize、iconAnchor,图标会在放大地图时位置发送偏移
 * iconAnchor:图标 "tip" 的坐标(相对于其左上角),该值大致为:[iconSize宽的一半,iconSize高]
 * iconAnchor需要在配置iconSize之后才会生效
 * popupAnchor:标记的弹出框的位置(使用默认弹出框时需要
 * popupAnchor若不配置,则默认为经纬度位置,会遮盖标记图标,-50表示将弹出框相对于经纬度位置向上移动50px
 */
let _L_DEFAULT_ICON = L.icon({
    iconUrl: _ICON,
    shadowUrl: _ICONSHADOW,
    iconSize: [25, 41],
    iconAnchor: [12, 40],
    popupAnchor: [0, -50]
});
L.Marker.prototype.options.icon = _L_DEFAULT_ICON

const locations = [[30.745922638363268, 104.00415658374735], [30.725309888823382, 104.03297424316408]]
const layer = L.tileLayer('http://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}')
const mapClick = ref(1)

let map: any = {}
let featureGroup: any = {}
let lineFeatureGroup: any = {}

onMounted(() => {
  // 初始化地图
  map = L.map('map', {
    center: [30.745922638363268, 104.00415658374735],//中心坐标
    zoom: 10,//缩放级别
    zoomControl: true, //缩放组件
    attributionControl: false, //去掉右下角logol
    layers: [layer],//图层
  })
  // 将视图点位到指定点,并放大16倍
  map.setView([30.745922638363268, 104.00415658374735], 16)
  // 地图点击
  map.on('click', (e: any) => {
    if(mapClick.value) {
      const latlng = e.latlng
      locations.push([latlng.lat, latlng.lng])
      // 清除要素
      if(featureGroup) featureGroup.clearLayers();
      locations.forEach(item => {
        point(item)
      })
    }
  })
  // 添加地图要素组
  featureGroup = L.featureGroup().addTo(map)
  lineFeatureGroup = L.featureGroup().addTo(map)
  // 设置初始打点
  locations.forEach(item => {
    point(item)
  })

  setPolyLine()
})

const point = (arr: number[]) => {
  // 设置点标记:[纬度, 经度]
  const marker = L.marker(arr)
  // 给标记添加事件
  marker.on('click', () => {
    // 创建弹出框:弹出框默认从经纬度位置弹出,会遮盖图标,可使用offset设置偏移量:[x轴偏移量, y轴偏移量]
    L.popup({ offset: [0, -50] })
      .setLatLng(marker.getLatLng())
      .setContent(arr[0] + ': ' + arr[1])
      .openOn(map);
  })
  // 将标记添加到要素组
  featureGroup.addLayer(marker)
}

const setPolyLine = () => {
  var polyline = L.polyline([locations[0], locations[1]], {color: 'red'}).addTo(map);
  lineFeatureGroup.addLayer(polyline)
}
</script>

<style scoped>
.hello, #map {
  height: 100%;
  width: 100%;
}

.hello {
  position: relative;
}

.controls {
  position: absolute;
  right: 0;
  top: 0;
  padding: 15px;
  z-index: 1000;

  font-size: 14px;
  background-color: #fff;
}

.fc {
  display: flex;
  align-items: center;
}

.ml15 {
  margin-left: 15px;
}

.mr15 {
  margin-right: 15px;
}
</style>

一、人物简介

  • 第一位闪亮登场,有请今后会一直教我们C语言的老师 —— 自在。

  • 第二位上场的是和我们一起学习的小白程序猿 —— 逍遥。

二、C语言简介

图片1

  • C语言是一种高级语言,运行效率仅次于汇编,支持跨平台

  • C语言是学习其他高级语言的基础,如C++、Java和Python

三、编程环境

1、在线编译

  • 百度搜索
    C语言在线编译
    ,会发现有很多在线编译工具

图片2

  • 这里以菜鸟工具为例,点击之后,会自动给你生成好一个C程序

图片3

  • 我们简单修改一下,然后点击运行,可以看到,右边输出了我们刚刚改动的内容。

图片5

  • 虽然在线编译很方便,但是如果断网了,我们就用不了。

游弋自如的生产力,在边缘。

IMMENSE、36氪|作者

1846年1月,纽约。

一行长短不一的电码顺着通讯线路飞往130公里开外的费城,这是华尔街的巨头们首次使用电报传输讯息,更具有金钱意味的是,电力通讯的成功,彰显着电力从那一刻起开始成为新的生产力,无数新经济的可能性将会接踵而至。

在那之后的50年中,电力不断得以普及,美国的重工业开始替代轻工业,电气、石油、化学等新兴产业也诞生于电力的基础上。

近200年后的今天,算力成为新的生产力,算法成为新的生产关系,生产资料即是数据。

但,正如电力花费了50年才走进人类生活,算力离无处不在也还有一定距离,最根本的原因在于:算力,是不够用的。

当下对于科技的最佳畅想中,进入元宇宙是必然。更有专家称:“当前这种状况与20世纪90年代初期的互联网一模一样。如果我们不能积极参与其中,就可能错过元宇宙最佳机遇期。”

而想要在元宇宙中开一场演唱会、打一场沙滩排球,或是看一场画展,都对算力提出了天翻地覆的要求。有业内人士曾估算,如果将现在的算力提升1000倍,才有可能达成《头号玩家》中的丝滑之感。

那么,在有限的算力下,如何最大化发挥?让算力不只存在于巨星的闪耀,而是变为繁星之境呢?

01 跳动的算力,边缘把握的脉搏

“我希望,对神经系统所作的更深入的数学研讨,会影响我们对数学自身各个方面的理解。”计算机之父冯·诺依曼在1958年出版的《计算机与人脑》一书中如此写道。

当我们思考“需要怎样的算力基础设施”这个问题时,不妨和冯·诺依曼一样,同样回归计算的生物学本源,将计算机与神经系统类比,来获得灵感。

就像我们用眼睛、耳朵和双手去感受世界,并对外界环境做出反应一样,在数智经济时代,海量、多类型的数据,在工业、医疗、交通等丰富的场景中产生,同时也需要对这些数据进行智能分析,快速完成智能决策以指导生产实践。

中国信息通信研究院在2022年发布的《中国算力发展指数白皮书》中指出,2016-2021年我国算力规模平均每年增长46%,对中国经济社会和产业能级的带动是大势之向。

面向庞大的数据处理,若选择将海量、需实时处理的数据传回云计算中心,不仅会产生高昂的带宽成本,还会因远距离的传输造成极大时延,从而影响终端的决策速度和体验。

为了寻找解决方案,可以参考人体神经系统在面对危险场景时,做出敏捷反应的应对模式。在数万年的进化之旅过后,人体给出的“危机处理方案”是遵循“就近”原则。

设想一下,手指被针扎到的瞬间,短暂的1秒内,我们是先缩回手,而后才感觉到疼痛。这是因为“缩手反射”作为简单的非条件反射,是由脊髓发出指令的,不需经过大脑皮层的深度信息加工。这样就大大提升了我们面对可能的危险时,做出反应的效率,从而提升存活概率。

同理,边缘算力也正是在以低延时、低成本,实现边端敏捷作业、业务快速迭代的需求下产生的。由此诞生的边缘云,最大的特征即是“就近”,即靠近用户侧和靠近数据。

具体而言,“边缘”是物、⼈与⽹络数字世界连接的物理位置,它是数字化转型以及⼈、物和企业之间新交互的关键推动因素。当前,边缘计算已呈快速发展之势,它满⾜了对靠近边缘处理数据的⽇益增⻓的需求。

与集中式云计算不同,边缘云可提供弹性扩展的云服务能力,具有快速响应、低延迟和轻量计算等特点,目前成为公有云、专属云、私有云解决方案之后,云计算生态下不可或缺的新拼图。

据权威机构IDC《中国边缘云市场跟踪研究》报告显示,2021年中国边缘云市场规模总计达50.4亿元人民币,其中,边缘公有云服务细分市场占比超过50%。2022上半年,中国边缘云总体市场规模达到30.7亿元人民币,同比增长50.8%,而边缘公有云增速更是高达56.2%。

在迅猛直上的边缘云市场,透过IDC最新数据,可以看到头部玩家聚集,其中,在最核心的公有云服务市场,阿里云边缘云持续占据国内第一,作为一股云上新势力,正在发挥不可小觑的能量。

数据来源:《中国边缘云市场跟踪研究,2022H1》

IDC Future Scape也提出预测,到2026年,50%中国企业的CIO将要求云和电信合作伙伴提供安全的云到边缘连接解决方案,可见其未来之势。

02 边缘云织网,算力捕鱼

算力向各行各业渗透的过程,如果只是针对单一企业的需求进行点状渗透,则远远达不到基础设施建设的长效作用。

对于数据来说,如果各个场景节点各自为战、就会变为数据孤岛。而将每一个节点串联成网,才能产生更标准的通用模板,也可以实现更大范围的复用。

边缘计算的核心就是要做到连结和流动,通过数据传输能力、调度能力,合理统筹云边资源,把算力流动起来,再根据场景新需求和工作负载的计算复杂度,分配算力节点,就可以让算力更为精准的满足不同节点的需求,数据也不再是凝固的结点。

而要实现上述的“流动”,关键在于打造算力网络。具体来说,是通过网络贯穿数据生命周期,实现数据与算力的匹配,从而形成一张满足“分发”与“计算”的网,实现边缘与中心、边缘与客户端之间的有效协同,做到算力高效释放、算力灵活调度和边缘最优服务。

如此之上,需要一张“算网”。

在边缘结点算力建设上,阿里云在全球提供500余个强有力的全节点,具备边缘云多态计算、融合存储、边缘数据库等所有云能力,通过合计170万核的通用算力CPU核,和400P FLOPS的异构算力核,来满足边缘场景的算力需求。

在网络及算网融合技术上,阿里云基于全球3200多个边缘节点打造了通信级的全球实时传输网络GRTN。

“算”和“网”双重布局的高效算力网络,带来了两方面的显性优势:

其一,基于树网结合的动态网络,真正实现了延时的极致之巅。

一般情况下,传统网络分发的延迟大约是5-6秒,而GRTN在国内的传输延迟约是100毫秒,延时下降了95%以上,极大提升了响应能力。这背后,是阿里云GRTN升级了分发体系,将传统CDN树状结构中的一级网络、二级网络汇聚后再做分层,相当于从原来的层级网络变为对等网络,再根据当前流或任务,生成最优的传输路径,从而形成树状和网状结合的动态网络。

其二,基于全分布式下沉,真正将运力与算力达到极致融合。

GRTN是基于全分布式下沉的,既有中心也有本地和边缘节点,再加上现场计算或一些融合的资源,阿里云把这些都纳入GRTN管理起来,并按需调用。

不同类型资源按照不同的特性可以划分为中心云资源、边缘云资源和多云融合资源。将不同的异构资源融合,适配不同的业务和场景,从而达到用整个分布式的所有资源构建起一张网,提供传输、计算服务。

具体实践中,GRTN可以在一张网中完成传输和计算任务。当GRTN遇到实时计算流需求时,可以按照用户定义的触发条件,在流节点附近有资源的容器纳管平台,进行匹配计算,处理之后再推回到本节点,完成接下来的分发。

这一高效机制,离不开阿里云分布式容器平台的底座,它能够弹性、可扩展地实现中心、边缘等多个节点的异构资源纳管,并辅以算子的托管和触发条件自定义控制。基于此,才有了GRTN可实现Serverless标准的集算子发布、托管、触发、升级于一体的分布式计算平台。

正所谓,边缘的网络是边缘存在的根本,而算力场景在边缘发生、在云边之间协同。

03 繁星之境,普惠之光

从商业的本质来说,无论技术应用多前瞻、数据指标多光鲜,如果无法为更大公约数的企业、用户使用,都只是象牙塔里面的一纸理论。

普惠的最大感知,游走于大众之端。

阿里云边缘云服务的冬奥会、淘宝天猫为例,大量H5页面渲染的计算工作从源站SSR服务器剥离出来,前置到了边缘节点上。简单来说,即是客户端请求到达边缘,由边缘节点渲染拼接出完整的Web页面并返回给客户端加载。由于边缘节点非常靠近客户端,极大缩短了网络链路,边缘渲染让H5页面加载耗时降低50%以上,直线提升了用户的流畅感官体验。

更重要的是,冬奥会、淘宝天猫这类在全球范围开展的业务,单一的SSR源站很难确保全球每个区域的访问都有好的网络质量,那么将渲染服务前置到全球覆盖的边缘节点,可保障每个区域的用户体验。

业务的革新之象,是普惠的开端。

过去,由于缺乏全域的算力覆盖和多形态的算力支持,很多创新场景受限于能力和成本,最终空有创意而难以落地。而如今,边缘的强大算力可以完成终端算力上移或云上算力大量下移,支持更多创新场景走上良性发展的轨道。

阿里云边缘云基于算力和网络基础设施的能力延展,结合自有飞天操作系统的技术积累,在边缘,云网融合的能力得到极大沉淀。算网融合,在边缘结点之间,在边缘和中心Region之间、在边缘和客户端之间,实现极致协同。恰恰基于此,企业能更好地满足场景化的用户需求,做自如的算力流动,而这种流动的灵活性同样促进了创新场景的层出不穷。

音视频、云游戏、云渲染、自动驾驶等场景尤为典型、生长也更为显著。近几年,边缘云尤其激发了新锐的云游戏的应用,仅2021一年,阿里云边缘云所带动的云游戏并发峰值就提升了十倍多,也驱动更多用户涌入到云游戏行业而不断革新。与此同时,在自动驾驶领域,阿里云边缘云所打造的车云协同能力,通过异构资源多节点部署,实现车机和终端就近接入时延小于20ms,破解行业之困。

从革新到落地,直面而来的一定是成本。

边缘云通过云边协同部署,将数据分发和上传处理终结在边缘,大幅缩短数据搬运的距离,真正实现了对业务综合成本的优化。

以支付宝App为例,每次的App启动都伴随着一次版本同步的请求,这类逻辑简单但并发巨大的请求,往往需要数百台服务器支撑,并且需要根据各类大促活动做好资源的扩缩容管理以保障业务正常。而现在,这类超高并发但逻辑简单的API服务往往直接部署在边缘节点,阿里云遍布全球的3200个边缘节点有充足的计算和存储资源,且具有天然的弹性,能保障所有的请求分到有计算资源的边缘节点进行处理,而无需人工扩容服务器集群,同时也极大减少了往返中心服务器的请求和流量,用普惠的边缘算力和流量的云-边-端架构,可降低80%的计算和流量成本。

成本、革新、体验,这一切让算力不再高高居上,而有了平凡的视角和支力。

在云计算基础设施服务方面,阿里云边缘云基于统一的飞天底座,提供一云多芯、一云多态的云计算架构,从中心向边缘辐射,让算力无处不在。

据IDC最新数据,2021-2026年边缘云市场年均复合增长率达到40%以上,除了基于边缘云资源的传输与分发体系,越来越多边云协同场景的出现、以及传统行业用户对“即服务”模式接受程度的提高,正在持续为市场注入活力。

与此同时,来自Gartner的《Competitive Landscape: Hyperscale Edge Solu-tion Providers》报告也提出最新预测,“到2025年,超过70%的组织将为其⾄少⼀个边缘计算系统,部署超⼤规模云边缘解决⽅案,并会结合其云部署,这比例远⾼于2022年的不到15%。”

不得不说,边缘云蓄势待发,中国的边缘云技术也已经走在了世界的最前沿。而阿里云边缘云的规模升级,也代表着中国边缘云行业的缩影。

未来,由“边缘”创造的万象新生,如繁星之境,不可估量。

04 结语

1882年,美国的第一座发电站珍珠街电站开始运行,爱迪生点亮了84个用户的400盏白炽灯。直到1887年,美国直流发电站数量达到121座,电力,才真正成为主要生产力。

大型应用是生产力得以变动的基础,而被刺激的泛在需求让这成为可能。供给和需求之间,一直以来都是相互促进的关系,今天持续流动于我们身边的算力,也是如此。

毫无疑问,更多新应用场景将会涌现、边缘侧的需求也将不断演进,这都促动着边缘云技术的进化和赛道的拓延。算力网络的搭建并非朝夕之间,在供给和需求的不断打磨中,算力真正广泛应用也还有很长的路要走,而阿里云作为先头部队引领的探索方向,正在为算力成为生产力,按下加速键。

当人类优渥于一种状态,总有想象力来冲破平衡。Cloud Imagine《云想之力》是阿里云联合36氪共同打造的系列报道,旨在探索云计算大背景下爆发的应用场景和新兴技术,以想象的高维碰撞之力,窥探“云”上的无限空间。穿梭回近200年前,在超大应用、大型基础设施的助力下,电力成为主要生产力大约花费了近50年,这一次,当算力成为主要生产力,无数算力网络之下的点点繁星,早已蓄势待发。

图像的矩可帮助你计算某些特征,如对象的质心,对象的面积等特征。函数cv.moments()给出了计算的所有矩值的字典。

从这一刻起,你可以提取有用的数据,如面积,质心等。
质心
由关系给出,
$$ C_{x}=\frac{M_{10}}{M_{00}} $$和 $$ C_{y}=\frac{M_{01}}{M_{00}} $$。
这可以按如下方式完成:

import cv2 as cv

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\rectangle.jpg')

imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)

# 获取轮廓点
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print(99, len(contours))

# 找到最内层的元素, 这里是2是因为图像总共有三层
cnt = contours[2]

# 标记选中的区域
res1 = cv.drawContours(img, cnt, -1, (0, 255, 0), 3)

# 计算矩
M = cv.moments(cnt)

# 计算质心
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])

# 画出质心
cv.circle(img, (cx, cy), 5, (255, 0, 0), -1)

cv.imshow('img', img)

cv.waitKey(0)

image

轮廓面积

轮廓区域由函数cv.contourArea()或M['m00']给出。

import cv2 as cv

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\rectangle.jpg')

imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)

# 获取轮廓点
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print(99, len(contours))

# 找到最内层的元素, 这里是2是因为图像总共有三层
cnt = contours[2]

# 标记选中的区域
res1 = cv.drawContours(img, cnt, -1, (0, 255, 0), 3)

# 计算矩
M = cv.moments(cnt)

# 计算轮廓面积
area = cv.contourArea(cnt)
print(666666, area, M['m00'])

image

轮廓周长

轮廓周长也被称为弧长。可以使用cv.arcLength()函数找到它。第二个参数指定形状是闭合轮廓(如果传递为True),还是仅仅是曲线。

import cv2 as cv

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\rectangle.jpg')

imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)

# 获取轮廓点
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print(99, len(contours))

# 找到最内层的元素, 这里是2是因为图像总共有三层
cnt = contours[2]

# 标记选中的区域
res1 = cv.drawContours(img, cnt, -1, (0, 255, 0), 3)

# 计算轮廓周长
perimeter = cv.arcLength(cnt, True)
print(222, perimeter)

image

轮廓近似

它根据我们指定的精度将轮廓形状近似为具有较少顶点数的另一个形状。它是Douglas-Peucker算法的一种实现方式。 要理解这一点,可以假设你试图在图像中找到一个正方形,但是由于图像中的一些问题,你没有得到一个完美的正方形,而是一个“坏形状”(如下图第一张图所示)。现在你可以使用此功能来近似形状。在这里,第二个参数称为epsilon,它是从轮廓到近似轮廓的最大距离。这是一个准确度参数。需要选择适当的epsilon才能获得正确的输出。参数越小,两直线越接近。

epsilon = 0.01 * cv.arcLength(cnt, True)
approx = cv.approxPolyDP(cnt, epsilon, True)
import cv2 as cv

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\rectangle1.jpg')

imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)

# 获取轮廓点
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print(99, len(contours))

# 找到最内层的元素, 这里是2是因为图像总共有三层
cnt = contours[2]

# 计算轮廓近似 epsilon=弧长的5%的近似曲线
epsilon = 0.05 * cv.arcLength(cnt, True)
approx = cv.approxPolyDP(cnt, epsilon, True)

# 绘制轮廓近似
res3 = cv.drawContours(img, [approx], -1, (0, 0, 255), 3)

cv.imshow('img', img)

cv.waitKey(0)

下边第一幅图是 epsilon=弧长的5%的近似曲线, 第二幅图是 epsilon=弧长的1%的近似曲线
image
image

凸包

凸包看起来类似于轮廓近似,但它不是(两者在某些情况下可能提供相同的结果)。这里,cv.convexHull()函数检查曲线的凸性缺陷并进行修正。一般而言,凸曲线是总是凸出或至少平坦的曲线。如果它在内部膨胀,则称为凸性缺陷。例如,检查下面的手形图像。红线表示手的凸包。双面箭头标记显示凸起缺陷,即船体与轮廓的局部最大偏差。

hull = cv.convexHull(points[, hull[, clockwise[, returnPoints]]
参数详情:

  • points:是我们传入的轮廓。
  • hull:是输出,通常我们忽略它。
  • clocwise:方向标志。如果为True,则输出凸包顺时针方向。否则,它逆时针方向。
  • reurnPoints:默认为True。然后它返回凸包点的坐标。如果为False,则返回与凸包点对应的轮廓点的索引。
import cv2 as cv

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\rectangle1.jpg')

imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)

# 获取轮廓点
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print(99, len(contours))

# 找到最内层的元素, 这里是2是因为图像总共有三层
cnt = contours[2]

# 凸包
hull = cv.convexHull(cnt)

# 绘制凸包
res4 = cv.drawContours(img, [hull], -1, (0, 0, 0), 3)

cv.imshow('img', img)

cv.waitKey(0)

image

检查凸性

函数cv.isContourConvex()可以检查曲线是否凸的,它只返回True或False,没有什么理解上的问题。
k = cv.isContourConvex(cnt)

边界矩形

有两种类型的边界矩形。

a.直边矩形

它是一个直的矩形,它不考虑对象的旋转。因此,边界矩形的面积不是最小的。它由函数cv.boundingRect()找到。
设(x,y)为矩形的左上角坐标,(w,h)为宽度和高度。

x,y,w,h = cv.boundingRect(cnt)
cv.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
import cv2 as cv

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\rectangle1.jpg')

imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)

# 获取轮廓点
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print(99, len(contours))

# 找到最内层的元素, 这里是2是因为图像总共有三层
cnt = contours[2]

# 直边矩形
x, y, w, h = cv.boundingRect(cnt)
cv.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)

cv.imshow('img', img)

cv.waitKey(0)

image

b.旋转矩形

这里,以最小面积绘制边界矩形,因此它也考虑旋转。使用的函数是cv.minAreaRect()。它返回一个Box2D结构,其中包含以下detals - (center(x,y),(width,height),rotation of rotation)。但要画这个矩形,我们需要矩形的4个角。它是由函数cv.boxPoints()获得的。

rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(img,[box],0,(0,0,255),2)
import cv2 as cv
import numpy as np

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\rectangle1.jpg')

imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)

# 获取轮廓点
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print(99, len(contours))

# 找到最内层的元素, 这里是2是因为图像总共有三层
cnt = contours[2]

# 旋转矩形
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(img, [box], 0, (0, 0, 255), 2)

cv.imshow('img', img)

cv.waitKey(0)

image

最小外接圈

接下来,我们使用函数cv.minEnclosingCircle()找到对象的外接圆。它是一个完全覆盖物体的圆圈,面积最小。

(x,y),radius = cv.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
cv.circle(img,center,radius,(0,255,0),2)
import cv2 as cv
import numpy as np

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\rectangle1.jpg')

imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)

# 获取轮廓点
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print(99, len(contours))

# 找到最内层的元素, 这里是2是因为图像总共有三层
cnt = contours[2]

# 最小外接圆
(x, y), radius = cv.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
cv.circle(img, center, radius, (0, 255, 0), 2)

cv.imshow('img', img)

cv.waitKey(0)

image

椭圆拟合

接下来是将椭圆拟合到一个对象上。它返回刻有椭圆的旋转矩形。

ellipse = cv.fitEllipse(cnt)
cv.ellipse(img, ellipse, (0, 255, 0), 2)
import cv2 as cv
import numpy as np

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\rectangle1.jpg')

imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)

# 获取轮廓点
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print(99, len(contours))

# 找到最内层的元素, 这里是2是因为图像总共有三层
cnt = contours[2]

# 拟合椭圆
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img, ellipse, (0, 255, 0), 2)

cv.imshow('img', img)

cv.waitKey(0)

image

拟合一条线

类似地,我们可以在一组点上拟合一条线。

rows,cols = img.shape[:2]
[vx,vy,x,y] = cv.fitLine(cnt, cv.DIST_L2,0,0.01,0.01)
lefty = int((-x*vy/vx) + y)
righty = int(((cols-x)*vy/vx)+y)
cv.line(img,(cols-1,righty),(0,lefty),(0,255,0),2)
import cv2 as cv
import numpy as np

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\rectangle1.jpg')

imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)

# 获取轮廓点
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print(99, len(contours))

# 找到最内层的元素, 这里是2是因为图像总共有三层
cnt = contours[2]

# 拟合一条线
rows, cols = img.shape[:2]
[vx, vy, x, y] = cv.fitLine(cnt, cv.DIST_L2, 0, 0.01, 0.01)
lefty = int((-x * vy / vx) + y)
righty = int(((cols - x) * vy / vx) + y)
cv.line(img, (cols - 1, righty), (0, lefty), (0, 255, 0), 2)

cv.imshow('img', img)

cv.waitKey(0)

image

上边完整代码

点击查看代码
import cv2 as cv
import numpy as np

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\rectangle1.jpg')

imgray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
ret, thresh = cv.threshold(imgray, 127, 255, 0)

# 获取轮廓点
contours, hierarchy = cv.findContours(thresh, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
print(99, len(contours))

# 找到最内层的元素, 这里是2是因为图像总共有三层
cnt = contours[2]

# 标记选中的区域
# res1 = cv.drawContours(img, cnt, -1, (0, 255, 0), 3)

# 计算矩
M = cv.moments(cnt)

# 计算质心
cx = int(M['m10'] / M['m00'])
cy = int(M['m01'] / M['m00'])

# 画出质心
cv.circle(img, (cx, cy), 5, (255, 0, 0), -1)

# 计算轮廓面积
area = cv.contourArea(cnt)
print(666666, area, M['m00'])

# 计算轮廓周长
perimeter = cv.arcLength(cnt, True)
print(222, perimeter)

# 计算轮廓近似 epsilon=弧长的5%的近似曲线
epsilon = 0.01 * cv.arcLength(cnt, True)
approx = cv.approxPolyDP(cnt, epsilon, True)
res3 = cv.drawContours(img, [approx], -1, (0, 0, 255), 3)

# 凸包
hull = cv.convexHull(cnt)
res4 = cv.drawContours(img, [hull], -1, (0, 0, 0), 3)

# 检查凸性
k = cv.isContourConvex(cnt)
print(333, k)

# 直边矩形
x, y, w, h = cv.boundingRect(cnt)
cv.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)

# 旋转矩形
rect = cv.minAreaRect(cnt)
box = cv.boxPoints(rect)
box = np.int0(box)
cv.drawContours(img, [box], 0, (0, 0, 255), 2)

# 最小外接圆
(x, y), radius = cv.minEnclosingCircle(cnt)
center = (int(x), int(y))
radius = int(radius)
cv.circle(img, center, radius, (0, 255, 0), 2)

# 拟合椭圆
ellipse = cv.fitEllipse(cnt)
cv.ellipse(img, ellipse, (0, 255, 0), 2)

# 拟合一条线
rows, cols = img.shape[:2]
[vx, vy, x, y] = cv.fitLine(cnt, cv.DIST_L2, 0, 0.01, 0.01)
lefty = int((-x * vy / vx) + y)
righty = int(((cols - x) * vy / vx) + y)
cv.line(img, (cols - 1, righty), (0, lefty), (0, 255, 0), 2)

cv.imshow('img', img)

cv.waitKey(0)

本节开始将对 Yarn 中的 NodeManager 服务进行剖析。
NodeManager 需要在每个计算节点上运行,与 ResourceManager 和 ApplicationMaster 进行交互。管理节点的计算资源以及调度容器。后续将对 NM 的功能职责、状态机、容器生命周期和资源隔离等方面进行讲解。本篇将从整体上对 NM 进行介绍。

一、NodeManager 基本职能

在 Hadoop 集群中,每个计算节点都需要有一个管理服务,其就是 NodeManager(NM)。
它负责与 ResourceManager 保持通信,管理 Container 的生命周期,监控每个 Container 的资源使用情况,追踪节点健康状况,管理日志等。
主要职责:

  1. 保持与 ResourceManager 同步
  2. 跟踪节点的健康状况
  3. 管理节点各个 Container 的生命周期,监控每个 Container 的资源使用情况
  4. 管理分布式缓存(对 Container 所需的 Jar,库文件的本地文件系统缓存)
  5. 管理各个 Container 生成日志

整体来说,NM 通过两个 RPC 协议与 RM 和 AM 交互,如下图所示。
image.png

一)与 RM 交互

通过
ResourceTrackerProtocol
协议:

  • NM 通过该 RPC 协议向 RM 注册、汇报节点健康状况和 Container 运行状态;
  • 领取 RM 下达的命令,包括重新初始化、清理 Container 占用资源等。

在该协议中,RM 扮演 RPC server 的角色,而 NM 扮演 RPC Client 的角色(由内部组件
NodeStatusUpdater
实现)。NM 与 RM 之间采用 「pull 模型」,NM 总是周期性地主动向 RM 发起请求,并领取下达给自己的命令。

二)与 AM 交互

通过 ContainerManagementProtocol 协议:

  • 应用程序的 AM 通过该 RPC 协议向 NM 发起 Container 的相关操作(启动、kill、获取 Container 执行状态等)。

在该协议中,AM 扮演 RPC Client 的角色,而 NM 扮演 RPC Server 的角色(由内部组件
ContainerManager
实现)。NM 与 AM 之间采用「push 模型」,AM 可以将 Container 相关操作的第一时间告诉 NM,相比于「pull 模型」,可以大大降低时间延迟。

二、NodeManager 内部结构

NodeManager 内部由多个组件构成,如下图所示。其中最主要的三个组件是:
NodeStatusUpdater

ContainerManager

NodeHealthCheckService

image.png

一)NodeStatusUpdater

NodeStatusUpdater
是 NM 与 RM 通信的唯一通道。

  • 当 NM 启动时,该组件负责向 RM 注册,并汇报节点上总的可用资源;
  • 之后,该组件周期性与 RM 通信,汇报各个 Container 的状态更新(包括节点上正在运行的 Container、已经完成的 Container 等信息);
  • 同时 RM 会返回待清理的 Container 列表、待清理的应用程序列表、诊断信息、各种 Token 等信息。

二)ContainerManager

ContainerManager 是 NM 中最核心的组件之一,它由多个子组件组成,每个子组件负责一部分功能,协同管理运行在该节点上的所有 Container,各个子组件如下。

  • RPC Server:
    该 RPC Server 实现了
    ContainerManagementProtocol
    协议,是 AM 与 NM 通信的唯一通道。ContainerManager 从各个 AM 上接收 RPC 请求以启动新的 Container 或者 停止正在运行的 Container。需要注意的是,任何 Container 操作均会经
    ContainerTokenSecretManager
    合法性验证,以防止伪造启动或停止 Container 的命令。
  • ResourceLocalizationService:
    负责 Container 所需资源的本地化,它能够按照描述从 HDFS 上下载 Container 所需的文件资源,并尽量将它们分摊到各个磁盘上以防止出现热点访问。此外,它会为下载的文件添加访问控制限制,并为之施加合适的磁盘空间使用份额。
  • ContianersLauncher:
    维护了一个线程池以并行完成 Container 相关操作,比如启动或者杀死 Container,其中启动 Container 请求是由 AM 发起的,而杀死 Container 请求则可能来自 AM 或者 RM。
  • AuxService:
    NodeManager 允许用户通过配置附属服务的方式扩展自己的功能,这使得每个节点可以定制一些特定框架的服务。附属服务需要在 NodeManager 启动之前配置好,并由 NodeManager 统一启动与关闭。
  • ContainersMonitor:
    ContainersMonitor 负责监控 Container 的资源使用量,为了实现资源隔离和公平共享,RM 为每个 Container 分配了一定量的资源。而 ContainersMonitor 周期性探测它在运行过程中的资源利用量,一旦发生 Container 超出了它的允许使用份额上线,就向 Container 发送信号将其杀掉,这可以避免资源密集型的 Container 影响同节点上其他正在运行的 Container。
  • LogHandler:
    一个可插拔组件,用户可通过它控制 Container 日志的保存方式,即是写到本地磁盘上还是将其打包后上传到一个文件系统中。
  • ContainerEventDispatcher:
    Container 事件调度器,负责将 ContainerEvent 类型的事件调度给对应 Container 的状态机 ContainerImpl。
  • ApplicationEventDispatcher:
    Application 事件调度器,负责将 ApplicationEvent 类型的事件调度给对应 Application 的状态机 ApplicationImpl。

三)NodeHealthCheckerService

NodeHealthCheckerService 通过周期性地运行一个自定义脚本(由组件 NodeHealthScriptRunner 完成)和向磁盘写文件(由服务 LocalDirsHandlerService 完成)检查节点的健康状况。
并通过 NodeStatusUpdater 传递给 ResourceManager。一旦 ResourceManager 发现一个节点处于不健康状态,则会将它加入黑名单,此后不再使用该资源,直到再次转为健康状态。需要注意的是,节点被加入黑名单时,正在运行的 Container 仍会正常运行,不会被杀死。

四)DeletionService

NodeManager 使用一个专门的服务用于文件删除。异步地删除失效文件,这样可避免删除文件带来的性能开销。

五)Security

安全部分。它包含两部分,分别是
ApplicationACLsManager

ContainerTokenSecretManager

ApplicationACLsManager
确保访问 NodeManager 的用户是合法的,
ContainerTokenSecretManager
确保用户请求的资源被 ResourceManager 授权过。

  • ApplicationACLsManager:
    NodeManager 需要为所有面向用户的 API 提供安全检查,如在 Web UI 上只能将 Container 日志显示给授权用户。该组件为每个应用程序维护了一个 ACL 列表,一旦收到类似请求后会利用该列表对其进行验证。
  • ContainerTokenSecretManager:
    检查收到的各种访问请求的合法性,确保这些请求操作已被 ResourceManager 授权。

六)WebServer

通过 Web 界面向用户展示该节点上所有应用程序运行状态、Container 列表、节点健康状况和 Container 产生的日志等信息。

七)ContainerExecutor

与底层操作系统交互,安全的放置 Container 所需要的文件和目录,随后以一个安全的方式启动和清理Container相关进程。

三、NodeManager 的事件与事件处理器

NodeManager主要组件也是通过事件进行交互的,这使得组件能够异步并发完成各种功能。如下图所示:
image.png

image.png

四、总结

本节对 NodeManager 整体结构进行了介绍。从它的基本职能、内部结构、事件处理三个方面进行讲解,对 NM 整体结构有了认知。
实际上 NM 主要就负责两个事情:1)与 RM 交互,注册以及汇报状态,领取 RM 指令处理 container。2)与 AM 交互,处理其管理的 container 操作。


参考文章:
《Hadoop技术内幕:深入解析YARN架构设计与实现原理》
深入YARN系列3:剖析NodeManager架构,组件与生产应用
NodeManager详细组件及功能
Yarn NodeManager总体架构