关于 iOS 的技术解读有很多,但是却鲜有设备可视化同步的介绍文章。本文一起了解下这个酷炫的 iOS 黑科技。

以下为译文:

一直以来,我可能都定义错了“量子纠缠(Quantum Entanglement)”这个古老的力学技术。通过实验我发现,利用它甚至可以应用到技术领域,实现酷炫的设备同步!下面我们来一步步验证看看吧。

我们的任务很简单——如上图所示,实时获取设备的当前方向。

UIDevice.current.orientation

首先,需要调用

beginGeneratingDeviceOrientationNotifications()

但仅仅这样还不行。因为如果设备上的旋转被锁定了,那么就不会产生以上通知。我的相机应用程序从头到尾都需要知道方向——所以我意识到我需要直接根据设备的加速度计算方向。

......情况不妙,这比我一开始想的复杂多了。

不过,运行 Core Motion 也并不是难事。一个简单的 import CoreMotion,之后可以使用 CMMotionManager() 来创建一个移动管理器。然后通过向该移动管理器的 startDeviceMotionUpdates 传递闭包和队列来接收值。

import CoreMotion
let motionManager = CMMotionManager()
let queue = OperationQueue()
motionManager.startDeviceMotionUpdates(to: queue) { (data: CMDeviceMotion?, error: Error?) inguard let data = data else {print("Error: \(error!)")return}let attitude: CMAttitude = data.attitudeprint("pitch: \(attitude.pitch)")print("yaw: \(attitude.yaw)")print("roll: \(attitude.roll)")
} 

好了,现在有了这些值,我们该做些什么呢?这是一个较难的部分。如果将所有内容都输出到控制台,那么我们很快就会被大量数据淹没。我认为还是在屏幕上显示这些值比较好。

但是,等等,如果将数值显示在图表上,会怎么样?别想图表了,我们可以来用开源的 Blender 试试,它可以实现这些值的可视化,并且很容易扩展。

这不是在为 Blender 打广告,只纯粹探讨利用其技术实现的可视化功能。

经过深思熟虑后,我打开了 Blender.org,并下载了最新版本。下载好后,搜索“iPhone 3D 模型”并点击第一个结果,它还自带 .blend 文件,非常棒。

打开该文件后,屏幕中间会显示一个漂亮的 iPhone 6 的模型。漂亮!所以我们只需要将这个模型与真正的 iPhone 通过量子纠缠在一起。

事实上,我也不知道这个屏幕上绝大部分控件是干什么用的

我之前从未使用过 Blender,所以我在 Google 上搜索了一大堆东西,并试图找出正确的搜索条件,以获得足够的知识来完成我想做的事情。

最终我发现下一步是使用 Text Editor 面板。因此,我通过点击 Blender 窗口左下角的小时钟图标,将 Timeline 面板改成了 Text Editor 面板。

点击底部中心的小按钮+,就可以创建新的文本文件。如果你希望按照本文描述按部就班,那么请将其命名为 Motion Server。

Blender 插件是用 Python 编写的。在新的编程环境中要做的第一件事,就是在控制台输出一些东西。因此,输入 print("hi") 并单击 Run Script 就可以确认控制台输出。

你应该看不到有文字输出。我甚至检查了 macOS 控制台应用程序,空空如也。好吧,让我们将右下方的面板变更为 Python 控制台。

现在我们只需输入 print("hi"),但是我们想要使用之前写的代码,免得之前敲键盘的努力白费了。我们可以复制并粘贴以下代码到控制台:

exec(compile(bpy.data.texts[‘Motion Server’].as_string(), Motion Server’, ‘exec’))

按 Enter 键运行该代码段,然后在控制台中显示 “hi”。 搞定!

刷新监控台

然而 Blender 并不是很好的代码编辑器,所以我们还是使用钟爱的外部编辑器吧。为了调用外部文件,我们可以将 print("hi") 替换成以下代码:

import bpy
import os
filename = os.path.join(os.path.dirname(bpy.data.filepath), "server.py")
exec(compile(open(filename).read(), filename, 'exec'))

下一步,我们需要在与 .blend 文件相同的文件夹中创建新的 server.py 文件,我们真正的代码就要保存在这里。现在我们可以用任何编辑器打开它,你可以选择 Atom、Sublime,甚至 Word 2007 都行。

我不是 Python 专家,所以我不得不在 Google 上搜索如何使用数组,以及如何解析 JSON。

最终,在一连串的复制和粘贴之后,我的服务器开始运行了。

