ss3中常用的几个动画效果:
ease: cubic-bezier(0.25, 0.1, 0.25, 1.0)
linear: cubic-bezier(0.0, 0.0, 1.0, 1.0)
ease-in: cubic-bezier(0.42, 0, 1.0, 1.0)
ease-out: cubic-bezier(0, 0, 0.58, 1.0)
ease-in-out: cubic-bezier(0.42, 0, 0.58, 1.0)
下面上图看效果:
网站截图
效果
效果
效果
在线预览网站:http://yisibl.github.io/cubic-bezier/#0,0,1,1
如果想交流的可以打开下面的网站:http://www.mackxin.com/xininn.html
一起学习,一起交流(前端,PS,分享)
关注分享,体验乐趣
分享是一种态度
import numpy as np import argparse import time import cv2 # construct the argument parse and parse the arguments ap = argparse.ArgumentParser() ap.add_argument("-v", "--video", help = "path to the (optional) video file") args = vars(ap.parse_args()) # define the upper and lower boundaries for a color # to be considered "blue" blueLower = np.array([100,67,0],dtype="uint8") blueUpper = np.array([255,128,50],dtype="uint8") # load the video if not args.get("video"): camera = cv2.VideoCapture(0) else: camera = cv2.VideoCapture(args["video"])
们将使用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("video") 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) > 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("Traccking",frame) cv2.imshow("Binary",blue) # if your machine is fast, it may display the frames in # what appears to be 'fast forward' 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 'q' key is pressed, stop the loop if cv2.waitKey(1) & 0xFF == ord("q"): 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)可选的。在许多较新型号的机器上,系统可能足够快以处理>32帧/秒。如果是这种情况,找到可接受的睡眠时间将减慢处理速度并将其降低到更正常的速度。
执行我们的脚本
python track.py
结果:
或者指定视频路径
python track.py --video "video\2018-11-27 18-38-15-927.mp4"
也是可以的。
<script src="https://lf3-cdn-tos.bytescm.com/obj/cdn-static-resource/tt_player/tt.player.js?v=20160723"></script>
博客地址(有完整代码):https://0leo0.github.io/2018/case_study_03.html
关注不迷路哦!!
以上两种实现方式我们可以根据两个因素 来决定哪一种更加适合自己:节点数量 的多少、排布的密集度 。
前者适合节点数量较少,排布比较稀松,例如,出租车轨迹回放,出租车行驶速度快,周期上报的时间也会相对较长。后者更加针对节点数量巨大、排布密集的路径,按秒记录位置的飞机行进轨迹,精细的地理边界等等。
无论选择两种方式,我们都需要先收集到客户端上报的信息,这些信息可以自定义,通常我们会包含:经纬度、速度、逆编码之后的地理位置、方向、海拔 等基本地理信息,同时我们也可以加入一些自定义 的信息,例如:人员信息(头像昵称等)、出行信息(订单等)。
实现的流程:
客户端上报的数据是按时间周期上报的,也就是说每个时间都对应了一个经纬度,经纬度在地图上就是一个又一个点,将这些点连接时,我们会得到 N 多条折线,为了绘制的轨迹更加美观,行动路线更加明确平滑,通常我们需要一个算法来简化折线。
例如:
这里官方也推荐了一种算法库 simplify.js供大家参考,这里不做过多的阐述。
这里我们使用第一种方式来实现 - 利用 JS API 和 AMap.PolyLine。
我们可以让车辆:
AMap.plugin('AMap.MoveAnimation', function(){
var marker, lineArr = [[116.478935,39.997761],[116.478939,39.997825],[116.478912,39.998549],[116.478912,39.998549],[116.478998,39.998555],[116.478998,39.998555],[116.479282,39.99856],[116.479658,39.998528],[116.480151,39.998453],[116.480784,39.998302],[116.480784,39.998302],[116.481149,39.998184],[116.481573,39.997997],[116.481863,39.997846],[116.482072,39.997718],[116.482362,39.997718],[116.483633,39.998935],[116.48367,39.998968],[116.484648,39.999861]];
var map = new AMap.Map("container", {
resizeEnable: true,
center: [116.397428, 39.90923],
zoom: 17
});
marker = new AMap.Marker({
map: map,
position: [116.478935,39.997761],
icon: "https://a.amap.com/jsapi_demos/static/demo-center-v2/car.png",
offset: new AMap.Pixel(-13, -26),
});
// 绘制历史轨迹
var polyline = new AMap.Polyline({
map: map,
path: lineArr,
showDir:true,
strokeColor: "#28F", //线颜色
// strokeOpacity: 1, //线透明度
strokeWeight: 6, //线宽
// strokeStyle: "solid" //线样式
});
// 驾驶途径过的轨迹
var passedPolyline = new AMap.Polyline({
map: map,
strokeColor: "#AF5", //线颜色
strokeWeight: 6, //线宽
});
// 监听车辆移动事件
marker.on('moving', function (e) {
// 延长驾驶途径过的轨迹
passedPolyline.setPath(e.passedPath);
// 将车辆位置设置为地图中心点
map.setCenter(e.target.getPosition(),true)
});
map.setFitView();
// 开始移动
window.startAnimation = function startAnimation () {
marker.moveAlong(lineArr, {
// 每一段的时长
duration: 500,//可根据实际采集时间间隔设置
// JSAPI2.0 是否延道路自动设置角度在 moveAlong 里设置
autoRotation: true,
});
};
// 暂停移动
window.pauseAnimation = function () {
marker.pauseMove();
};
// 恢复移动
window.resumeAnimation = function () {
marker.resumeMove();
};
// 停止移动
window.stopAnimation = function () {
marker.stopMove();
};
});
参考链接:https://lbs.amap.com/demo/jsapi-v2/example/marker/replaying-historical-running-data
使用 JS API 和 AMapUI 组件库 配合使用,利用 PathSimplifier(轨迹展示组件)绘制出行动轨迹,这种方案比较简单,只需要进行一些配置即可,例如说方案一中的倍速播放就需要计算,同时还存在不能动态改变倍速的弊端,但是方案二却不会存在。
//加载PathSimplifier,loadUI的路径参数为模块名中 'ui/' 之后的部分
AMapUI.load(['ui/misc/PathSimplifier'], function(PathSimplifier) {
if (!PathSimplifier.supportCanvas) {
alert('当前环境不支持 Canvas!');
return;
}
//启动页面
initPage(PathSimplifier);
});
function initPage(PathSimplifier) {
//创建组件实例
var pathSimplifierIns = new PathSimplifier({
zIndex: 100,
map: map, //所属的地图实例
getPath: function(pathData, pathIndex) {
//返回轨迹数据中的节点坐标信息,[AMap.LngLat, AMap.LngLat...] 或者 [[lng|number,lat|number],...]
return pathData.path;
},
getHoverTitle: function(pathData, pathIndex, pointIndex) {
//返回鼠标悬停时显示的信息
if (pointIndex >= 0) {
//鼠标悬停在某个轨迹节点上
return pathData.name + ',点:' + pointIndex + '/' + pathData.path.length;
}
//鼠标悬停在节点之间的连线上
return pathData.name + ',点数量' + pathData.path.length;
},
renderOptions: {
//轨迹线的样式
pathLineStyle: {
strokeStyle: 'red',
lineWidth: 6,
dirArrowStyle: true
}
}
});
//这里构建两条简单的轨迹,仅作示例
pathSimplifierIns.setData([{
name: '轨迹0',
path: [
[100.340417, 27.376994],
[108.426354, 37.827452],
[113.392174, 31.208439],
[124.905846, 42.232876]
]
}, {
name: '大地线',
//创建一条包括500个插值点的大地线
path: PathSimplifier.getGeodesicPath([116.405289, 39.904987], [87.61792, 43.793308], 500)
}]);
//创建一个巡航器
var navg0 = pathSimplifierIns.createPathNavigator(0, //关联第1条轨迹
{
loop: true, //循环播放
speed: 1000000
});
navg0.start();
}
参考链接:https://lbs.amap.com/demo/amap-ui/demos/amap-ui-pathsimplifier/index
*请认真填写需求信息,我们会在24小时内与您取得联系。