这周花三天做了一demo,算上之前的,怎么也有五天,上一篇是opencv介绍,以及定义native方法,通过本地图片路径传参,底层调用Opencv图像库合成,有兴趣的可以看看,这篇重点在于krpano的全景图展示,话说刚才上传了22张片照片合成全景图,感觉有半个小时也没有合成完,我这电脑是有多垃圾

那我们代码走一走(都说不上代码是老流氓)

这是demo的目录结构

@Controller

public class PanoramaController {

/**

* @Description: 文件上传以及全景合成

* @Date: 15:58 2018/7/6

* @Params: * @param null

*/

@RequestMapping("/upload")

public ModelAndView login07(@RequestParam(value = "file", required = false)MultipartFile[] files,

@RequestParam(value = "title",defaultValue = "未命名") String title,HttpServletRequest request) {

ModelAndView mv=new ModelAndView("success.html");

MultipartFile tempMultipartFile;

//基本路径

StringBuffer sb=new StringBuffer();

String baseURL="E:/demo";

String tempStr = "/" + UUID.randomUUID().toString();

for (int j = 0; j < files.length; j++) {

tempMultipartFile = files[j];

// 获得上传的文件名称-带有后缀

String fileNameAndSuffixName = tempMultipartFile.getOriginalFilename();

// 获取上传的文件名称

//String fileName = fileNameAndSuffixName.substring(0, fileNameAndSuffixName.lastIndexOf("."));

String urlPath;

urlPath = Imgeupload.fileUpdata(tempMultipartFile, "E:/demo", "" + tempStr);

System.out.println("=====" + urlPath);

if (j==files.length-2){

sb.append(baseURL+tempStr+"/"+urlPath);

break;

}else if (j

sb.append(baseURL+tempStr+"/"+urlPath+",");

}else {

System.out.println();

}

}

//拼接URL

System.out.println("拼接URL"+sb.toString());

//调用native

String result= OpenCVUtil.changeArrValue(sb.toString());

if(!result.contains(",")){

mv.setViewName("failure.html");

return mv;

}

//System.out.println(result);

//复制图片

String basedirNew=UUID.randomUUID().toString()+","+title;

String dirNew="D:\\tupian\\img\\"+basedirNew+"\\";

try {

copyFile(new File("D:/result.jpg"),new File(dirNew+title+".jpg"),dirNew);

} catch (IOException e) {

e.printStackTrace();

}

mv.addObject("imgUrl","/img/"+basedirNew+"/"+title+".jpg");

mv.addObject("title",title);

mv.addObject("fileName",basedirNew);

return mv;

}

@RequestMapping("/index")

public String login06() {

return "upload.html";

}

/**

* @Description: 复制生成的图片到全景图静态区

* @Date: 9:45 2018/7/5

* @Params: * @param null

*/

public void copyFile(File fromFile, File toFile,String fromUrl) throws IOException {

File file=new File(fromUrl);

if (!file.exists()) {

file.mkdir();

}

FileInputStream ins = new FileInputStream(fromFile);

FileOutputStream out = new FileOutputStream(toFile);

byte[] b = new byte[1024];

int n=0;

while((n=ins.read(b))!=-1){

out.write(b, 0, n);

}

ins.close();

out.close();

}

}

这个主要是文件上传以及全景图合成调用native,生成result.jpg全景图,利用生成的全景图通过krpano工具,生成相应的文件放在tomcat对应webapp,启动tomcat即可访问,这是最开始使用krpano看效果的流程,当然java项目不可能手动复制粘贴文件吧,一切都是通过程序控制,所以不可避免java IO操作,这时遇到一个很大的坑,听我细细道来

当时想已经通过krpano生成的相应的文件复制到webapp这部分的操作通过io进行操作,后来才发现,用IO发现权限不够,我使用的是spring boot 项目,tomcat是嵌入式,所以复制到当前项目classes文件下,那换一条路既然生成的都是静态文件,那从当前的项目,引用绝对路径,从本地获取静态资源,呵呵,通过指定端口当用的项目,静态资源权限只限当前项目,从网上收集许多资料,最终使用

spring:

resources:static-locations: //相当于,这块空间和static目录下,resource下,webjar下同级,

//而这块空间可以io进行操作

好了,这个问题解决了,那java代码如何将某个图片拖到本地某个应用XX.bat上看如下代码