我们使用的是 Python 的 C 语言绑定,所以实际上它是非常底层的代码。我们绑定一个 TCP 套接字来监听所有的接口,然后循环接受新的连接,并调用 select 来检查任何可读的连接。所有这些工作都需要在一个新的线程中完成,以免阻塞 Blender 的主线程。因为这种行为可能会让 Blender 锁死,这不是闹着玩的事儿。

有趣的是,重新运行上面的脚本会覆盖我们的全局函数,所以可以使用它在不断开客户端的情况下更新功能,这样修改代码就更容易了。

一旦连接可读,就读取数据,解析 JSON,并将生成的对象发送给 receivedMotionData。该函数引用当前屏幕上的 iPhone 对象。我们可以使用右上角的面板重命名 iPhone,以便我们的代码正常工作。

Cube 并不是立方体,来改个名

找到该 Cube 对象,点击右键并选择重命名,重命名为 iPhone。现在让我们再来看一看 server.py。

import socket
import select
import json
import threading
import traceback
class ServerThread(threading.Thread):def __init__(self):threading.Thread.__init__(self)self.running = Truedef stopServer(self):self.running = Falseself.server.running = Falsedef run(self):try:self.server = Server()while self.running:self.server.receive()except:pass
class Server:def __init__(self):self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)self.socket.setblocking(False)self.socket.bind((str(socket.INADDR_ANY), 9845))self.socket.listen(2)self.running = Truedef __exit__(self, exc_type, exc_value, traceback):self.socket.close()def receive(self):pairs = []timeout = 1while self.running:sockets = list(map(lambda x: x[0], pairs))if len(pairs) > 0:read_sockets, write_sockets, error_sockets = select.select(sockets, [], [], timeout)for sock in read_sockets:data = sock.recv(4096)if not data :print('Client disconnected')pairs = []else :self.connectionReceivedData(connection, data.decode())try:try:connection,address = self.socket.accept()print("new connection: ", connection)pairs.append((connection, address))except:passexcept:passfor pair in pairs:(connection, address) = pairconnection.close()def connectionReceivedData(self, connection, data):try:motionData = json.loads(data)except json.decoder.JSONDecodeError:print("Invalid JSON: ", data)return NonereceivedMotionData(motionData)
# This is a global so when we run the script again, we can keep the server alive
# but change how it works
import bpy
def receivedMotionData(motionData):phone = bpy.context.scene.objects["iPhone"]phone.rotation_quaternion.x = float(motionData['x'])phone.rotation_quaternion.y = 0 - float(motionData['z'])phone.rotation_quaternion.z = float(motionData['y'])phone.rotation_quaternion.w = float(motionData['w'])pass
try:if serverThread.running == False:serverThread = ServerThread()serverThread.start()print("Starting server")else:print("Server already running, using new motion handler.")
except:serverThread = ServerThread()serverThread.start()print("Starting server")

乍一看上去很多代码的样子。

将上面的代码放到 server.py 中,然后在 Python 控制台中按方向键上,调用最后一个执行语句。服务器运行后,控制台现在应该显示说“服务器已启动”。现在我们快速地测试一下。创建一个名为 client.py 的新文件,并保存以下代码:

import socket
send = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
send.connect(("localhost", 9845))
send.send("{ \"x\": 1.2, \"y\": 0, \"z\": 1.2, \"w\": 0}")
send.close()

如果你使用的是 Sublime,可以点击 Tools - > Build 菜单项运行它。否则,可以在终端上,使用 python /path/to/client.py 运行。

检查 Blender,你应该看到 iPhone 根本没有改变。这是因为上面的脚本使用四元组设置了 iPhone 的旋转角度,并且它使用了欧拉角进行旋转。需要做一些修改。将 Python 控制台面切换到 “Properties”,然后单击该面板顶部的橙色立方体图标。中部 Transform 的下面,点击 XYZ Euler 并选择 Quaternion。现在尝试再次运行 client.py。

你应该看到 iPhone 立即翻转过来了。不要惊慌,这就是我们想要的。现在,我们需要让这个模型跟着实际的 iPhone 旋转。

上述步骤成功后,你应该看到这样的效果

我们需要将运动数据从 iPhone 发送到运行 Blender 的计算机。感谢上苍我们不需要深入到 Swift 中的原始 C 套接字级别,因为 Foundation 具有抽象。

我们可以将以下代码放入新的 iOS 项目中,以替换默认的 ViewController。请确保使用计算机的本地 IP 地址替换 host 变量。

