初步在安卓上实现魔方还原助手。之前考虑用Qt框架实现跨平台,但相应的移动端开发资料实在是太少,暂时不考虑。后续考虑跨平台实现的可能性。
因为之前没有接触过安卓开发,没法具体到每步该如何开展,但是可以把开发阶段分为以下几大步
安装好后启动
新建一个新的项目
Rubik Cube Helper
真心好难用……各种被封
SDK使用代理
mirrors.neusoft.edu.cn:80
gradle 不太清楚作用是什么
而且gradle.properties
文件代理出bug导致一直提示无法下载,删除重复项后可以下载了
C:\Users\name.gradle\gradle.properties
居然也会有重复项……
项目的build.gradle
里加上代理
allprojects {
repositories {
google()
jcenter()
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
}
}
到选择一个模板稍作修改,编译部署
编译环境已经搭建好了,我们现在需要考虑如何实现拍照并存图。
先使用系统自带的API,熟悉安卓应用的基本开发之后再考虑做一个带有标记的拍照界面,类似微信扫一扫。
可能需要先学习Java……
主活动
子页面拍照
保存至本地
到这一步可以用octave离线计算
因为相机拍摄的有空隙,所以手动截取了
后续需要补充手机应用自动截取保存
Up.png
2 5 1
2 6 2
4 5 5
Front.png
2 6 1
6 1 6
3 5 6
Down.png
2 2 3
1 2 4
3 1 2
Right.png
6 3 4
3 4 1
5 3 5
Back.png
6 3 5
2 3 1
1 5 4
Left.png
3 4 1
6 5 4
6 4 4
人肉对比了下,应该是正确的
随机生成魔方状态并还原
结果显示在底部
Android 好像自带矩阵运算,可以考虑用直接用矩阵运算解决。
经过半天的折腾最终Android Studio 下opencv配置好了。
转灰度图了
Mark下 2019年7月8日 22点02分 经过一个星期的努力,现在软件终于可以实现拍照并给出还原步骤
相机拍摄到的六个面原始图像
2019-07-08 22:05:29.648 W/CubeState: loadImages() >
2019-07-08 22:05:29.648 W/CubeState: loadImage() > Up.png
2019-07-08 22:05:29.652 W/CubeState: loadImage() /storage/emulated/0/Rubik Cube Pictures/Up.png
2019-07-08 22:05:29.662 W/CubeState: width=188 height=188
2019-07-08 22:05:29.663 W/CubeState: loadImage() <
2019-07-08 22:05:29.663 W/CubeState: loadImage() > Right.png
2019-07-08 22:05:29.664 W/CubeState: loadImage() /storage/emulated/0/Rubik Cube Pictures/Right.png
2019-07-08 22:05:29.669 W/CubeState: width=188 height=188
2019-07-08 22:05:29.669 W/CubeState: loadImage() <
2019-07-08 22:05:29.669 W/CubeState: loadImage() > Front.png
2019-07-08 22:05:29.671 W/CubeState: loadImage() /storage/emulated/0/Rubik Cube Pictures/Front.png
2019-07-08 22:05:29.675 W/CubeState: width=188 height=188
2019-07-08 22:05:29.676 W/CubeState: loadImage() <
2019-07-08 22:05:29.676 W/CubeState: loadImage() > Down.png
2019-07-08 22:05:29.677 W/CubeState: loadImage() /storage/emulated/0/Rubik Cube Pictures/Down.png
2019-07-08 22:05:29.682 W/CubeState: width=188 height=188
2019-07-08 22:05:29.683 W/CubeState: loadImage() <
2019-07-08 22:05:29.683 W/CubeState: loadImage() > Left.png
2019-07-08 22:05:29.685 W/CubeState: loadImage() /storage/emulated/0/Rubik Cube Pictures/Left.png
2019-07-08 22:05:29.690 W/CubeState: width=188 height=188
2019-07-08 22:05:29.690 W/CubeState: loadImage() <
2019-07-08 22:05:29.690 W/CubeState: loadImage() > Back.png
2019-07-08 22:05:29.692 W/CubeState: loadImage() /storage/emulated/0/Rubik Cube Pictures/Back.png
2019-07-08 22:05:29.697 W/CubeState: width=188 height=188
2019-07-08 22:05:29.697 W/CubeState: loadImage() <
2019-07-08 22:05:29.698 W/CubeState: loadImages() <
2019-07-08 22:05:29.698 W/CubeState: toGray() >
2019-07-08 22:05:29.699 W/CubeState: toGray() <
2019-07-08 22:05:29.699 W/CubeState: getCubeEdgeColor() >
2019-07-08 22:05:29.699 W/CubeState: getGraySumMat() >
2019-07-08 22:05:29.701 W/CubeState: getGraySumMat() <
2019-07-08 22:05:29.701 W/CubeState: getCubeEdgeColor() Core.mean(graySumMat)117.79023313716614
2019-07-08 22:05:29.701 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.702 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.702 W/CubeState: getCubeEdgeColor() edge color is black
2019-07-08 22:05:29.702 W/CubeState: getCubeEdgeColor() facelet center mean = 140.66666666666666
2019-07-08 22:05:29.702 W/CubeState: getCubeEdgeColor() grayCentersMat [ 3*3*CV_8UC1, isCont=true, isSubmat=false, nativeObj=0x7f7b4406a0, dataAddr=0x7f897feec0 ]
2019-07-08 22:05:29.702 W/CubeState: getCubeEdgeColor() <
2019-07-08 22:05:29.702 W/CubeState: getHSV() >
2019-07-08 22:05:29.703 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.704 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.705 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.706 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.707 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.707 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.709 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.709 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.711 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.711 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.713 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.713 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.714 W/CubeState: getHSV() <
2019-07-08 22:05:29.714 W/CubeState: getSaturationGray() >
2019-07-08 22:05:29.714 W/CubeState: getSaturationGray() Up getFaceletCenter
2019-07-08 22:05:29.714 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.715 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.715 W/CubeState: getSaturationGray() Right getFaceletCenter
2019-07-08 22:05:29.715 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.716 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.717 W/CubeState: getSaturationGray() Front getFaceletCenter
2019-07-08 22:05:29.717 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.717 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.718 W/CubeState: getSaturationGray() Down getFaceletCenter
2019-07-08 22:05:29.718 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.718 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.719 W/CubeState: getSaturationGray() Left getFaceletCenter
2019-07-08 22:05:29.719 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.719 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.720 W/CubeState: getSaturationGray() Back getFaceletCenter
2019-07-08 22:05:29.720 W/CubeState: getFaceletCenter() >
2019-07-08 22:05:29.720 W/CubeState: getFaceletCenter() <
2019-07-08 22:05:29.720 W/CubeState: getSaturationGray() >
2019-07-08 22:05:29.720 W/CubeState: getWhiteThresh() >
2019-07-08 22:05:29.724 W/CubeState: getHueThresh() hueThresh [0]=4 [1]=37 [2]=70 [3]=102 [4]=164
2019-07-08 22:05:29.724 W/CubeState: getHueThresh() <
2019-07-08 22:05:29.724 W/CubeState: getLabel() >
2019-07-08 22:05:29.725 W/CubeState: getLabel() colorLabel [0]=4 [1]=6 [2]=3 [3]=2 [4]=6 [5]=3 [6]=2 [7]=1 [8]=3 [9]=5 [10]=4 [11]=4 [12]=5 [13]=4 [14]=5 [15]=3 [16]=4 [17]=1 [18]=1 [19]=2 [20]=2 [21]=6 [22]=1 [23]=1 [24]=1 [25]=2 [26]=5 [27]=6 [28]=3 [29]=6 [30]=5 [31]=2 [32]=1 [33]=5 [34]=6 [35]=6 [36]=6 [37]=4 [38]=4 [39]=6 [40]=5 [41]=1 [42]=2 [43]=2 [44]=4 [45]=2 [46]=3 [47]=3 [48]=3 [49]=3 [50]=4 [51]=5 [52]=5 [53]=1
2019-07-08 22:05:29.725 W/CubeState: getLabel() <
2019-07-08 22:05:29.726 W/CubeState: getLabelString() faceLabel [0]=6 [1]=4 [2]=1 [3]=2 [4]=5 [5]=3
2019-07-08 22:05:29.726 W/ItemListActivity: kociembaInput RUBDUBDFBLRRLRLBRFFDDUFFFDLUBULDFLUUURRULFDDRDBBBBRLLF
2019-07-08 22:05:29.750 W/ItemListActivity: kociemba solution = U2 R' D2 R2 F D2 F' R B L U2 L2 B2 U2 R2 D' R2 U' L2 F2
不到1s时间内读图处理并给出还原步骤
其中大多都是软件相关问题,由于时间关系,后续不一定有时间跟进。
居然在快完成时在Google商店上看到了Cubex,实在是无奈……
作者diozz是用C#基于.Net框架开发的软件,使用过程中很容易会碰到应用卡死的情况。
和Cubex侧重点不同,我们更加侧重于魔方的姿态识别。而Cubex更加倾向于软件的应用。
Cube的思路应该是每个面简单把色块分类,而这个分类是直接根据预先设定的值来判断的。
这样就涉及到了一个有意思的事情,兼容性。
本来之前是用Java编写3D还原动画,但是看到Cubex后觉得还是直接用threejs 就可以了。
作者在软件里给出了开源项目链接,我就直接引用了。
虽然都有java,但是是两个不同的语言
使用Cuber 的过程中,百思不得其解Cubex是如何建立魔方初始状态的。
我的思路是根据还原步骤,从原始状态逆序到当前魔方状态。
但是这样,初始化的时间特别长。
尝试修改初始化过程中cubes.js
内的cubelet赋值,修改后魔方没有渲染出来。
根据对cuber项目的理解,属性应该是改了,但是不知道改如何重新渲染。
想了几个小时都没有想明白Cubex到底是如何实现的,忍不住出手逆向了……
下载到了APK的包,从逆向的.dex文件中并没有看到有用的信息。
webView.loadUrl("javascript:cube.twistQueue.add('R')");
这句话是我在java中调用JavaScript的语句
按照我的理解,编译后字符串是不可能消失的
也就是说除非作者重写了部分模块,否则不可能实现。
对比Cuber-DEMO和Cubex资源下的cube.js 作者加了一个属性,旋转时间。
难道diozz这么简单粗暴的解决了问题?
因为Cubex是用xamarin开发的,所以从包下的Dll文件开始找思路,果然有个CubeX.dll
返编译后最终找到了
loader.webView.EvaluateJavascript("cube.setDuration(0); camera.fov = 30; cube.twistQueue.add('" + str + "'); ", null);
果然,就是这么粗暴!
先按照这个思路解决,不得不说Cubex的界面做的是真好看,魔方助手的界面简直惨不忍睹啊!前端培训班了解下……