public class CmdBat {

/*public static void main(String[] args) {

Room r = new Room();

//项目的位置

String dpath = "D:\\tupian\\vshow";

//全景图的位置

String file = "3";

String[] fn1 = { "2",

"3" };

String[] fn2 = { "客厅", "卧室","大客厅" };

String title = "哈哈哈哈哈哈哈哈";

String music = "vshow/backgroundmusic/default.mp3";

try {

setKrpano(r,dpath, file, fn1, fn2, title,music);

} catch (InterruptedException e) {

e.printStackTrace();

System.out.println("上传失败");

}

}*/

/**

* @Description:

* @Date: 10:15 2018/7/6

* @Params: * @param null

*/

public static void setKrpano(final Room r, final String dpath, final String file,

final String[] fn1, final String[] fn2, final String title, final String music)

throws InterruptedException {

//全景图存的位置

final String temppath = "D:\\tupian\\img\\";

String path = temppath+file;

String ex = "krpanotools32.exe makepano -config=templates\\vtour-multires.config "

+ path + "\\*.jpg";

//执行

Runtime runtime = Runtime.getRuntime();

boolean b = true;

Process p = null;

try {

//krpano 安装位置

p = runtime.exec("cmd /c start D:\\Krpano\\krpano.1.19.pr16\\krpano-1.19-pr16\\" + ex);

} catch (Exception e) {

b = false;

}

if (b) {

final InputStream is1 = p.getInputStream();

final InputStream is2 = p.getErrorStream();

new Thread() {

public void run() {

BufferedReader br1 = new BufferedReader(

new InputStreamReader(is1));

try {

String line1 = null;

while ((line1 = br1.readLine()) != null) {

if (line1 != null) {

System.out.println("=AA==========line1======"

+ line1);

}

}

} catch (IOException e) {

e.printStackTrace();

} finally {

try {

is1.close();

// 执行文件复制

File f = new File(dpath + "\\" + file);

f.mkdirs();// 创建目录

// 复制文件

boolean b1 = copyFile(temppath + file

+ "\\vtour\\tour.js", dpath + "\\" + file

+ "\\tour.js");

if (b1) {

boolean b2 = copyFile(temppath + file

+ "\\vtour\\tour.swf", dpath + "\\"

+ file + "\\tour.swf");

if (b2) {

boolean b3 = copyFile(temppath

+ file + "\\vtour\\tour.xml", dpath

+ "\\" + file + "\\tour.xml");

if (b3) {

// 复制文件夹

boolean b4 = copyFolder(

temppath + file

+ "\\vtour\\panos",

dpath + "\\" + file + "\\panos");

if (b4) {

// 删除临时生成文件

delFolder(temppath + file);

// 修改krpano文件内容

String xmlPath = dpath + "\\"

+ file + "\\tour.xml";

File xmlFile = new File(xmlPath);

DocumentBuilderFactory dbFactory = DocumentBuilderFactory

.newInstance();

DocumentBuilder dBuilder;

try {

dBuilder = dbFactory

.newDocumentBuilder();

Document doc = dBuilder

.parse(xmlFile);

doc.getDocumentElement()

.normalize();

for (int i = 0; i < fn1.length; i++) {

updateAttributeValue(doc,

fn1[i], fn2[i]);

}

// update Element value

updateElementValue(doc, title);

// delete element

deleteElement(doc);

// add new element

addElement(doc);

updateAttributeColorValue(doc,

"0x000000");

addMusicElement(doc,music);

// write the updated document to

// file or console

doc.getDocumentElement()

.normalize();

TransformerFactory transformerFactory = TransformerFactory

.newInstance();

Transformer transformer = transformerFactory

.newTransformer();

DOMSource source = new DOMSource(

doc);

StreamResult result = new StreamResult(

new File(xmlPath));

transformer.setOutputProperty(

OutputKeys.INDENT,

"yes");

transformer.transform(source,

result);

//生成成功

r.setMark("1");

//AdminService as = ContextUtil.getBean(AdminService.class, "adminService");

//as.updateRoom(r);

/*System.out

.println("XML file updated successfully");*/

} catch (

SAXException

| ParserConfigurationException

| IOException

| TransformerException e1) {

e1.printStackTrace();

//生成失败

r.setMark("2");

//AdminService as = ContextUtil.getBean(AdminService.class, "adminService");

//as.updateRoom(r);

}

}

}

}

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

}.start();

new Thread() {

public void run() {

BufferedReader br2 = new BufferedReader(

new InputStreamReader(is2));

try {

String line2 = null;

while ((line2 = br2.readLine()) != null) {

if (line2 != null) {

System.out.println("=AA==========line2======"

+ line2);

}

}

} catch (IOException e) {

e.printStackTrace();

} finally {

try {

is2.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}.start();

p.waitFor();

p.destroy();

} else {

System.out.println("上传失败");

}

}

/**

* 复制单个文件

*

* @param oldPath

* String 原文件路径 如:c:/fqf.txt

* @param newPath

* String 复制后路径 如:f:/fqf.txt

* @return boolean

*/

public static boolean copyFile(String oldPath, String newPath) {

try {

int bytesum = 0;

int byteread = 0;

File oldfile = new File(oldPath);

if (oldfile.exists()) { // 文件存在时

InputStream inStream = new FileInputStream(oldPath); // 读入原文件

FileOutputStream fs = new FileOutputStream(newPath);

byte[] buffer = new byte[1444];

int length;

while ((byteread = inStream.read(buffer)) != -1) {

bytesum += byteread; // 字节数 文件大小

// System.out.println(bytesum);

fs.write(buffer, 0, byteread);

}

inStream.close();

}

} catch (Exception e) {

// System.out.println("复制单个文件操作出错");

e.printStackTrace();

return false;

}

return true;

}

/**

* 复制整个文件夹内容

*

* @param oldPath

* String 原文件路径 如:c:/fqf

* @param newPath

* String 复制后路径 如:f:/fqf/ff

* @return boolean

*/

public static boolean copyFolder(String oldPath, String newPath) {

try {

(new File(newPath)).mkdirs(); // 如果文件夹不存在 则建立新文件夹

File a = new File(oldPath);

String[] file = a.list();

File temp = null;

for (int i = 0; i < file.length; i++) {

if (oldPath.endsWith(File.separator)) {

temp = new File(oldPath + file[i]);

} else {

temp = new File(oldPath + File.separator + file[i]);

}

if (temp.isFile()) {

FileInputStream input = new FileInputStream(temp);

FileOutputStream output = new FileOutputStream(newPath

+ "/" + (temp.getName()).toString());

byte[] b = new byte[1024 * 5];

int len;

while ((len = input.read(b)) != -1) {

output.write(b, 0, len);

}

output.flush();

output.close();

input.close();

}

if (temp.isDirectory()) {// 如果是子文件夹

copyFolder(oldPath + "/" + file[i], newPath + "/" + file[i]);

}

}

} catch (Exception e) {

// System.out.println("复制整个文件夹内容操作出错");

e.printStackTrace();

return false;

}

return true;

}

// 删除文件夹

public static void delFolder(String folderPath) {

try {

delAllFile(folderPath); // 删除完里面所有内容

String filePath = folderPath;

filePath = filePath.toString();

File myFilePath = new File(filePath);

myFilePath.delete(); // 删除空文件夹

} catch (Exception e) {

e.printStackTrace();

}

}

public static boolean delAllFile(String path) {

boolean flag = false;

File file = new File(path);

if (!file.exists()) {

return flag;

}

if (!file.isDirectory()) {

return flag;

}

String[] tempList = file.list();

File temp = null;

for (int i = 0; i < tempList.length; i++) {

if (path.endsWith(File.separator)) {

temp = new File(path + tempList[i]);

} else {

temp = new File(path + File.separator + tempList[i]);

}

if (temp.isFile()) {

temp.delete();

}

if (temp.isDirectory()) {

delAllFile(path + "/" + tempList[i]);// 先删除文件夹里面的文件

delFolder(path + "/" + tempList[i]);// 再删除空文件夹

flag = true;

}

}

return flag;

}

private static void addElement(Document doc) {

NodeList employees = doc.getElementsByTagName("krpano");

Element emp = null;

// loop for each employee

for (int i = 0; i < employees.getLength(); i++) {

emp = (Element) employees.item(i);

Element vtourskin = doc.createElement("include");

vtourskin.setAttribute("url", "../skin/vtourskin.xml");

emp.appendChild(vtourskin);

Element skinselect = doc.createElement("include");

skinselect.setAttribute("url", "../skinselect.xml");

emp.appendChild(skinselect);

}

}

private static void addMusicElement(Document doc,String music) {

NodeList employees = doc.getElementsByTagName("krpano");

Element emp = null;

// loop for each employee

for (int i = 0; i < employees.getLength(); i++) {

emp = (Element) employees.item(i);

Element musicEl = doc.createElement("action");

musicEl.setAttribute("name", "bgsnd_action");

musicEl.setAttribute("autorun", "onstart");

musicEl.appendChild(doc.createTextNode("playsound(bgsnd, '"+music+"', 0);"));

emp.appendChild(musicEl);

}

}

private static void deleteElement(Document doc) {

NodeList employees = doc.getElementsByTagName("krpano");

Element emp = null;

// loop for each employee

for (int i = 0; i < employees.getLength(); i++) {

emp = (Element) employees.item(i);

Node genderNode = emp.getElementsByTagName("include").item(0);

emp.removeChild(genderNode);

}

}

private static void updateElementValue(Document doc, String title) {

NodeList employees = doc.getElementsByTagName("krpano");

Element emp = null;

// loop for each employee

for (int i = 0; i < employees.getLength(); i++) {

emp = (Element) employees.item(i);

emp.setAttribute("title", title);

}

}

private static void updateAttributeValue(Document doc, String oldname,

String newname) {

NodeList employees = doc.getElementsByTagName("scene");

Element emp = null;

// loop for each employee

for (int i = 0; i < employees.getLength(); i++) {

emp = (Element) employees.item(i);

if (emp.getAttribute("title").equals(oldname)) {

emp.setAttribute("title", newname);

break;

}

}

}

private static void updateAttributeColorValue(Document doc, String newname) {

NodeList employees = doc.getElementsByTagName("skin_settings");

Element emp = null;

// loop for each employee

for (int i = 0; i < employees.getLength(); i++) {

emp = (Element) employees.item(i);

emp.setAttribute("design_bgcolor", newname);

emp.setAttribute("design_bgalpha", "0.8");

}

}

}

去掉mian函数的注释,启动一下,看看执行结果,这段代码相当于手动拖动图片到.bat上,其中可以修改krpano下的templates下的vtour-multires.config文件

# basic settings

include basicsettings.config

panotype=sphere

# panotype=autodetect

hfov=360

makescenes=true

自动生成一种固定的全景图(柱型,球型...)

Ok,看一下生成文件中哪些是固定的公用的

@Controller

public class FileLibraryController {

/**

* @Description: 从文件中获取全景图

* @Date: 17:13 2018/7/5

* @Params: * @param null

*/

@RequestMapping("/all")

public ModelAndView list(Model model){

// List wjList = new ArrayList();//新建一个文件集合

List list=new ArrayList<>();

File file=new File(ConstantBank.PANORAMA_BANK_URL);

ModelAndView mv=new ModelAndView("list.html");

File[] fileList = file.listFiles();//将该目录下的所有文件放置在一个File类型的数组中

for (int i = 0; i < fileList.length; i++) {

if (fileList[i].isDirectory()) {//判断是否为文件

// wjList.add(fileList[i]);

String directoryName=fileList[i].getName();

if (!directoryName.equals(ConstantBank.EXCLUSIVE_SECOND_DIR_NAEM)&&!directoryName.equals(ConstantBank.EXCLUSIVE_FIRST_DIR_NAME)){

//分割文件名

String title =directoryName.substring(directoryName.indexOf(",")+1);

PanoramaDO panoramaDO=new PanoramaDO();

panoramaDO.setId(directoryName);

if (title==null||title.equals("")){

panoramaDO.setTitle("未命名");

}else {

panoramaDO.setTitle(title);

}

list.add(panoramaDO);

//System.out.println(directoryName);

//System.out.println(title);

}

}

}

System.out.println(list);

mv.addObject("list",list);

return mv;

}

@RequestMapping("/{id}/temp")

public ModelAndView jump(@PathVariable("id") String vid){

ModelAndView mv =new ModelAndView("vr.html");

//mv.getView().

mv.addObject("vid",vid);

String title =vid.substring(vid.indexOf(",")+1);

if (title==null||title.equals("")){

mv.addObject("title","未命名");

}else {

mv.addObject("title",title);

}

System.out.println("===>"+vid);

return mv;

}

@RequestMapping("/compound/{fileName}/{title}")

public ModelAndView compound(@PathVariable("fileName") String fileName,

@PathVariable("title") String title){

ModelAndView mv =new ModelAndView("redirect:/all");

//mv.getView().

Room r = new Room();

//项目的位置

String dpath = "D:\\tupian\\vshow";

//全景图的文件名

String file = fileName;

String[] fn1 = { "2",

"3" };

String[] fn2 = { "客厅", "卧室","大客厅" };

//String title = "哈哈哈哈哈哈哈哈";

String music = "vshow/backgroundmusic/default.mp3";

try {

CmdBat.setKrpano(r,dpath, file, fn1, fn2, title,music);

} catch (InterruptedException e) {

e.printStackTrace();

System.out.println("上传失败");

}

return mv;

}

}

vr.html  公用的vr.html

@-ms-viewport { width:device-width; }

@media only screen and (min-device-width:800px) { html { overflow:hidden; } }

html { height:100%; }

body { height:100%; overflow:hidden; margin:0; padding:0; font-family:Arial, Helvetica, sans-serif; font-size:16px; color:#FFFFFF; background-color:#000000; }

logo.jpg

ERROR:
Javascript not activated

/*

var vid = "[[${vid}]]";

embedpano({swf:"/vshow/"+vid+"/tour.swf", xml:"/vshow/"+vid+"/tour.xml", target:"pano", html5:"prefer", mobilescale:1.0, passQueryParameters:true});

/* ]]>*/

upload.html  这是文件上传以及回显,下面的js等待加载过度

.uploadImgBtn {

width: 100px;

height: 100px;

cursor: pointer;

position: relative;

background: url("img/plus.png") no-repeat;

-webkit-background-size: cover;

background-size: cover;

}

.uploadImgBtn .uploadImg {

position: absolute;

right: 0;

top:0;

width: 100%;

height: 100%;

opacity: 0;

cursor: pointer;

}

//这是一个用做回显的盒子的样式

.pic{

width: 100px;

height: 100px;

}

.pic img {

width: 200px;

height: 100px;

}

合成全景图片的名字

function haha() {

var html01='

全景图正在合成请稍等...

';

$(".fakeloader").append($(html01));

$(".fakeloader").fakeLoader({

timeToHide:1200000,

bgColor:"#d9d2e9",

spinner:"spinner2"

});

}

$(document).ready(function(){

//为外面的盒子绑定一个点击事件

$("#uploadImgBtn").click(function(){

/*

1、先获取input标签

2、给input标签绑定change事件

3、把图片回显

*/

// 1、先回去input标签

var $input = $("#file");

console.log($input)

// 2、给input标签绑定change事件

$input.on("change" , function(){

console.log(this)

//补充说明:因为我们给input标签设置multiple属性,因此一次可以上传多个文件

//获取选择图片的个数

var files = this.files;

var length = files.length;

console.log("选择了"+length+"张图片");

//3、回显

$.each(files,function(key,value){

//每次都只会遍历一个图片数据

var div = document.createElement("div"),

img = document.createElement("img");

div.className = "pic";

var fr = new FileReader();

fr.onload = function(){

img.src=this.result;

div.appendChild(img);

document.body.appendChild(div);

}

fr.readAsDataURL(value);

})

})

//4、我们把当前input标签的id属性remove

$input.removeAttr("id");

//我们做个标记,再class中再添加一个类名就叫test

var newInput = '';

$(this).append($(newInput));

})

})

这写到这把,后续会弄全景漫游....这些都要整合项目里面

java 全景_java实现,使用opencv合成全景图,前端使用krpano展示相关推荐

  1. 全景视频拼接(二)--OpenCV源码解析

    本文参考:http://blog.csdn.net/skeeee/article/details/19480693,做了一定修改和补充. 一.stitching_detail程序运行流程 1.命令行调 ...

  2. [转载]全景视频拼接(二)--OpenCV源码解析

    转载自 [https://blog.csdn.net/App_12062011/article/details/52438357] 本文参考:http://blog.csdn.net/skeeee/a ...

  3. 【源码+教程】Java课设项目_12款最热最新Java游戏项目_Java游戏开发_Java小游戏_飞翔的小鸟_王者荣耀_超级玛丽_推箱子_黄金矿工_贪吃蛇

    马上就要期末了,同学们课设做的如何了呢?本篇为大家带来了12款热门Java小游戏项目的源码和教程,助力大家顺利迎接暑假![源码+教程]Java课设项目_12款最热最新Java游戏项目_Java游戏开发 ...

  4. 【Opencv】Python+openCV实现全景图拼接(左右两张图片拼接成一张全景图)

    Python+openCV实现全景图拼接(左右两张图片拼接成一张全景图) 全景图拼接 思路 具体步骤 代码与结果 代码 效果测试1 效果测试2 全景图拼接 思路 这个就是简单对左右两张图进行拼接,希望 ...

  5. 全景管家在线解析720yun、蛙色中krpano全景图

    现如今,进入科技时代的我们,VR技术发展的时代,krpano全景图在当今是必不可少的存在,现在有不少的全景平台,绝大多数都是由krpano来开发的. 最近有很多小伙伴来找我要全景图资源,我挖了好久才找 ...

  6. Java在eclipse中调用opencv时报错:java.lang.UnsatisfiedLinkError的解决方法

    昨天把Java+opencv的环境配置好后,迫不及待的进行测试...... 出师不利.... 第一个代码就有错误,代码如下: import org.opencv.core.*; import org. ...

  7. java通过模板匹配html,OpenCV模板匹配

    目标 在本教程中,您将学习如何:使用OpenCV功能matchTemplate()来搜索图像补丁和输入图像之间的匹配 使用OpenCV函数minMaxLoc()来查找给定数组中的最大值和最小值(以及它 ...

  8. Java实现多段小视频合成一个视频

    Java实现多段小视频合成一个视频 import java.io.*; import java.util.*; import java.util.stream.Collectors;public cl ...

  9. 黑马程序员全套Java教程_Java基础教程_异常(含扩展)(二十三)

    黑马程序员全套Java教程_Java基础教程_异常(含扩展)(二十三) 1.1 异常概述与异常体系结构 1.2 JVM遇到异常时的默认处理方案 1.3 异常处理 1.4 异常处理之try--catch ...

最新文章

  1. linux主设备编号从0到多少,Linux驱动开发之主设备号找驱动,次设备号找设备
  2. seg显示时间——51程序
  3. vue-router配置介绍和使用方法(一)
  4. Java实现计算景区门票GUI版(入门)
  5. markdown如何修改为 微软雅黑 字体
  6. Spring相关面试题总结
  7. 分享112个留言聊天PHP源码,总有一款适合你
  8. Redis与数据库数据同步解决方案
  9. 个性化定制将成鞋服行业未来开拓新方向
  10. document.querySelector()方法
  11. 苹果cmsV10暗色系在线动漫影视网站模板
  12. 药品过5关价翻12倍 批发商抢走药品一半利润
  13. Stack OverFlow 正确的使用姿势(快速访问、优雅搜索)
  14. TImage、TPaintBox、TPicture、TBitmap、TCanvas、TGraphic 的关系与区别作者:万一 来源:博客园 发布时间:2009-01-09 23:01
  15. 备忘-华为认证HCIP路由交换V2.5考试大纲(HCIP-Routing Switching V2.5认证考试)
  16. Zabbix报警 More than 100 items having missing data for more than 10 minutes
  17. php 数组 json_decode,php中json_decode返回数组或对象_PHP教程
  18. RFID相关知识总结(超高频UHF)
  19. C语言+图形编程——自制象棋
  20. element-ui el-table表格出现抖动闪动问题的解决

热门文章

  1. html 中的 css 的强制换行与不换行文本
  2. 【服务器数据恢复】EMC存储Zfs文件系统下raid5数据恢复案例
  3. MATLAB2014a,MEX编译问题
  4. 苹果xr十大隐藏功能_Win10系统中你想象不到的十大隐藏功能
  5. 工科女的自嘲:聪明的女生,长大后都变成了男人
  6. 浪潮sa5112m5和sam5212m5服务器配置管理口和raid方法
  7. python实现匈牙利算法
  8. 【01Studio MaixPy AI K210】6.PWM
  9. git ssh远程登录
  10. 微信开发(六)微信分享接入