import UIKit
import CoreMotion
class CoreMotionViewController: UIViewController, StreamDelegate {let motionManager = CMMotionManager()let queue = OperationQueue()let host = "192.168.1.2"override func viewDidLoad() {super.viewDidLoad()setUpStreams(host: host)motionManager.startDeviceMotionUpdates(to: queue) { (data: CMDeviceMotion?, error: Error?) inguard let data = data else {print("Error: \(error!)")return}let attitude: CMAttitude = data.attitudelet quaternion = attitude.quaternionvar motionData = MotionData()motionData.x = quaternion.xmotionData.y = quaternion.ymotionData.z = quaternion.zmotionData.w = quaternion.wlet encoder = JSONEncoder()do {let json = try encoder.encode(motionData)self.send(data: json)} catch let error {print("Couldn't send data, error: \(error)")}}}// MARK: - Streamsvar inputStream: InputStream?var outputStream: OutputStream?func setUpStreams(host: String) {var readStream: Unmanaged<CFReadStream>?var writeStream: Unmanaged<CFWriteStream>?CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,host as CFString, 9845,&readStream,&writeStream)inputStream = readStream!.takeRetainedValue()outputStream = writeStream!.takeRetainedValue()guard let inputStream = inputStream, let outputStream = outputStream else {print("Failed to create streams")return}inputStream.delegate = selfoutputStream.delegate = selfinputStream.schedule(in: .current, forMode: .commonModes)outputStream.schedule(in: .current, forMode: .commonModes)inputStream.open()outputStream.open()}func send(data: Data) {guard let outputStream = outputStream else {return}_ = data.withUnsafeBytes {outputStream.write($0, maxLength: data.count)}}func stream(_ aStream: Stream, handle eventCode: Stream.Event) {if eventCode == .errorOccurred {inputStream = niloutputStream = nilprint("Error: Stream error")} else if eventCode == .endEncountered {inputStream = niloutputStream = nilprint("Error: Encountered end of stream")}let maxReadLength = 4096if eventCode == .hasBytesAvailable {guard let inputStream = inputStream else {return}while inputStream.hasBytesAvailable {let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: maxReadLength)inputStream.read(buffer, maxLength: maxReadLength)buffer.deallocate()}}}
}
// MARK: - Data Model
private struct MotionData: Codable {var x: Double = 0var y: Double = 0var z: Double = 0var w: Double = 0
}

代码非常简单。它为 host 打开一个套接字,然后为每个动作更新创建一个 MotionData 值,在其上设置属性,将其编码为 JSON,并发送到在 Blender 中运行的脚本。同时读取 host 发送的任何数据并将其丢弃。

现在你应该可以看到完整的可视化效果了,恭喜。

成功实现了!

那么最终我是如何从移动管理器获取方向信息的?这个是留给读者的一个练习。哈哈,开个玩笑,点击这里获取代码:

https://github.com/JohnCoates/Slate/commit/34e89b2eb7a0d80f144abaa10d98d2a7b52d7fe6#diff-7115c8127d96763420a91c4c72a58788R91

原文:https://medium.com/@JohnCoatesDev/visualizing-an-ios-device-in-blender-through-quantum-entanglement-ba8b6f0b47a5

作者:John Coates

译者:弯月,责编:郭芮

声明:本文已获作者翻译授权。

征稿啦!

CSDN 公众号秉持着「与千万技术人共成长」理念,不仅以「极客头条」、「畅言」栏目在第一时间以技术人的独特视角描述技术人关心的行业焦点事件,更有「技术头条」专栏,深度解读行业内的热门技术与场景应用,让所有的开发者紧跟技术潮流,保持警醒的技术嗅觉,对行业趋势、技术有更为全面的认知。
如果你有优质的文章,或是行业热点事件、技术趋势的真知灼见,或是深度的应用实践、场景方案等的新见解,欢迎联系 CSDN 投稿,联系方式:微信(guorui_1118,请备注投稿+姓名+公司职位),邮箱(guorui@csdn.net)。

一文教你如何用 Python 将 iPhone “玩弄于股掌之中”!相关推荐

  1. 如何用Python实现iPhone X的人脸解锁功能?

    翻译 | AI科技大本营(公众号ID:rgznai100) 参与 | 林椿眄 编辑 | 费棋 FaceID 是新款 iPhone X 最受欢迎的功能之一,它取代 TouchID 成为了最前沿的解锁方式 ...

  2. python---如何用Python实现iPhone X的人脸解锁功能?

    翻译 | AI科技大本营(公众号ID:rgznai100) 参与 | 林椿眄 编辑 | 费棋 FaceID 是新款 iPhone X 最受欢迎的功能之一,它取代 TouchID 成为了最前沿的解锁方式 ...

  3. 一文教你如何用Python预测股票价格,程序员学以致用

    翻译 | AI科技大本营(rgznai100) 参与 | 刘畅 编辑 | 周翔 [AI科技大本营导读]最近,A股尤其是上证指数走势凌厉,让营长有种身在牛市中的错觉.然而大盘天天涨,营长账户中还是那几百 ...

  4. 一文教你如何用Python预测股票价格

    翻译 | AI科技大本营(rgznai100) 参与 | 刘畅 编辑 | 周翔 [AI科技大本营导读]最近,A股尤其是上证指数走势凌厉,让营长有种身在牛市中的错觉.然而大盘天天涨,营长账户中还是那几百 ...

  5. 一文教你如何用Python读取图片GPS定位

    起因 早上起来,看到有人问Python获取一张JPG格式图片拍摄的时候的GPS定位的代码.GPS应该说是个敏感的信息,既然有人想读取我们的信息,那么我们至少应该直到我们的敏感信息被保存在了哪里. 研究 ...

  6. 如何用Python和深度神经网络识别图像?

    本文授权转自微信公众号芝兰玉树 作者 | 王树义 只需要10几行Python代码,你就能自己构建机器视觉模型,对图片做出准确辨识和分类.快来试试吧! 视觉 进化的作用,让人类对图像的处理非常高效. 这 ...

  7. 如何用python制作动态二维码,提升表白成功率?

    来源:凹凸数据 本文约1000字,建议阅读5分钟. 本文教你用python制作动态二维码,助你表白成功! 关注数据派THU(DatapiTHU)后台回复"20200520"获取完整 ...

  8. 动态数据交换 python_如何用 Python 和 Streamlit 做交互式数据分析产品?

    「本文参与少数派 2019 年度征文 + 效率有心得」 不用学前端编程,你就能用 Python 简单高效写出漂亮的交互式 Web 应用,将你的数据分析成果立即展示给团队和客户. 痛点 从我开始折腾数据 ...

  9. 如何用Python处理自然语言?(Spacy与Word Embedding)

    本文教你用简单易学的工业级Python自然语言处理软件包Spacy,对自然语言文本做词性分析.命名实体识别.依赖关系刻画,以及词嵌入向量的计算和可视化. (由于微信公众号外部链接的限制,文中的部分链接 ...

最新文章

  1. Linux用户环境变量
  2. Socket 阻塞模式和非阻塞模式
  3. 手机html检测蓝牙打印机,打印机手机确认.html
  4. 概述SharePoint 2007
  5. java类似goto_原来java中也有类似goto语句的标签啊--java label标签
  6. 八、一篇文章快速搞懂MySQL 常见的数据类型(整型、小数、字符型、日期型详解)
  7. r软件 image画出来的图是颠倒的_如何用Python抠图?试试scikitimage
  8. Hive函数:GROUPING SETS,GROUPING__ID,CUBE,ROLLUP
  9. azure git怎么使用_使用GitOps管理您的Kubernetes集群
  10. Android开发笔记(八十六)几个特殊的类
  11. 工厂模式(简单、普通、抽象)
  12. 工业线阵相机与面阵相机特点分析
  13. 【项目实训】微信公众号获取用户openid
  14. 北航单片机李广弟c语言还是汇编,单片机基础备课笔记
  15. 大数据技术之Hive 第8章 函数 系统内置函数
  16. 用FFmpeg保存JPEG图片
  17. 数据库系统原理(1)--绪论
  18. 缓存的穿透、击穿、雪崩分别是什么,有什么解决方法
  19. 通过IP地址绘制信息地图(Python+PowerBI+MapBox)
  20. python爬斗鱼直播房间名和主播名_斗鱼爬虫,爬取颜值频道的主播图片和名字

热门文章

  1. java 减法基础_java基础之运算符
  2. 今日头条电脑版官方版_imclass电脑版下载-imclass在线教室pc版下载 v1.0.2 官方版...
  3. 2017级C语言大作业 - 元气骑士
  4. 银行招聘考试题库计算机,2019银行招聘计算机试题(一)答案
  5. 5W2H分析法,哪哪儿都能用到的方法,人生也可以套路进来
  6. element ui 表单验证残留提示处理
  7. puml绘制思维导图_思维导图的使用方法和技巧
  8. oracle数据库cp命令,Oracle数据库备份与恢复(I)
  9. “通用大模型”趋势下,AI未来当如何?
  10. 自动驾驶如何处理突发状况?