@ -0,0 +1,15 @@ |
|||
*.iml |
|||
.gradle |
|||
/local.properties |
|||
/.idea/caches |
|||
/.idea/libraries |
|||
/.idea/modules.xml |
|||
/.idea/workspace.xml |
|||
/.idea/navEditor.xml |
|||
/.idea/assetWizardSettings.xml |
|||
.DS_Store |
|||
/build |
|||
/captures |
|||
.externalNativeBuild |
|||
.cxx |
|||
local.properties |
@ -0,0 +1 @@ |
|||
/build |
@ -0,0 +1,88 @@ |
|||
plugins { |
|||
alias(libs.plugins.android.application) |
|||
alias(libs.plugins.kotlin.android) |
|||
kotlin("plugin.serialization") version "2.1.0" |
|||
} |
|||
|
|||
android { |
|||
namespace = "com.example.myopencv" |
|||
compileSdk = 34 |
|||
|
|||
defaultConfig { |
|||
applicationId = "com.example.myopencv" |
|||
minSdk = 24 |
|||
targetSdk = 34 |
|||
versionCode = 1 |
|||
versionName = "1.0" |
|||
|
|||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" |
|||
vectorDrawables { |
|||
useSupportLibrary = true |
|||
} |
|||
} |
|||
|
|||
buildTypes { |
|||
release { |
|||
isMinifyEnabled = false |
|||
proguardFiles( |
|||
getDefaultProguardFile("proguard-android-optimize.txt"), |
|||
"proguard-rules.pro" |
|||
) |
|||
} |
|||
} |
|||
compileOptions { |
|||
sourceCompatibility = JavaVersion.VERSION_17 |
|||
targetCompatibility = JavaVersion.VERSION_17 |
|||
} |
|||
kotlinOptions { |
|||
jvmTarget = "17" |
|||
} |
|||
buildFeatures { |
|||
compose = true |
|||
} |
|||
composeOptions { |
|||
kotlinCompilerExtensionVersion = "1.5.1" |
|||
} |
|||
packaging { |
|||
resources { |
|||
excludes += "/META-INF/{AL2.0,LGPL2.1}" |
|||
} |
|||
} |
|||
} |
|||
|
|||
dependencies { |
|||
|
|||
implementation(libs.androidx.core.ktx) |
|||
implementation(libs.androidx.lifecycle.runtime.ktx) |
|||
implementation(libs.androidx.activity.compose) |
|||
implementation(platform(libs.androidx.compose.bom)) |
|||
implementation(libs.androidx.ui) |
|||
implementation(libs.androidx.ui.graphics) |
|||
implementation(libs.androidx.ui.tooling.preview) |
|||
implementation(libs.androidx.material3) |
|||
implementation(project(":opencv")) |
|||
testImplementation(libs.junit) |
|||
androidTestImplementation(libs.androidx.junit) |
|||
androidTestImplementation(libs.androidx.espresso.core) |
|||
androidTestImplementation(platform(libs.androidx.compose.bom)) |
|||
androidTestImplementation(libs.androidx.ui.test.junit4) |
|||
debugImplementation(libs.androidx.ui.tooling) |
|||
debugImplementation(libs.androidx.ui.test.manifest) |
|||
} |
|||
|
|||
dependencies { |
|||
|
|||
// Camerax implementation |
|||
//def cameraxVersion = "1.3.1" |
|||
implementation (libs.camera.core) |
|||
implementation (libs.androidx.camera.camera.camera22) |
|||
implementation (libs.androidx.camera.camera.view) |
|||
implementation (libs.camera.lifecycle) |
|||
// Camerax implementation |
|||
} |
|||
|
|||
|
|||
dependencies { |
|||
implementation(libs.core.ktx) |
|||
implementation(libs.kotlinx.serialization.json) |
|||
} |
@ -0,0 +1,21 @@ |
|||
# Add project specific ProGuard rules here. |
|||
# You can control the set of applied configuration files using the |
|||
# proguardFiles setting in build.gradle. |
|||
# |
|||
# For more details, see |
|||
# http://developer.android.com/guide/developing/tools/proguard.html |
|||
|
|||
# If your project uses WebView with JS, uncomment the following |
|||
# and specify the fully qualified class name to the JavaScript interface |
|||
# class: |
|||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
|||
# public *; |
|||
#} |
|||
|
|||
# Uncomment this to preserve the line number information for |
|||
# debugging stack traces. |
|||
#-keepattributes SourceFile,LineNumberTable |
|||
|
|||
# If you keep the line number information, uncomment this to |
|||
# hide the original source file name. |
|||
#-renamesourcefileattribute SourceFile |
@ -0,0 +1,84 @@ |
|||
package com.example.myopencv |
|||
|
|||
import android.content.Context |
|||
import android.graphics.BitmapFactory |
|||
import androidx.test.core.app.ApplicationProvider |
|||
import androidx.test.platform.app.InstrumentationRegistry |
|||
import androidx.test.ext.junit.runners.AndroidJUnit4 |
|||
|
|||
import org.junit.Test |
|||
import org.junit.runner.RunWith |
|||
|
|||
import org.junit.Assert.* |
|||
import org.opencv.android.Utils |
|||
import org.opencv.core.CvType |
|||
import org.opencv.core.Mat |
|||
|
|||
/** |
|||
* Instrumented test, which will execute on an Android device. |
|||
* |
|||
* See [testing documentation](http://d.android.com/tools/testing). |
|||
*/ |
|||
@RunWith(AndroidJUnit4::class) |
|||
class ExampleInstrumentedTest { |
|||
@Test |
|||
fun useAppContext() { |
|||
// Context of the app under test. |
|||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext |
|||
assertEquals("com.example.myopencv", appContext.packageName) |
|||
} |
|||
|
|||
|
|||
@Test |
|||
fun testLoadBitmapFromResource() { |
|||
//照片选取 计算后的xReal=-127.2813 yReal=94.4458 |
|||
//测点区域 x=97,y=1054,w=569,z=425 |
|||
// 质心 x=-284.0299 , y=210.900 |
|||
val imageResourceId =R.drawable.zp_127_245___210_90 |
|||
val options = BitmapFactory.Options() |
|||
options.inScaled = false |
|||
|
|||
|
|||
// 获取应用程序上下文 |
|||
val context: Context = ApplicationProvider.getApplicationContext() |
|||
val rawBitmap = BitmapFactory.decodeResource(context.resources, imageResourceId,options) |
|||
val rawMat = Mat(rawBitmap.width, rawBitmap.height, CvType.CV_8UC3) |
|||
Utils.bitmapToMat(rawBitmap, rawMat) |
|||
//灰度 |
|||
val garyMat =imageHandler.mat2gary(rawMat) |
|||
val garyBitmap = imageHandler.mat2bitMap(garyMat) |
|||
|
|||
//二值化 |
|||
val thresholdValue=128.0 |
|||
val binaryBitmap=imageHandler.binaryBitMap(thresholdValue,garyMat) |
|||
|
|||
|
|||
//图像截取 取值实际配置的区域参数 |
|||
var x=97 |
|||
var y=1054 |
|||
var w=569 |
|||
var h=425 |
|||
|
|||
val subMat=imageHandler.cropMat(rawMat,x,y,w,h) |
|||
val subBitMat= imageHandler.mat2bitMap(subMat) |
|||
|
|||
val subGary=imageHandler.mat2gary(subMat) |
|||
val binaryThreshold=30.0 |
|||
val subBinary=imageHandler.binaryMat(binaryThreshold,subGary) |
|||
val center=imageHandler.calcMoments(subBinary) |
|||
println("center数据= $center") |
|||
} |
|||
} |
|||
|
|||
|
|||
class ConfigLoadTest { |
|||
@Test |
|||
fun useAppContext() { |
|||
// Context of the app under test. |
|||
// val appContext = InstrumentationRegistry.getInstrumentation().targetContext |
|||
// val info=UserInfo(2017,"lucas","249324454@qq.com","male") |
|||
// SharedPrefs.saveUserToPreferences(appContext,info) |
|||
// val infoRead=SharedPrefs.loadUserFromPreferences(appContext) |
|||
// assertEquals(info, infoRead) |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:tools="http://schemas.android.com/tools"> |
|||
|
|||
<application |
|||
android:allowBackup="true" |
|||
android:dataExtractionRules="@xml/data_extraction_rules" |
|||
android:fullBackupContent="@xml/backup_rules" |
|||
android:icon="@mipmap/ic_launcher" |
|||
android:label="@string/app_name" |
|||
android:roundIcon="@mipmap/ic_launcher_round" |
|||
android:supportsRtl="true" |
|||
android:theme="@style/Theme.MyOpenCv" |
|||
tools:targetApi="31"> |
|||
<activity |
|||
android:name=".MainActivity" |
|||
android:exported="true" |
|||
android:theme="@style/Theme.MyOpenCv"> |
|||
<intent-filter> |
|||
<action android:name="android.intent.action.MAIN" /> |
|||
|
|||
<category android:name="android.intent.category.LAUNCHER" /> |
|||
</intent-filter> |
|||
</activity> |
|||
|
|||
<service |
|||
android:name=".MyTcpServer" |
|||
android:enabled="true" |
|||
android:exported="true"> |
|||
</service> |
|||
<service |
|||
android:name=".UdpServer" |
|||
android:enabled="true" |
|||
android:exported="true"> |
|||
</service> |
|||
</application> |
|||
<uses-permission android:name="android.permission.INTERNET" /> |
|||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
|||
|
|||
|
|||
<uses-permission android:name="android.permission.CAMERA" /> |
|||
<uses-feature android:name="android.hardware.camera.any" /> |
|||
|
|||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
|||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> |
|||
</manifest> |
@ -0,0 +1,311 @@ |
|||
package com.example.myopencv |
|||
|
|||
import ImageHandler |
|||
import android.Manifest |
|||
import android.content.ComponentName |
|||
import android.content.Context |
|||
import android.content.Intent |
|||
import android.content.ServiceConnection |
|||
import android.content.pm.PackageManager |
|||
import android.graphics.BitmapFactory |
|||
import android.os.Bundle |
|||
import android.os.IBinder |
|||
import android.util.Log |
|||
import android.widget.Toast |
|||
import androidx.activity.ComponentActivity |
|||
import androidx.activity.compose.setContent |
|||
import androidx.activity.enableEdgeToEdge |
|||
import androidx.activity.result.contract.ActivityResultContracts |
|||
import androidx.compose.foundation.Image |
|||
import androidx.compose.foundation.layout.fillMaxSize |
|||
import androidx.compose.foundation.layout.padding |
|||
import androidx.compose.foundation.layout.size |
|||
import androidx.compose.foundation.layout.wrapContentSize |
|||
import androidx.compose.foundation.lazy.LazyColumn |
|||
import androidx.compose.material3.Button |
|||
import androidx.compose.material3.HorizontalDivider |
|||
import androidx.compose.material3.Scaffold |
|||
import androidx.compose.material3.Text |
|||
import androidx.compose.runtime.Composable |
|||
import androidx.compose.runtime.getValue |
|||
import androidx.compose.runtime.mutableIntStateOf |
|||
import androidx.compose.runtime.remember |
|||
import androidx.compose.runtime.setValue |
|||
import androidx.compose.ui.Alignment |
|||
import androidx.compose.ui.Modifier |
|||
import androidx.compose.ui.graphics.Color |
|||
import androidx.compose.ui.graphics.asImageBitmap |
|||
import androidx.compose.ui.layout.ContentScale |
|||
import androidx.compose.ui.platform.LocalContext |
|||
import androidx.compose.ui.res.painterResource |
|||
import androidx.compose.ui.tooling.preview.Preview |
|||
import androidx.compose.ui.unit.dp |
|||
import androidx.core.content.ContextCompat |
|||
import com.example.myopencv.ui.theme.MyOpenCvTheme |
|||
import org.opencv.android.OpenCVLoader |
|||
import org.opencv.android.Utils |
|||
import org.opencv.core.CvType |
|||
import org.opencv.core.Mat |
|||
import org.opencv.core.Scalar |
|||
import org.opencv.imgproc.Imgproc |
|||
|
|||
|
|||
val imageHandler = ImageHandler() |
|||
lateinit var myTcpSvc: MyTcpServer |
|||
lateinit var myUdpSvc: UdpServer |
|||
val deflectometer= Deflectometer() |
|||
|
|||
class MainActivity : ComponentActivity() { |
|||
|
|||
private var binderTcp: MyTcpServer.LocalBinder? = null |
|||
private var binderUdp: UdpServer.LocalBinder? = null |
|||
|
|||
override fun onCreate(savedInstanceState: Bundle?) { |
|||
super.onCreate(savedInstanceState) |
|||
enableEdgeToEdge() |
|||
val configContent=fileUtils.readerFile(this,"settings.json") |
|||
deflectometer.defaultSet(configContent) |
|||
val isOk=OpenCVLoader.initLocal() |
|||
if (isOk){ |
|||
Toast.makeText(this,"opencv加载成功",Toast.LENGTH_SHORT).show() |
|||
}else{ |
|||
Toast.makeText(this, |
|||
"opencv加载失败,退出", |
|||
Toast.LENGTH_SHORT).show() |
|||
finish() |
|||
} |
|||
|
|||
applyForPermission() |
|||
|
|||
val serviceIntentTcp = Intent(this, MyTcpServer::class.java) |
|||
bindService(serviceIntentTcp, serviceConnectionTcp, Context.BIND_AUTO_CREATE) |
|||
|
|||
val serviceIntentUdp = Intent(this, UdpServer::class.java) |
|||
bindService(serviceIntentUdp, serviceConnectionUdp, Context.BIND_AUTO_CREATE) |
|||
|
|||
|
|||
when (PackageManager.PERMISSION_GRANTED) { |
|||
ContextCompat.checkSelfPermission( |
|||
this, |
|||
Manifest.permission.CAMERA |
|||
) -> { |
|||
setCameraPreview() |
|||
} |
|||
} |
|||
} |
|||
|
|||
private val serviceConnectionTcp = object : ServiceConnection { |
|||
override fun onServiceConnected(className: ComponentName, iBService: IBinder) { |
|||
//绑定到服务,可以获取服务中的Binder实例 |
|||
binderTcp = iBService as MyTcpServer.LocalBinder |
|||
myTcpSvc = binderTcp!!.getService() |
|||
} |
|||
|
|||
override fun onServiceDisconnected(arg0: ComponentName) { |
|||
binderTcp = null |
|||
} |
|||
} |
|||
|
|||
private val serviceConnectionUdp = object : ServiceConnection { |
|||
override fun onServiceConnected(className: ComponentName, iBService: IBinder) { |
|||
//绑定到服务,可以获取服务中的Binder实例 |
|||
binderUdp = iBService as UdpServer.LocalBinder |
|||
myUdpSvc = binderUdp!!.getService() |
|||
} |
|||
|
|||
override fun onServiceDisconnected(arg0: ComponentName) { |
|||
binderUdp = null |
|||
} |
|||
} |
|||
|
|||
|
|||
private val cameraPermissionRequest = |
|||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> |
|||
if (isGranted) { |
|||
setCameraPreview() |
|||
Log.i("权限","同意授予") |
|||
} else { |
|||
// Camera permission denied |
|||
Log.i("权限","拒绝授予") |
|||
} |
|||
|
|||
} |
|||
private fun setImageHandler() { |
|||
setContent { |
|||
MyOpenCvTheme { |
|||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> |
|||
Greeting( |
|||
name = "Android,来了", |
|||
modifier = Modifier.padding(innerPadding) |
|||
) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private fun setCameraPreview() { |
|||
setContent { |
|||
CameraPreviewScreen() |
|||
} |
|||
} |
|||
|
|||
|
|||
private fun applyForPermission(){ |
|||
if (ContextCompat.checkSelfPermission( |
|||
this, Manifest.permission.WRITE_EXTERNAL_STORAGE |
|||
)!=PackageManager.PERMISSION_GRANTED ) { |
|||
cameraPermissionRequest.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) |
|||
Log.i("权限", "请求外部存储") |
|||
} |
|||
if (ContextCompat.checkSelfPermission( |
|||
this, Manifest.permission.CAMERA |
|||
)!= PackageManager.PERMISSION_GRANTED ) { |
|||
cameraPermissionRequest.launch(Manifest.permission.CAMERA) |
|||
Log.i("权限", "请求相机") |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
@Composable |
|||
fun Greeting(name: String, modifier: Modifier = Modifier) { |
|||
|
|||
//照片选取 |
|||
var result by remember { mutableIntStateOf(2) } |
|||
val imageResource = when (result) { |
|||
1 -> R.drawable.zp_1 |
|||
else -> R.drawable.yu_5 |
|||
} |
|||
val options = BitmapFactory.Options() |
|||
options.inScaled = false |
|||
val rawBitmap = BitmapFactory.decodeResource(LocalContext.current.resources, imageResource,options) |
|||
val rawMat =Mat(rawBitmap.width, rawBitmap.height, CvType.CV_8UC3) |
|||
Utils.bitmapToMat(rawBitmap, rawMat) |
|||
//灰度 |
|||
val garyMat =imageHandler.mat2gary(rawMat) |
|||
val garyBitmap = imageHandler.mat2bitMap(garyMat) |
|||
|
|||
//二值化 |
|||
val thresholdValue=128.0 |
|||
val binaryBitmap=imageHandler.binaryBitMap(thresholdValue,garyMat) |
|||
|
|||
|
|||
//图像截取 取值实际配置的区域参数 |
|||
var x=600 |
|||
var y=600 |
|||
var w=252 |
|||
var h=186 |
|||
if (result==2 ||result==6){ |
|||
x=97 |
|||
y=1054 |
|||
w=569 |
|||
h=425 |
|||
} |
|||
|
|||
//用外包方式的计算质心 |
|||
val viewRect=android.graphics.Rect(x,y,x+w-1,y+h-1) |
|||
val wbc=imageHandler.getBinImageCentroid(rawBitmap,30,viewRect) |
|||
// |
|||
|
|||
val subMat=imageHandler.cropMat(rawMat,x,y,w,h) |
|||
val subBitMat= imageHandler.mat2bitMap(subMat) |
|||
|
|||
val subGary=imageHandler.mat2gary(subMat) |
|||
val binaryThreshold=30.0 |
|||
val subBinary=imageHandler.binaryMat(binaryThreshold,subGary) |
|||
val center=imageHandler.calcMoments(subBinary) |
|||
//测试 |
|||
// val deflectometer= Deflectometer() |
|||
// val cp= CentroidPoint(center.x,center.y) |
|||
// val centroidDataArray= CentroidData("", arrayOf(cp)) |
|||
// val r=deflectometer.dataProcess(centroidDataArray) |
|||
// println("挠度数据= $r") |
|||
//测试 |
|||
|
|||
// 在原图像上标记质心 |
|||
Imgproc.circle(subMat, center, 5, Scalar(0.0, 0.0, 255.0), 2) |
|||
val centerBitMap=imageHandler.mat2bitMap(subMat) |
|||
LazyColumn( |
|||
modifier = Modifier |
|||
.fillMaxSize() |
|||
.wrapContentSize(), |
|||
horizontalAlignment = Alignment.CenterHorizontally |
|||
) { |
|||
item { |
|||
|
|||
Image( |
|||
painter = painterResource(imageResource), |
|||
contentDescription = result.toString(), |
|||
Modifier.size(300.dp), |
|||
contentScale = ContentScale.Fit |
|||
) |
|||
|
|||
Image( |
|||
bitmap = garyBitmap.asImageBitmap(), |
|||
contentDescription = "灰度", |
|||
Modifier.size(300.dp), |
|||
contentScale = ContentScale.Fit |
|||
) |
|||
|
|||
Button(onClick = { |
|||
result = (1..7).random() |
|||
|
|||
}) { |
|||
Text("灰化效果") |
|||
} |
|||
} |
|||
|
|||
|
|||
item { |
|||
HorizontalDivider(color = Color(0.1f, 0.8f, 0.9f, 1.0f)) |
|||
Image( |
|||
bitmap = binaryBitmap.asImageBitmap(), |
|||
contentDescription = "二值化", |
|||
Modifier.size(300.dp), |
|||
contentScale = ContentScale.Fit |
|||
) |
|||
} |
|||
|
|||
item { |
|||
Image( |
|||
bitmap = subBitMat.asImageBitmap(), |
|||
contentDescription = "区域截取", |
|||
Modifier.size(300.dp), |
|||
contentScale = ContentScale.Fit |
|||
) |
|||
} |
|||
|
|||
item { |
|||
Image( |
|||
bitmap = imageHandler.mat2bitMap(subGary).asImageBitmap(), |
|||
contentDescription = "区域灰度", |
|||
Modifier.size(300.dp), |
|||
contentScale = ContentScale.Fit |
|||
) |
|||
} |
|||
|
|||
item { |
|||
Image( |
|||
bitmap = centerBitMap.asImageBitmap(), |
|||
contentDescription = "质心标记", |
|||
Modifier.size(300.dp), |
|||
contentScale = ContentScale.Fit |
|||
) |
|||
} |
|||
|
|||
item { |
|||
Text("底部,没有了") |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
|
|||
@Preview(showBackground = true) |
|||
@Composable |
|||
fun GreetingPreview() { |
|||
MyOpenCvTheme { |
|||
//Greeting("Android") |
|||
} |
|||
} |
@ -0,0 +1,77 @@ |
|||
package com.example.myopencv |
|||
import android.app.Service |
|||
import android.content.Intent |
|||
import android.os.Binder |
|||
import android.os.IBinder |
|||
import android.util.Log |
|||
import java.net.DatagramPacket |
|||
import java.net.DatagramSocket |
|||
import java.net.InetAddress |
|||
|
|||
|
|||
|
|||
class UdpServer:Service(){ |
|||
// 内部Binder类 |
|||
inner class LocalBinder : Binder() { |
|||
// 返回TcpService的实例 |
|||
fun getService(): UdpServer = this@UdpServer |
|||
} |
|||
private val binder = LocalBinder() |
|||
override fun onBind(intent: Intent?): IBinder { |
|||
println("==onBind") |
|||
return binder |
|||
} |
|||
override fun onCreate() { |
|||
super.onCreate() |
|||
Thread(runnable).start() |
|||
println("启动 udp runnable") |
|||
} |
|||
|
|||
override fun onDestroy() { |
|||
super.onDestroy() |
|||
Log.d("data","-> onDestroy") |
|||
} |
|||
|
|||
private var runnable= kotlinx.coroutines.Runnable { |
|||
udpWork() |
|||
} |
|||
private fun udpWork(){ |
|||
val serverSocket= DatagramSocket(2230) |
|||
println(serverSocket.localSocketAddress) |
|||
println(serverSocket.localAddress) |
|||
while (true) { |
|||
val buffer = ByteArray(102) |
|||
val packet = DatagramPacket(buffer, buffer.size) |
|||
|
|||
serverSocket.receive(packet) |
|||
|
|||
val remoteAddress = packet.address |
|||
val remotePort = packet.port |
|||
val recvMsg = String(packet.data, 0, packet.length) |
|||
// 创建应答数据 |
|||
val responseMsg=decodeCmd(recvMsg,this) |
|||
val response = responseMsg.toByteArray() |
|||
|
|||
// 发送应答 |
|||
val responsePacket = DatagramPacket(response, response.size, remoteAddress, remotePort) |
|||
serverSocket.send(responsePacket) |
|||
println("收到${remoteAddress}:${remotePort}搜索设备命令${recvMsg}") |
|||
println("应答搜索${responseMsg}") |
|||
} |
|||
} |
|||
|
|||
private fun sendData(msg:String,ip:String){ |
|||
val socket = DatagramSocket() |
|||
val address = InetAddress.getByName(ip) |
|||
val port = 2230 |
|||
|
|||
val data = msg.toByteArray() |
|||
val packet = DatagramPacket(data, data.size, address, port) |
|||
socket.send(packet) |
|||
socket.close() |
|||
println("发送完毕") |
|||
} |
|||
|
|||
} |
|||
|
|||
|
@ -0,0 +1,262 @@ |
|||
package com.example.myopencv |
|||
|
|||
import ImageHandler |
|||
import android.content.ContentValues |
|||
import android.content.Context |
|||
import android.graphics.Bitmap |
|||
import android.graphics.BitmapFactory |
|||
import android.graphics.Rect |
|||
import android.icu.text.SimpleDateFormat |
|||
import android.os.Build |
|||
import android.provider.MediaStore |
|||
import android.util.Log |
|||
import androidx.camera.core.CameraSelector |
|||
import androidx.camera.core.ImageAnalysis |
|||
import androidx.camera.core.ImageCapture |
|||
import androidx.camera.core.ImageCaptureException |
|||
import androidx.camera.core.ImageProxy |
|||
import androidx.camera.core.Preview |
|||
import androidx.camera.core.resolutionselector.ResolutionSelector |
|||
import androidx.camera.core.resolutionselector.ResolutionStrategy |
|||
import androidx.camera.lifecycle.ProcessCameraProvider |
|||
import androidx.camera.view.PreviewView |
|||
import androidx.compose.foundation.Image |
|||
import androidx.compose.foundation.layout.Box |
|||
import androidx.compose.foundation.layout.Column |
|||
import androidx.compose.foundation.layout.Spacer |
|||
import androidx.compose.foundation.layout.fillMaxSize |
|||
import androidx.compose.foundation.layout.padding |
|||
import androidx.compose.foundation.layout.safeDrawingPadding |
|||
import androidx.compose.foundation.layout.size |
|||
import androidx.compose.material3.Button |
|||
import androidx.compose.material3.Text |
|||
import androidx.compose.runtime.Composable |
|||
import androidx.compose.runtime.LaunchedEffect |
|||
import androidx.compose.runtime.mutableDoubleStateOf |
|||
import androidx.compose.runtime.mutableLongStateOf |
|||
import androidx.compose.runtime.mutableStateOf |
|||
import androidx.compose.runtime.remember |
|||
import androidx.compose.ui.Alignment |
|||
import androidx.compose.ui.Modifier |
|||
import androidx.compose.ui.graphics.Color |
|||
import androidx.compose.ui.graphics.asImageBitmap |
|||
import androidx.compose.ui.layout.ContentScale |
|||
import androidx.compose.ui.platform.LocalContext |
|||
import androidx.compose.ui.platform.LocalLifecycleOwner |
|||
import androidx.compose.ui.unit.dp |
|||
import androidx.compose.ui.unit.sp |
|||
import androidx.compose.ui.viewinterop.AndroidView |
|||
import androidx.core.content.ContextCompat |
|||
import com.example.myopencv.models.CentroidData |
|||
import com.example.myopencv.models.UData |
|||
import kotlinx.coroutines.GlobalScope |
|||
import kotlinx.coroutines.launch |
|||
import kotlinx.coroutines.runBlocking |
|||
import kotlinx.serialization.encodeToString |
|||
import kotlinx.serialization.json.Json |
|||
import java.util.Locale |
|||
import java.util.concurrent.Executors |
|||
import kotlin.coroutines.resume |
|||
import kotlin.coroutines.suspendCoroutine |
|||
|
|||
@Composable |
|||
fun CameraPreviewScreen() { |
|||
val imageHandler = ImageHandler() |
|||
val cameraExecutor=Executors.newSingleThreadExecutor() |
|||
val lensFacing = CameraSelector.LENS_FACING_BACK |
|||
val lifecycleOwner = LocalLifecycleOwner.current |
|||
val context = LocalContext.current |
|||
// val previewView = remember { |
|||
// PreviewView(context) |
|||
// } |
|||
val cameraxSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build() |
|||
// val imageCapture = remember { |
|||
// ImageCapture.Builder().build() |
|||
// } |
|||
|
|||
|
|||
val timeStampState= remember { mutableLongStateOf(0) } |
|||
val xView= remember { mutableDoubleStateOf(0.0) } |
|||
val yView= remember { mutableDoubleStateOf(0.0) } |
|||
//var centroidObj by remember { mutableStateOf(0.0) } |
|||
// val rawMat = Mat(2112, 1568, CvType.CV_8UC3) |
|||
// val options = BitmapFactory.Options() |
|||
// options.inScaled = false |
|||
val rawBitmap = BitmapFactory.decodeResource(LocalContext.current.resources, R.drawable.yu_5) |
|||
|
|||
val yuMap= remember { mutableStateOf(rawBitmap) } |
|||
|
|||
//val preview = Preview.Builder().build() |
|||
val imageAnalyzer = ImageAnalysis.Builder() |
|||
.setResolutionSelector( |
|||
ResolutionSelector.Builder(). |
|||
setResolutionStrategy(ResolutionStrategy.HIGHEST_AVAILABLE_STRATEGY) |
|||
//.setAspectRatioStrategy(AspectRatioStrategy.RATIO_16_9_FALLBACK_AUTO_STRATEGY) |
|||
.build() |
|||
) |
|||
.build() |
|||
.also { |
|||
it.setAnalyzer(cameraExecutor, GdndAnalyzer { img -> |
|||
val imageBitMapRaw=img.toBitmap() |
|||
//缓存bitmap 用于图像上传 |
|||
val rotation = img.imageInfo.rotationDegrees //180 |
|||
//println("角度信息: $rotation") |
|||
|
|||
val imageBitMap=imageHandler.rotateBitmap(imageBitMapRaw, rotation.toFloat()) |
|||
val grayBitMap=imageHandler.bitMap2Gray(imageBitMap) |
|||
yuMap.value=imageBitMap |
|||
timeStampState.longValue=System.currentTimeMillis() |
|||
val timeStr=timeNow2string() |
|||
val centroidDataArray= CentroidData(timeStr, mutableListOf()) |
|||
val threshold=deflectometer.readThreshold() |
|||
|
|||
deflectometer.readSensor().forEach{ s -> |
|||
val x=s.x.toInt() |
|||
val y=s.y.toInt() |
|||
val w=s.w.toInt() |
|||
val h=s.h.toInt() |
|||
//cv计算 |
|||
//val p=imageHandler.img2Moments(img,threshold,x,y,w,h) |
|||
|
|||
//外包计算方式 |
|||
val viewRect= Rect(x,y,x+w-1,y+h-1) |
|||
val p=imageHandler.getBinImageCentroid(imageBitMap,threshold,viewRect) |
|||
//Log.d("传感器[${s.pos}] ${s.des}", "计算图像[w=${img.width},h=${img.height}]质心${p}") |
|||
centroidDataArray.centroids.add(p) |
|||
} |
|||
|
|||
myTcpSvc.cacheImage(grayBitMap) |
|||
val mResult=deflectometer.dataProcess(centroidDataArray) |
|||
// val uploadData=result2uData(mResult) |
|||
// val uploadDataJson=Json.encodeToString<UData>(uploadData) |
|||
// myTcpSvc.send2All(uploadDataJson) |
|||
|
|||
// //preview显示首个测点数据 |
|||
// if (mResult.sensorsData.size>0){ |
|||
// xView.doubleValue=mResult.sensorsData[0].xReal |
|||
// yView.doubleValue=mResult.sensorsData[0].yReal |
|||
// } |
|||
|
|||
0.0 |
|||
}) |
|||
} |
|||
|
|||
LaunchedEffect(lensFacing) { |
|||
val cameraProvider = context.getCameraProvider() |
|||
cameraProvider.unbindAll() |
|||
cameraProvider.bindToLifecycle(lifecycleOwner, cameraxSelector, imageAnalyzer) |
|||
//注意实际运行的时候 注释preview |
|||
//preview.setSurfaceProvider(previewView.surfaceProvider) |
|||
} |
|||
|
|||
|
|||
Box(contentAlignment = Alignment.BottomCenter, modifier = Modifier.fillMaxSize()) { |
|||
//AndroidView({ previewView }, modifier = Modifier.fillMaxSize()) |
|||
Column( |
|||
modifier = Modifier |
|||
.padding(horizontal = 20.dp) |
|||
.safeDrawingPadding(), |
|||
horizontalAlignment = Alignment.CenterHorizontally, |
|||
|
|||
) { |
|||
|
|||
Text( |
|||
text = time2string(timeStampState.longValue), |
|||
color = Color.Green, |
|||
fontSize = 30.sp |
|||
) |
|||
Text( |
|||
text = "xReal= ${String.format(Locale.CHINA,"%.5f", xView.doubleValue)}\nyReal= ${String.format(Locale.CHINA,"%.5f", yView.doubleValue)}", |
|||
color = Color.Green, |
|||
fontSize = 30.sp |
|||
) |
|||
Image( |
|||
bitmap = yuMap.value.asImageBitmap(), |
|||
contentDescription = "图像区域截取", |
|||
Modifier.size(500.dp), |
|||
contentScale = ContentScale.Fit |
|||
) |
|||
Button( |
|||
onClick = { |
|||
//captureImage(imageCapture, context) |
|||
} |
|||
) { |
|||
Text(text = "抓拍") |
|||
} |
|||
|
|||
Spacer(Modifier.size(60.dp)) |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
private fun captureImage(imageCapture: ImageCapture, context: Context) { |
|||
val tName= timeNow2string() |
|||
val dirName="光电" |
|||
val name = "${dirName}_${tName}.jpeg" |
|||
val contentValues = ContentValues().apply { |
|||
put(MediaStore.MediaColumns.DISPLAY_NAME, name) |
|||
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") |
|||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { |
|||
put(MediaStore.Images.Media.RELATIVE_PATH, "光电抓拍拍") |
|||
} |
|||
} |
|||
val outputOptions = ImageCapture.OutputFileOptions |
|||
.Builder( |
|||
context.contentResolver, |
|||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, |
|||
contentValues |
|||
) |
|||
.build() |
|||
imageCapture.takePicture( |
|||
outputOptions, |
|||
ContextCompat.getMainExecutor(context), |
|||
object : ImageCapture.OnImageSavedCallback { |
|||
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { |
|||
Log.i("照片存储","Successes") |
|||
} |
|||
|
|||
override fun onError(exception: ImageCaptureException) { |
|||
Log.i("照片存储","Failed $exception") |
|||
} |
|||
|
|||
}) |
|||
} |
|||
|
|||
private suspend fun Context.getCameraProvider(): ProcessCameraProvider = |
|||
suspendCoroutine { continuation -> |
|||
ProcessCameraProvider.getInstance(this).also { cameraProvider -> |
|||
cameraProvider.addListener({ |
|||
continuation.resume(cameraProvider.get()) |
|||
}, ContextCompat.getMainExecutor(this)) |
|||
} |
|||
} |
|||
|
|||
|
|||
fun timeNow2string() :String{ |
|||
val sf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS",Locale.CHINA) |
|||
return sf.format(System.currentTimeMillis()) |
|||
} |
|||
|
|||
private fun time2string(timeStamp:Long) :String{ |
|||
val sf = SimpleDateFormat("yyyyMMdd_HHmmss.SSS",Locale.CHINA) |
|||
return sf.format(timeStamp) |
|||
} |
|||
|
|||
|
|||
|
|||
private class GdndAnalyzer(private val listener: imageListener) : ImageAnalysis.Analyzer { |
|||
|
|||
override fun analyze(image: ImageProxy) { |
|||
|
|||
//图像处理 |
|||
//Log.i("图像处理","处理图片帧") |
|||
listener(image) |
|||
image.close() |
|||
} |
|||
} |
|||
|
|||
|
|||
typealias imageListener = (img: ImageProxy) -> Double |
|||
|
@ -0,0 +1,90 @@ |
|||
package com.example.myopencv |
|||
|
|||
import android.content.Context |
|||
import android.content.SharedPreferences |
|||
import com.example.myopencv.models.UserInfo |
|||
import kotlinx.serialization.Serializable |
|||
import kotlinx.serialization.json.Json |
|||
import kotlinx.serialization.encodeToString |
|||
|
|||
|
|||
|
|||
object SharedPrefs { |
|||
private const val TOKEN_PREF = "TOKEN_PREF" |
|||
private const val TOKEN_KEY = "TOKEN_KEY" |
|||
|
|||
private fun getPreferences(context: Context): SharedPreferences { |
|||
return context.getSharedPreferences(TOKEN_PREF, Context.MODE_PRIVATE) |
|||
} |
|||
|
|||
// 存储单个值 |
|||
// 调用 : SharedPrefs.setToken(this@MyActivity, "123456") |
|||
fun setToken(context: Context, token: String) { |
|||
val editor = getPreferences(context).edit() |
|||
editor.putString(TOKEN_KEY, token) |
|||
editor.apply() |
|||
} |
|||
|
|||
// 获取单个值 |
|||
// 调用 : token = SharedPrefs.getToken(this@MyActivity) |
|||
fun getToken(context: Context?): String? { |
|||
return context?.let { getPreferences(it).getString(TOKEN_KEY, null) } |
|||
} |
|||
|
|||
// 移除单个值 |
|||
// 调用SharedPrefs.removeToken(this@MyActivity) |
|||
fun removeToken(context: Context?) { |
|||
context?.let { getPreferences(it).edit().remove(TOKEN_KEY).apply() } |
|||
} |
|||
|
|||
// 存储userInfo对象 |
|||
// 调用 : SharedPrefs.saveUserToPreferences(this@MyActivity, jsonElement) |
|||
fun saveUserToPreferences(context: Context, userinfo: UserInfo) { |
|||
val json = Json.encodeToString(userinfo) |
|||
val sharedPreferences = context.getSharedPreferences("UserPrefs", Context.MODE_PRIVATE) |
|||
val editor = sharedPreferences.edit() |
|||
editor.putString("userinfo", json) |
|||
editor.apply() |
|||
} |
|||
|
|||
// 获取userInfo对象缓存 |
|||
// 调用 : userInfo = SharedPrefs.loadUserFromPreferences(this@MyActivity) userId = userInfo .id |
|||
fun loadUserFromPreferences(context: Context): UserInfo { |
|||
val sharedPreferences = context.getSharedPreferences("UserPrefs", Context.MODE_PRIVATE) |
|||
val json = sharedPreferences.getString("userinfo", "") |
|||
val userInfo = Json.decodeFromString<UserInfo>(json?:"") |
|||
return userInfo |
|||
} |
|||
|
|||
// 更新userInfo对象某个字段 |
|||
// 调用 : SharedPrefs.setUserInfo(this@MyActivity, "username", "alic") |
|||
fun setUserInfo(context: Context, editName: String, editValue: String) { |
|||
val sharedPreferences = context.getSharedPreferences("UserPrefs", Context.MODE_PRIVATE) |
|||
val json = sharedPreferences.getString("userinfo", "") |
|||
val myObject1 = Json.decodeFromString<UserInfo>(json?:"") |
|||
|
|||
val editor = sharedPreferences.edit() |
|||
if (editName == "username") { |
|||
myObject1.username = editValue |
|||
}else if (editName == "email") { |
|||
myObject1.email = editValue |
|||
}else if (editName == "sex") { |
|||
myObject1.sex = editValue |
|||
} |
|||
|
|||
val myObject2 = Json.encodeToString(myObject1) |
|||
editor.putString("userinfo", myObject2) |
|||
println("更新后的个人信息 : $myObject2") |
|||
editor.apply() |
|||
} |
|||
|
|||
// 清除所有缓存 |
|||
// 调用 : SharedPrefs.clearAllData(this@MyActivity) |
|||
fun clearAllData(context: Context) { |
|||
val preferences = context.getSharedPreferences("UserPrefs", Context.MODE_PRIVATE) |
|||
val editor = preferences.edit() |
|||
removeToken(context) |
|||
editor.clear() |
|||
editor.apply() |
|||
} |
|||
} |
@ -0,0 +1,277 @@ |
|||
package com.example.myopencv |
|||
|
|||
import com.example.myopencv.models.CentroidData |
|||
import com.example.myopencv.models.SensorData |
|||
import com.example.myopencv.models.ManSetting2 |
|||
import com.example.myopencv.models.Sensor |
|||
import kotlinx.serialization.* |
|||
|
|||
|
|||
@Serializable |
|||
data class MResult( |
|||
var sensorsData: MutableList<SensorData>, |
|||
var timestamp:String |
|||
) |
|||
|
|||
class Deflectometer { |
|||
var imageSendEnabled=true |
|||
var isClearZero=false |
|||
private var m_settings = ManSetting2() |
|||
|
|||
|
|||
var m_result = MResult(mutableListOf(),"") |
|||
|
|||
fun dataProcess(data: CentroidData) :MResult { |
|||
// 从设置中获取零点计数,并确保其至少为1 |
|||
var zeroCount = m_settings.baseParam.zeroCount |
|||
zeroCount = if (zeroCount <= 0) 1 else zeroCount |
|||
// 获取是否清除零点的标志 |
|||
// 获取无效数据计数 |
|||
val invalidDataCount = m_settings.baseParam.invalidDataCount |
|||
// 获取传感器数组 |
|||
val t_sensors = m_settings.sensors |
|||
|
|||
// 获取结果中的传感器数组 |
|||
val sensorDataList = m_result.sensorsData |
|||
// 获取数据的时间戳 |
|||
val timestamp = data.timestampStr |
|||
// 获取质心数组 |
|||
val centroids = data.centroids |
|||
|
|||
// 遍历质心数组 |
|||
for (i in centroids.indices) { |
|||
val centroid = centroids[i] |
|||
val xCur = centroid.x |
|||
val yCur = centroid.y |
|||
if (i < sensorDataList.size && i < t_sensors.size) { |
|||
val sensorData = sensorDataList[i] |
|||
val t_sensor = t_sensors[i] |
|||
var invalidCount = sensorData.invalidCount |
|||
// 如果当前质心为(0,0) |
|||
if (xCur == 0.0 && yCur == 0.0) { |
|||
if (invalidCount < invalidDataCount) { |
|||
invalidCount++ |
|||
} |
|||
} else { |
|||
if (invalidCount > 0) { |
|||
invalidCount-- |
|||
} else { |
|||
sensorData.xCur = xCur |
|||
sensorData.yCur = yCur |
|||
var xAvg = sensorData.xAvg |
|||
var yAvg = sensorData.yAvg |
|||
// 计算平均值 |
|||
xAvg = if (xAvg == 0.0) xCur else (xAvg * (zeroCount - 1) + xCur) / (zeroCount*1.0) |
|||
yAvg = if (yAvg == 0.0) yCur else (yAvg * (zeroCount - 1) + yCur) / (zeroCount*1.0) |
|||
sensorData.xAvg = xAvg |
|||
sensorData.yAvg = yAvg |
|||
|
|||
// 如果需要清除零点 |
|||
if (isClearZero) { |
|||
sensorData.xZero = xAvg |
|||
sensorData.yZero = yAvg |
|||
t_sensor.xZero = xAvg |
|||
t_sensor.yZero = yAvg |
|||
} |
|||
|
|||
val xZero = sensorData.xZero |
|||
val yZero = sensorData.yZero |
|||
val xTemp = xCur - xZero |
|||
val yTemp = yCur - yZero |
|||
sensorData.xTemp = xTemp |
|||
sensorData.yTemp = yTemp |
|||
} |
|||
} |
|||
sensorData.invalidCount = invalidCount |
|||
sensorDataList[i] = sensorData |
|||
t_sensors[i] = t_sensor |
|||
} |
|||
} |
|||
|
|||
m_settings.sensors=t_sensors |
|||
// 重置清除零点 |
|||
if (isClearZero) { |
|||
isClearZero = false |
|||
} |
|||
|
|||
var xBase = 0.0 |
|||
var yBase = 0.0 |
|||
// 查找基准传感器 |
|||
for (i in sensorDataList.indices) { |
|||
val sensor = sensorDataList[i] |
|||
if (sensor.sensor.tar == "y") { |
|||
xBase = sensor.xTemp |
|||
yBase = sensor.yTemp |
|||
break |
|||
} |
|||
} |
|||
|
|||
// 获取结果计数 |
|||
var resultCount = m_settings.baseParam.resultCount |
|||
resultCount = if (resultCount <= 0) 1 else resultCount |
|||
// 遍历传感器数组进行计算 |
|||
for (i in sensorDataList.indices) { |
|||
val sensor = sensorDataList[i] |
|||
val invalidCount = sensor.invalidCount |
|||
if (invalidCount > 0) { |
|||
if (invalidCount >= invalidDataCount) { |
|||
sensor.xReal = Double.NaN |
|||
sensor.yReal = Double.NaN |
|||
} |
|||
} else { |
|||
val arg = sensor.sensor.arg.toDouble() |
|||
val xTemp = sensor.xTemp |
|||
val yTemp = sensor.yTemp |
|||
val xReal = sensor.xReal |
|||
val yReal = sensor.yReal |
|||
|
|||
// 计算实际值 |
|||
val xVal = (xTemp - xBase) * arg |
|||
val yVal = (yTemp - yBase) * arg |
|||
val xAvg = if (sensor.xReal.isNaN()) xVal else ((resultCount - 1) * xReal + xVal) / resultCount |
|||
val yAvg = if (sensor.yReal.isNaN()) yVal else ((resultCount - 1) * yReal + yVal) / resultCount |
|||
sensor.xReal = xAvg |
|||
sensor.yReal = yAvg |
|||
} |
|||
sensorDataList[i] = sensor |
|||
} |
|||
m_result.sensorsData = sensorDataList |
|||
m_result.timestamp = timestamp |
|||
return m_result |
|||
} |
|||
|
|||
fun readResult():MResult{ |
|||
return m_result |
|||
} |
|||
|
|||
fun defaultSet(configStr:String){ |
|||
m_settings = ManSetting2( |
|||
sensors = mutableListOf() |
|||
) |
|||
if (configStr!="") { |
|||
m_settings = json.decodeFromString<ManSetting2>(configStr) |
|||
}else { |
|||
m_settings.sensors.add( |
|||
Sensor( |
|||
arg = "0.448", |
|||
des = "default", |
|||
pos = "1", |
|||
tar = "n", |
|||
x = "97", |
|||
y = "1054", |
|||
h = "425", |
|||
w = "569", |
|||
xZero = 0.0, |
|||
yZero = 0.0 |
|||
) |
|||
) |
|||
} |
|||
|
|||
|
|||
defaultSensorsData() |
|||
} |
|||
|
|||
private fun defaultSensorsData(){ |
|||
m_result.sensorsData= mutableListOf() |
|||
m_settings.sensors.forEach { |
|||
val sd = SensorData(sensor = it) |
|||
sd.xZero=it.xZero |
|||
sd.yZero=it.yZero |
|||
m_result.sensorsData.add(sd) |
|||
} |
|||
} |
|||
|
|||
fun readSensor():List<Sensor>{ |
|||
return m_settings.sensors |
|||
} |
|||
fun updateSensor(sensorList: MutableList<Sensor>):ManSetting2{ |
|||
//纪录之前Zero |
|||
for(i in 0 until sensorList.size){ |
|||
m_settings.sensors.forEach { |
|||
if(it.pos==sensorList[i].pos){ |
|||
sensorList[i].xZero=it.xZero |
|||
sensorList[i].yZero=it.yZero |
|||
} |
|||
} |
|||
} |
|||
m_settings.sensors=sensorList |
|||
defaultSensorsData() |
|||
return m_settings |
|||
} |
|||
|
|||
fun updateThreshold(t:Int):ManSetting2{ |
|||
m_settings.baseParam.threshold=t |
|||
return m_settings |
|||
} |
|||
fun readThreshold():Int{ |
|||
return m_settings.baseParam.threshold |
|||
} |
|||
|
|||
fun updateInvalidDataCount(t:Int):ManSetting2{ |
|||
m_settings.baseParam.invalidDataCount=t |
|||
return m_settings |
|||
} |
|||
fun readInvalidDataCount():Int{ |
|||
return m_settings.baseParam.invalidDataCount |
|||
} |
|||
|
|||
fun updateImageSendTime(t:Int):ManSetting2{ |
|||
m_settings.baseParam.imageSendTime=t |
|||
return m_settings |
|||
} |
|||
fun readImageSendTime():Int{ |
|||
return m_settings.baseParam.imageSendTime |
|||
} |
|||
|
|||
fun updateZeroCount(t:Int):ManSetting2{ |
|||
m_settings.baseParam.zeroCount=t |
|||
return m_settings |
|||
} |
|||
fun readZeroCount():Int{ |
|||
return m_settings.baseParam.zeroCount |
|||
} |
|||
|
|||
fun updateResultCount(t:Int):ManSetting2{ |
|||
m_settings.baseParam.resultCount=t |
|||
return m_settings |
|||
} |
|||
fun readResultCount():Int{ |
|||
return m_settings.baseParam.resultCount |
|||
} |
|||
|
|||
fun updateDataFps(t:Int):ManSetting2{ |
|||
var interval=t |
|||
if (interval<=0){ |
|||
interval=1 |
|||
} |
|||
m_settings.baseParam.dataFPS=interval |
|||
return m_settings |
|||
} |
|||
fun readDataFps():Int{ |
|||
return m_settings.baseParam.dataFPS |
|||
} |
|||
|
|||
fun updateVideoFps(t:Int):ManSetting2{ |
|||
m_settings.baseParam.videoFPS=t |
|||
return m_settings |
|||
} |
|||
fun readVideoFps():Int{ |
|||
return m_settings.baseParam.videoFPS |
|||
} |
|||
fun updateClearZero(set: Boolean):ManSetting2 { |
|||
isClearZero=set |
|||
|
|||
//更新配置 |
|||
for (i in 0 until m_settings.sensors.size) { |
|||
val pos=m_settings.sensors[i].pos |
|||
m_result.sensorsData.forEach { |
|||
if (it.sensor.pos==pos){ |
|||
m_settings.sensors[i].xZero=it.xAvg |
|||
m_settings.sensors[i].yZero=it.yAvg |
|||
} |
|||
} |
|||
} |
|||
return m_settings |
|||
} |
|||
|
|||
} |
@ -0,0 +1,286 @@ |
|||
package com.example.myopencv |
|||
|
|||
import android.content.Context |
|||
import com.example.myopencv.models.UData |
|||
import com.example.myopencv.models.USensor |
|||
import com.example.myopencv.models.URealValue |
|||
import java.io.ByteArrayOutputStream |
|||
import android.graphics.Bitmap |
|||
import android.util.Base64 |
|||
import com.example.myopencv.models.Command |
|||
import com.example.myopencv.models.ManSetting2 |
|||
import com.example.myopencv.models.Sensor |
|||
import kotlinx.serialization.encodeToString |
|||
import kotlinx.serialization.json.Json |
|||
import kotlinx.serialization.json.JsonArray |
|||
import kotlinx.serialization.json.JsonNull |
|||
import kotlinx.serialization.json.JsonObject |
|||
import kotlinx.serialization.json.boolean |
|||
import kotlinx.serialization.json.int |
|||
import kotlinx.serialization.json.jsonArray |
|||
import kotlinx.serialization.json.jsonObject |
|||
import kotlinx.serialization.json.jsonPrimitive |
|||
|
|||
fun result2uData(r:MResult):UData{ |
|||
|
|||
val uValue=URealValue( |
|||
timestamp = r.timestamp, |
|||
sensors = mutableListOf() |
|||
) |
|||
r.sensorsData.forEach { |
|||
uValue.sensors.add( |
|||
USensor( |
|||
arg = it.sensor.arg, |
|||
des = it.sensor.des, |
|||
pos = it.sensor.pos, |
|||
tar = it.sensor.tar, |
|||
xReal = if (it.xReal.isNaN()) null else it.xReal, |
|||
yReal = if (it.yReal.isNaN()) null else it.yReal, |
|||
) |
|||
) |
|||
|
|||
|
|||
} |
|||
|
|||
return UData( |
|||
command = "result", |
|||
type = "reply", |
|||
values = uValue |
|||
) |
|||
|
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
fun bitmapToBase64(bitmap: Bitmap): String { |
|||
// 创建一个ByteArrayOutputStream |
|||
val byteArrayOutputStream = ByteArrayOutputStream() |
|||
|
|||
// 将Bitmap压缩为PNG格式并写入到ByteArrayOutputStream |
|||
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, byteArrayOutputStream) |
|||
|
|||
// 将压缩后的数据转换为字节数组 |
|||
val bytes = byteArrayOutputStream.toByteArray() |
|||
|
|||
// 使用Base64进行编码 |
|||
// 这里的 Base64.NO_WRAP 参数表示不将编码结果分割为多行 |
|||
val base64Image = Base64.encodeToString(bytes,Base64.NO_WRAP) |
|||
|
|||
// 关闭ByteArrayOutputStream |
|||
byteArrayOutputStream.close() |
|||
|
|||
// 返回Base64编码的字符串 |
|||
return base64Image |
|||
} |
|||
|
|||
|
|||
|
|||
fun decodeCmd(cmdStr:String,ctx:Context):String{ |
|||
val command = json.decodeFromString<Command>(cmdStr) |
|||
|
|||
|
|||
var reply="" |
|||
|
|||
when(command.command) { |
|||
"sensors" -> { |
|||
when (command.type) { |
|||
//传感器信息获取 |
|||
"get" -> { |
|||
val s=Json.encodeToString(deflectometer.readSensor()) |
|||
val replyCommand=Command( |
|||
command = command.command, |
|||
type = command.type, |
|||
values = Json.parseToJsonElement(s) |
|||
) |
|||
reply = Json.encodeToString(replyCommand) |
|||
} |
|||
|
|||
"set" -> { |
|||
val sensorSetStr = command.values.jsonArray.toString() |
|||
val sensors = json.decodeFromString<MutableList<Sensor>>(sensorSetStr) |
|||
val mSet = deflectometer.updateSensor(sensors) |
|||
val mSetStr=json.encodeToString<ManSetting2>(mSet) |
|||
fileUtils.saveFile(ctx,"settings.json",mSetStr) |
|||
reply = """{"command":"sensors","type":"set","values":"ok"}""" |
|||
} |
|||
} |
|||
} |
|||
|
|||
"imageSendEnabled" ->{ |
|||
when (command.type) { |
|||
//信息获取 |
|||
"set" -> { |
|||
val imageSendEnabled = command.values.jsonPrimitive.boolean |
|||
deflectometer.imageSendEnabled=imageSendEnabled |
|||
reply = """{"command":"imageSendEnabled","type":"set","values":"ok"}""" |
|||
} |
|||
} |
|||
} |
|||
|
|||
"isClearZero" ->{ |
|||
when (command.type) { |
|||
//信息获取 |
|||
"set" -> { |
|||
val isClearZero=command.values.jsonPrimitive.boolean |
|||
val mSet=deflectometer.updateClearZero(isClearZero) |
|||
val mSetStr=json.encodeToString(mSet) |
|||
fileUtils.saveFile(ctx,"settings.json",mSetStr) |
|||
reply = """{"command":"isClearZero","type":"set","values":"ok"}""" |
|||
} |
|||
} |
|||
} |
|||
|
|||
"threshold" ->{ |
|||
when (command.type) { |
|||
"set" -> { |
|||
val threshold=command.values.jsonPrimitive.int |
|||
val mSet=deflectometer.updateThreshold(threshold) |
|||
val mSetStr=json.encodeToString(mSet) |
|||
fileUtils.saveFile(ctx,"settings.json",mSetStr) |
|||
reply = """{"command":"isClearZero","type":"set","values":"ok"}""" |
|||
} |
|||
"get" -> { |
|||
val t=deflectometer.readThreshold() |
|||
reply = """{"command":"threshold","type":"get","values":${t}}""" |
|||
} |
|||
} |
|||
} |
|||
|
|||
"invalidDataCount" ->{ |
|||
when (command.type) { |
|||
"set" -> { |
|||
val invalidDataCount=command.values.jsonPrimitive.int |
|||
val mSet=deflectometer.updateInvalidDataCount(invalidDataCount) |
|||
val mSetStr=json.encodeToString<ManSetting2>(mSet) |
|||
fileUtils.saveFile(ctx,"settings.json",mSetStr) |
|||
reply = """{"command":"invalidDataCount","type":"set","values":"ok"}""" |
|||
} |
|||
"get" -> { |
|||
val t=deflectometer.readInvalidDataCount() |
|||
reply = """{"command":"invalidDataCount","type":"get","values":${t}}""" |
|||
} |
|||
} |
|||
} |
|||
|
|||
"imageSendTime" ->{ |
|||
when (command.type) { |
|||
"set" -> { |
|||
val imageSendTime=command.values.jsonPrimitive.int |
|||
val mSet=deflectometer.updateImageSendTime(imageSendTime) |
|||
val mSetStr=json.encodeToString<ManSetting2>(mSet) |
|||
fileUtils.saveFile(ctx,"settings.json",mSetStr) |
|||
reply = """{"command":"imageSendTime","type":"set","values":"ok"}""" |
|||
} |
|||
"get" -> { |
|||
val t=deflectometer.readImageSendTime() |
|||
reply = """{"command":"imageSendTime","type":"get","values":${t}}""" |
|||
} |
|||
} |
|||
} |
|||
|
|||
"zeroCount" ->{ |
|||
when (command.type) { |
|||
"set" -> { |
|||
val imageSendTime=command.values.jsonPrimitive.int |
|||
val mSet=deflectometer.updateZeroCount(imageSendTime) |
|||
val mSetStr=json.encodeToString<ManSetting2>(mSet) |
|||
fileUtils.saveFile(ctx,"settings.json",mSetStr) |
|||
reply = """{"command":"zeroCount","type":"set","values":"ok"}""" |
|||
} |
|||
"get" -> { |
|||
val t=deflectometer.readZeroCount() |
|||
reply = """{"command":"zeroCount","type":"get","values":${t}}""" |
|||
} |
|||
} |
|||
} |
|||
|
|||
"resultCount" ->{ |
|||
when (command.type) { |
|||
"set" -> { |
|||
val resultCount=command.values.jsonPrimitive.int |
|||
val mSet=deflectometer.updateResultCount(resultCount) |
|||
val mSetStr=json.encodeToString<ManSetting2>(mSet) |
|||
fileUtils.saveFile(ctx,"settings.json",mSetStr) |
|||
reply = """{"command":"resultCount","type":"set","values":"ok"}""" |
|||
} |
|||
"get" -> { |
|||
val t=deflectometer.readResultCount() |
|||
reply = """{"command":"resultCount","type":"get","values":${t}}""" |
|||
} |
|||
} |
|||
} |
|||
|
|||
"dataFps" ->{ |
|||
when (command.type) { |
|||
"set" -> { |
|||
val dataFps=command.values.jsonPrimitive.int |
|||
val mSet=deflectometer.updateDataFps(dataFps) |
|||
val mSetStr=json.encodeToString<ManSetting2>(mSet) |
|||
fileUtils.saveFile(ctx,"settings.json",mSetStr) |
|||
reply = """{"command":"dataFps","type":"set","values":"ok"}""" |
|||
} |
|||
"get" -> { |
|||
val t=deflectometer.readDataFps() |
|||
reply = """{"command":"dataFps","type":"get","values":${t}}""" |
|||
} |
|||
} |
|||
} |
|||
|
|||
"videoFps" ->{ |
|||
when (command.type) { |
|||
"set" -> { |
|||
val videoFps=command.values.jsonPrimitive.int |
|||
val mSet=deflectometer.updateVideoFps(videoFps) |
|||
val mSetStr=json.encodeToString<ManSetting2>(mSet) |
|||
fileUtils.saveFile(ctx,"settings.json",mSetStr) |
|||
reply = """{"command":"videoFps","type":"set","values":"ok"}""" |
|||
} |
|||
"get" -> { |
|||
val t=deflectometer.readVideoFps() |
|||
reply = """{"command":"videoFps","type":"get","values":${t}}""" |
|||
} |
|||
} |
|||
} |
|||
|
|||
"name" ->{ |
|||
when (command.type) { |
|||
"get" -> { |
|||
val ip= getIP4Address() |
|||
reply = """{"command":"name","type":"reply","values":"$ip"}""" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
return reply |
|||
} |
|||
|
|||
|
|||
fun testParseValue(cmdStr:String){ |
|||
val command = json.decodeFromString<Command>(cmdStr) |
|||
|
|||
// 检查values字段的类型并处理 |
|||
when (command.values) { |
|||
is JsonArray -> { |
|||
// 处理数组 |
|||
command.values.jsonArray.forEach { element -> |
|||
val pointsStr=element.toString() |
|||
println(pointsStr) |
|||
} |
|||
} |
|||
is JsonObject -> { |
|||
// 处理对象 |
|||
command.values.jsonObject.keys.forEach { key -> |
|||
println("Key: $key, Value: ${command.values.jsonObject[key]}") |
|||
} |
|||
} |
|||
is JsonNull -> { |
|||
println("null") |
|||
} |
|||
else ->{ |
|||
println("其他类型") |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,43 @@ |
|||
package com.example.myopencv |
|||
|
|||
import android.content.Context |
|||
import android.util.Log |
|||
import java.io.BufferedReader |
|||
import java.io.BufferedWriter |
|||
import java.io.InputStreamReader |
|||
import java.io.OutputStreamWriter |
|||
|
|||
object fileUtils { |
|||
/*保存文件*/ |
|||
fun saveFile(context: Context, fileName: String, cont: String) { |
|||
try { |
|||
//第一个参数是文件名 |
|||
//第二个参数是文件的操作模式(相同文件MODE_PRIVATE覆盖,MODE_APPEND追加内容) |
|||
val output = context.openFileOutput(fileName, Context.MODE_PRIVATE) |
|||
val writer = BufferedWriter(OutputStreamWriter(output)) |
|||
writer.use { |
|||
it.write(cont) |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.e("配置settings.json", "存储异常 ${e.message}") |
|||
} |
|||
} |
|||
|
|||
/*读取文件*/ |
|||
fun readerFile(context: Context, fileName: String): String { |
|||
val content = StringBuffer() |
|||
try { |
|||
val input = context.openFileInput(fileName) |
|||
val reader = BufferedReader(InputStreamReader(input)) |
|||
reader.use { |
|||
reader.forEachLine { |
|||
content.append(it) |
|||
} |
|||
} |
|||
} catch (e: Exception) { |
|||
Log.i("配置settings.json", "读取异常 ${e.message}") |
|||
} |
|||
return content.toString() |
|||
} |
|||
} |
|||
|
@ -0,0 +1,19 @@ |
|||
package com.example.myopencv.models |
|||
|
|||
import kotlinx.serialization.Serializable |
|||
|
|||
|
|||
@Serializable |
|||
data class CentroidPoint( |
|||
val x: Double, |
|||
val y: Double |
|||
) |
|||
|
|||
@Serializable |
|||
data class CentroidData( |
|||
val timestampStr: String, |
|||
val centroids: MutableList<CentroidPoint> |
|||
) |
|||
|
|||
|
|||
|
@ -0,0 +1,15 @@ |
|||
package com.example.myopencv.models |
|||
|
|||
import kotlinx.serialization.* |
|||
import kotlinx.serialization.descriptors.* |
|||
import kotlinx.serialization.encoding.* |
|||
import kotlinx.serialization.json.Json |
|||
import kotlinx.serialization.json.JsonElement |
|||
|
|||
@Serializable |
|||
data class Command( |
|||
val command: String, |
|||
val type: String, |
|||
|
|||
val values: JsonElement= Json.parseToJsonElement("0") |
|||
) |
@ -0,0 +1,39 @@ |
|||
package com.example.myopencv.models |
|||
|
|||
import kotlinx.serialization.Serializable |
|||
|
|||
|
|||
@Serializable |
|||
data class ManSetting ( |
|||
var dataFPS: Int=1, |
|||
var imageSendEnabled: Boolean=false, |
|||
var imageSendTime: Int=1000, |
|||
var invalidDataCount: Int=1, |
|||
var isClearZero: Boolean=false, |
|||
var resultCount: Int=10, |
|||
var sensors: MutableList<Sensor> =mutableListOf(), |
|||
var threshold: Int=128, |
|||
var videoFPS: Int=10, |
|||
var watchValues: String="pos, des, arg, tar, xReal, yReal", |
|||
var zeroCount: Int=30 |
|||
) |
|||
|
|||
@Serializable |
|||
data class ManSetting2 ( |
|||
var baseParam:BaseParam=BaseParam(), |
|||
var sensors: MutableList<Sensor> =mutableListOf() |
|||
) |
|||
|
|||
@Serializable |
|||
data class BaseParam ( |
|||
var dataFPS: Int=1, |
|||
var imageSendEnabled: Boolean=false, |
|||
var imageSendTime: Int=1000, |
|||
var invalidDataCount: Int=1, |
|||
var isClearZero: Boolean=false, |
|||
var resultCount: Int=10, |
|||
var threshold: Int=128, |
|||
var videoFPS: Int=10, |
|||
var zeroCount: Int=1, |
|||
private var watchValues: String="pos, des, arg, tar, xReal, yReal", |
|||
) |
@ -0,0 +1,17 @@ |
|||
package com.example.myopencv.models |
|||
|
|||
import kotlinx.serialization.Serializable |
|||
|
|||
@Serializable |
|||
data class Sensor ( |
|||
var arg: String, |
|||
var des: String, |
|||
var pos: String, |
|||
var tar: String, |
|||
var x: String, |
|||
var y: String, |
|||
var h: String, |
|||
var w: String, |
|||
var xZero: Double = 0.0, |
|||
var yZero: Double = 0.0, |
|||
) |
@ -0,0 +1,19 @@ |
|||
package com.example.myopencv.models |
|||
|
|||
import kotlinx.serialization.Serializable |
|||
|
|||
@Serializable |
|||
data class SensorData( |
|||
var xCur: Double = 0.0, |
|||
var yCur: Double = 0.0, |
|||
var xAvg: Double = 0.0, |
|||
var yAvg: Double = 0.0, |
|||
var xZero: Double = 0.0, |
|||
var yZero: Double = 0.0, |
|||
var xTemp: Double = 0.0, |
|||
var yTemp: Double = 0.0, |
|||
var xReal: Double = Double.NaN, |
|||
var yReal: Double = Double.NaN, |
|||
var sensor: Sensor, |
|||
var invalidCount: Int = 0, |
|||
) |
@ -0,0 +1,40 @@ |
|||
package com.example.myopencv.models |
|||
|
|||
import kotlinx.serialization.Serializable |
|||
|
|||
@Serializable |
|||
data class USensor ( |
|||
var arg: String, |
|||
var des: String, |
|||
var pos: String, |
|||
var tar: String, |
|||
var xReal: Double?, |
|||
var yReal: Double?, |
|||
) |
|||
|
|||
@Serializable |
|||
data class URealValue( |
|||
val sensors: MutableList<USensor>, |
|||
val timestamp: String |
|||
) |
|||
|
|||
@Serializable |
|||
data class UData( |
|||
val command: String, |
|||
val type: String, |
|||
val values: URealValue |
|||
) |
|||
|
|||
|
|||
@Serializable |
|||
data class UImage( |
|||
val command: String, |
|||
val type: String, |
|||
val values: ImageInfo |
|||
) |
|||
|
|||
@Serializable |
|||
data class ImageInfo( |
|||
val image: String, |
|||
val timestamp: String |
|||
) |
@ -0,0 +1,12 @@ |
|||
package com.example.myopencv.models |
|||
|
|||
import kotlinx.serialization.Serializable |
|||
|
|||
@Serializable |
|||
data class UserInfo( |
|||
val id: Int, |
|||
var username: String, |
|||
var email: String, |
|||
var sex: String, |
|||
) |
|||
|
@ -0,0 +1,224 @@ |
|||
package com.example.myopencv |
|||
|
|||
import android.app.Service |
|||
import android.content.Intent |
|||
import android.graphics.Bitmap |
|||
import android.os.Binder |
|||
import android.os.IBinder |
|||
import android.util.Log |
|||
import com.example.myopencv.models.ImageInfo |
|||
import com.example.myopencv.models.UData |
|||
import com.example.myopencv.models.UImage |
|||
import kotlinx.coroutines.CoroutineScope |
|||
import kotlinx.coroutines.Dispatchers |
|||
import kotlinx.coroutines.GlobalScope |
|||
import kotlinx.coroutines.delay |
|||
import kotlinx.coroutines.launch |
|||
import kotlinx.serialization.encodeToString |
|||
import kotlinx.serialization.json.Json |
|||
import java.io.BufferedReader |
|||
import java.io.BufferedWriter |
|||
import java.io.InputStreamReader |
|||
import java.io.OutputStreamWriter |
|||
import java.io.PrintWriter |
|||
import java.net.NetworkInterface |
|||
import java.net.ServerSocket |
|||
import java.net.Socket |
|||
import java.util.regex.Pattern |
|||
import kotlin.math.log |
|||
|
|||
|
|||
class MyTcpServer:Service(){ |
|||
private val coroutineScope = CoroutineScope(Dispatchers.IO) |
|||
// 内部Binder类 |
|||
inner class LocalBinder : Binder() { |
|||
// 返回TcpService的实例 |
|||
fun getService(): MyTcpServer = this@MyTcpServer |
|||
} |
|||
private val binder = LocalBinder() |
|||
override fun onBind(intent: Intent?): IBinder { |
|||
println("==onBind") |
|||
return binder |
|||
} |
|||
override fun onCreate() { |
|||
super.onCreate() |
|||
val ip= getIP4Address() |
|||
println(ip) |
|||
Thread(runnable).start() |
|||
|
|||
dataTask() |
|||
imageTask() |
|||
heartTask() |
|||
} |
|||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
|||
Log.d("data","-> onStartCommand") |
|||
return super.onStartCommand(intent, flags, startId) |
|||
} |
|||
|
|||
override fun onDestroy() { |
|||
super.onDestroy() |
|||
serverAllowed = false |
|||
Log.d("data","-> onDestroy") |
|||
} |
|||
|
|||
private var clientMaps= mutableMapOf<String,Socket>() |
|||
private var serverAllowed=true |
|||
private var runnable= kotlinx.coroutines.Runnable { |
|||
val serverSocket=ServerSocket(9999) |
|||
while (true) { |
|||
val accept: Socket = serverSocket.accept() |
|||
val acceptId=accept.remoteSocketAddress.toString() |
|||
clientMaps[acceptId] = accept |
|||
Thread { response(accept) }.start() |
|||
} |
|||
} |
|||
|
|||
private fun response(accept: Socket) { |
|||
val acceptId=accept.remoteSocketAddress.toString() |
|||
//从客户端接收的信息 |
|||
val bufferedReaderIn: BufferedReader = BufferedReader(InputStreamReader(accept.getInputStream())) |
|||
//发送信息给客户端 |
|||
val out: PrintWriter = PrintWriter(BufferedWriter(OutputStreamWriter(accept.getOutputStream())),true) |
|||
try { |
|||
|
|||
while (serverAllowed){ |
|||
//0xOA换行为一次读取 |
|||
val msg = bufferedReaderIn.readLine() |
|||
Log.i("tcp", "收到客户端[${accept.remoteSocketAddress}]的信息: $msg") |
|||
|
|||
if (msg.length<=1){ |
|||
continue |
|||
} |
|||
val reply = decodeCmd(msg,this) |
|||
if (reply.isNotEmpty()){ |
|||
send2All(reply) |
|||
} |
|||
|
|||
} |
|||
|
|||
}catch (e:Exception){ |
|||
println("异常:${e.message}") |
|||
println("异常-离线:$acceptId") |
|||
}finally { |
|||
println("关闭服务:$acceptId") |
|||
bufferedReaderIn.close() |
|||
out.close() |
|||
accept.close() |
|||
clientMaps.remove(acceptId) |
|||
println("剩余客户端数量:${clientMaps.size}") |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
private fun send2All(msg:String){ |
|||
val nMsg=msg+"\n" |
|||
clientMaps.forEach{ |
|||
try { |
|||
val out: PrintWriter = PrintWriter( |
|||
BufferedWriter(OutputStreamWriter(it.value.getOutputStream())) |
|||
,true) |
|||
out.println(nMsg); |
|||
}catch (e:Exception){ |
|||
Log.i("数据上报","发送异常:${e.message}") |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
private fun sendData2All(r: MResult) { |
|||
//多次发送 更新时间戳 |
|||
r.timestamp=timeNow2string() |
|||
val uploadData = result2uData(r) |
|||
if (uploadData.values.sensors.size==0){ |
|||
return |
|||
} |
|||
val uploadDataJson = Json.encodeToString<UData>(uploadData) |
|||
send2All(uploadDataJson) |
|||
} |
|||
|
|||
private fun sendImage2All(){ |
|||
|
|||
imageBitmapList.forEach { |
|||
val base64=bitmapToBase64(it) |
|||
val imgInfo= ImageInfo(base64,timeNow2string()) |
|||
val uploadImage=UImage("image","reply",imgInfo) |
|||
val imageBase64= Json.encodeToString<UImage>(uploadImage) |
|||
send2All(imageBase64) |
|||
} |
|||
|
|||
} |
|||
//缓存图像 |
|||
private var imageBitmapList= mutableListOf<Bitmap>() |
|||
fun cacheImage(bitmap: Bitmap) { |
|||
if (imageBitmapList.size>0){ |
|||
imageBitmapList[0]=bitmap |
|||
}else{ |
|||
imageBitmapList.add(bitmap) |
|||
} |
|||
} |
|||
|
|||
|
|||
private fun imageTask(){ |
|||
coroutineScope.launch { |
|||
while (true) { |
|||
delay(deflectometer.readImageSendTime().toLong()) |
|||
if(deflectometer.imageSendEnabled) { |
|||
sendImage2All() |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private fun dataTask(){ |
|||
coroutineScope.launch { |
|||
while (true) { |
|||
val interval=deflectometer.readDataFps().toLong() |
|||
delay(1000/interval) |
|||
sendData2All(deflectometer.readResult()) |
|||
} |
|||
} |
|||
} |
|||
|
|||
private fun heartTask(){ |
|||
coroutineScope.launch { |
|||
while (true) { |
|||
delay(10000L) |
|||
val heart="""{"command":"heartbeat","type":"reply","values":0}""" |
|||
send2All(heart) |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
|
|||
fun getIP4Address(): String? { |
|||
try { |
|||
val en = NetworkInterface.getNetworkInterfaces() |
|||
while (en.hasMoreElements()) { |
|||
val intf = en.nextElement() |
|||
val enumIpAddr = intf.inetAddresses |
|||
while (enumIpAddr.hasMoreElements()) { |
|||
val inetAddress = enumIpAddr.nextElement() |
|||
if (!inetAddress.isLoopbackAddress && isIPv4(inetAddress.hostAddress)) { |
|||
return inetAddress.hostAddress |
|||
} |
|||
} |
|||
} |
|||
} catch (ex: java.net.SocketException) { |
|||
ex.printStackTrace() |
|||
} |
|||
return null |
|||
} |
|||
fun isIPv4(ip: String?): Boolean { |
|||
val pattern = Pattern.compile("^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)$") |
|||
return pattern.matcher(ip?:"").matches() |
|||
} |
|||
|
|||
val json = Json { ignoreUnknownKeys = true } // 允许JSON中存在未知键 |
|||
|
|||
|
|||
|
|||
|
@ -0,0 +1,11 @@ |
|||
package com.example.myopencv.ui.theme |
|||
|
|||
import androidx.compose.ui.graphics.Color |
|||
|
|||
val Purple80 = Color(0xFFD0BCFF) |
|||
val PurpleGrey80 = Color(0xFFCCC2DC) |
|||
val Pink80 = Color(0xFFEFB8C8) |
|||
|
|||
val Purple40 = Color(0xFF6650a4) |
|||
val PurpleGrey40 = Color(0xFF625b71) |
|||
val Pink40 = Color(0xFF7D5260) |
@ -0,0 +1,58 @@ |
|||
package com.example.myopencv.ui.theme |
|||
|
|||
import android.app.Activity |
|||
import android.os.Build |
|||
import androidx.compose.foundation.isSystemInDarkTheme |
|||
import androidx.compose.material3.MaterialTheme |
|||
import androidx.compose.material3.darkColorScheme |
|||
import androidx.compose.material3.dynamicDarkColorScheme |
|||
import androidx.compose.material3.dynamicLightColorScheme |
|||
import androidx.compose.material3.lightColorScheme |
|||
import androidx.compose.runtime.Composable |
|||
import androidx.compose.ui.platform.LocalContext |
|||
|
|||
private val DarkColorScheme = darkColorScheme( |
|||
primary = Purple80, |
|||
secondary = PurpleGrey80, |
|||
tertiary = Pink80 |
|||
) |
|||
|
|||
private val LightColorScheme = lightColorScheme( |
|||
primary = Purple40, |
|||
secondary = PurpleGrey40, |
|||
tertiary = Pink40 |
|||
|
|||
/* Other default colors to override |
|||
background = Color(0xFFFFFBFE), |
|||
surface = Color(0xFFFFFBFE), |
|||
onPrimary = Color.White, |
|||
onSecondary = Color.White, |
|||
onTertiary = Color.White, |
|||
onBackground = Color(0xFF1C1B1F), |
|||
onSurface = Color(0xFF1C1B1F), |
|||
*/ |
|||
) |
|||
|
|||
@Composable |
|||
fun MyOpenCvTheme( |
|||
darkTheme: Boolean = isSystemInDarkTheme(), |
|||
// Dynamic color is available on Android 12+ |
|||
dynamicColor: Boolean = true, |
|||
content: @Composable () -> Unit |
|||
) { |
|||
val colorScheme = when { |
|||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { |
|||
val context = LocalContext.current |
|||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) |
|||
} |
|||
|
|||
darkTheme -> DarkColorScheme |
|||
else -> LightColorScheme |
|||
} |
|||
|
|||
MaterialTheme( |
|||
colorScheme = colorScheme, |
|||
typography = Typography, |
|||
content = content |
|||
) |
|||
} |
@ -0,0 +1,34 @@ |
|||
package com.example.myopencv.ui.theme |
|||
|
|||
import androidx.compose.material3.Typography |
|||
import androidx.compose.ui.text.TextStyle |
|||
import androidx.compose.ui.text.font.FontFamily |
|||
import androidx.compose.ui.text.font.FontWeight |
|||
import androidx.compose.ui.unit.sp |
|||
|
|||
// Set of Material typography styles to start with |
|||
val Typography = Typography( |
|||
bodyLarge = TextStyle( |
|||
fontFamily = FontFamily.Default, |
|||
fontWeight = FontWeight.Normal, |
|||
fontSize = 16.sp, |
|||
lineHeight = 24.sp, |
|||
letterSpacing = 0.5.sp |
|||
) |
|||
/* Other default text styles to override |
|||
titleLarge = TextStyle( |
|||
fontFamily = FontFamily.Default, |
|||
fontWeight = FontWeight.Normal, |
|||
fontSize = 22.sp, |
|||
lineHeight = 28.sp, |
|||
letterSpacing = 0.sp |
|||
), |
|||
labelSmall = TextStyle( |
|||
fontFamily = FontFamily.Default, |
|||
fontWeight = FontWeight.Medium, |
|||
fontSize = 11.sp, |
|||
lineHeight = 16.sp, |
|||
letterSpacing = 0.5.sp |
|||
) |
|||
*/ |
|||
) |
@ -0,0 +1,181 @@ |
|||
import android.graphics.Bitmap |
|||
import android.graphics.Color |
|||
import android.graphics.Matrix |
|||
import androidx.camera.core.ImageProxy |
|||
import com.example.myopencv.imageHandler |
|||
import com.example.myopencv.models.CentroidPoint |
|||
import org.opencv.android.Utils |
|||
import org.opencv.core.CvType |
|||
import org.opencv.core.Mat |
|||
import org.opencv.core.Point |
|||
import org.opencv.core.Rect |
|||
import org.opencv.core.Scalar |
|||
import org.opencv.imgproc.Imgproc |
|||
|
|||
class ImageHandler { |
|||
|
|||
|
|||
fun mat2bitMap(mat: Mat): Bitmap { |
|||
val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.RGB_565) |
|||
Utils.matToBitmap(mat, bitmap) |
|||
return bitmap |
|||
} |
|||
|
|||
|
|||
fun mat2gary(mat: Mat): Mat { |
|||
//灰度 |
|||
val garyMat = Mat(mat.width(), mat.height(), CvType.CV_8UC3) |
|||
Imgproc.cvtColor(mat, garyMat, Imgproc.COLOR_RGB2GRAY) |
|||
|
|||
return garyMat |
|||
} |
|||
|
|||
//图片二值化 |
|||
fun binaryMat(thresholdValue: Double, mat: Mat): Mat { |
|||
// 设置二值化的最大值 |
|||
val maxValue = 255.0 |
|||
val binaryMat = Mat(mat.width(), mat.height(), CvType.CV_8UC3) |
|||
// 进行二值化操作 |
|||
Imgproc.threshold(mat, binaryMat, thresholdValue, maxValue, Imgproc.THRESH_BINARY) |
|||
return binaryMat |
|||
} |
|||
|
|||
fun binaryBitMap(thresholdValue: Double, mat: Mat): Bitmap { |
|||
// 设置二值化的最大值 |
|||
val binaryMat = binaryMat(thresholdValue, mat) |
|||
val bitmap = mat2bitMap(binaryMat) |
|||
return bitmap |
|||
} |
|||
|
|||
//图片加文字 |
|||
fun tagText2mat(content: String, mat: Mat): Bitmap { |
|||
val point1 = Point(100.0, 100.0) |
|||
val point2 = Point(300.0, 300.0) |
|||
val color = Scalar(0.0, 0.0, 255.0) |
|||
Imgproc.line(mat, point1, point2, color, 5) |
|||
Imgproc.putText(mat, content, point1, 1, 10.0, color, 5) |
|||
val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.RGB_565) |
|||
Utils.matToBitmap(mat, bitmap) |
|||
return bitmap |
|||
} |
|||
|
|||
//裁剪图片指定区域 |
|||
fun cropMat(mat: Mat,x: Int, y: Int, width: Int, height: Int): Mat { |
|||
val subMat: Mat = Mat(mat, Rect(x, y, width, height)) |
|||
|
|||
return subMat |
|||
} |
|||
|
|||
//裁剪图片指定区域 并生成点位图 |
|||
fun cropMat2bitMap(x: Int, y: Int, width: Int, height: Int, mat: Mat): Bitmap { |
|||
val subMat: Mat = Mat(mat, Rect(x, y, width, height)) |
|||
val bitmap = Bitmap.createBitmap(subMat.width(), subMat.height(), Bitmap.Config.RGB_565) |
|||
Utils.matToBitmap(subMat, bitmap) |
|||
return bitmap |
|||
} |
|||
|
|||
//计算质量心 |
|||
fun calcMoments(binary: Mat): Point { |
|||
var center = Point(0.0, 0.0) |
|||
val moments = Imgproc.moments(binary) |
|||
val area = moments.m00 |
|||
// 如果面积为0,则质心不存在 |
|||
if (area != 0.0) { |
|||
val cx = moments.m10 / area |
|||
val cy = moments.m01 / area |
|||
center = Point(cx.toDouble(), cy.toDouble()) |
|||
|
|||
//println("质心: (${center.x}, ${center.y})") |
|||
|
|||
} else { |
|||
println("质心不存在") |
|||
} |
|||
return center |
|||
} |
|||
|
|||
fun rotateBitmap(original: Bitmap, degrees: Float): Bitmap { |
|||
// 创建一个新的Bitmap对象,具有与原始Bitmap相同的配置 |
|||
val rotated = Bitmap.createBitmap( |
|||
original, 0, 0, original.width, original.height, null, false |
|||
) |
|||
|
|||
// 创建Matrix对象 |
|||
val matrix = Matrix() |
|||
|
|||
// 设置旋转角度和旋转中心 |
|||
matrix.postRotate(degrees, (original.width/2).toFloat(), (original.height/2).toFloat()) |
|||
// 创建一个新的Canvas对象,并使用Matrix进行变换 |
|||
val canvas = android.graphics.Canvas(rotated) |
|||
canvas.drawBitmap(original, matrix, null) |
|||
|
|||
// 返回旋转后的Bitmap |
|||
return rotated |
|||
} |
|||
|
|||
fun img2Moments(image: ImageProxy,threshold: Int,x: Int,y: Int,w: Int,h: Int):CentroidPoint{ |
|||
val bitImg=image.toBitmap() |
|||
val rawMat=Mat(bitImg.width, bitImg.height, CvType.CV_8UC3) |
|||
Utils.bitmapToMat(bitImg,rawMat) |
|||
val subMat= imageHandler.cropMat(rawMat,x,y,w,h) |
|||
//灰度 |
|||
val garyMat =imageHandler.mat2gary(subMat) |
|||
|
|||
//二值化 |
|||
val binaryMat=imageHandler.binaryMat(threshold.toDouble(),garyMat) |
|||
val center=imageHandler.calcMoments(binaryMat) |
|||
return CentroidPoint(center.x,center.y) |
|||
} |
|||
|
|||
fun bitMap2Gray(bitImg: Bitmap):Bitmap{ |
|||
val rawMat=Mat(bitImg.width, bitImg.height, CvType.CV_8UC3) |
|||
Utils.bitmapToMat(bitImg,rawMat) |
|||
//灰度 |
|||
val garyMat =imageHandler.mat2gary(rawMat) |
|||
Utils.matToBitmap(garyMat,bitImg) |
|||
return bitImg |
|||
} |
|||
|
|||
//外包的计算方法 |
|||
fun getBinImageCentroid(image: Bitmap, threshold: Int, viewport: android.graphics.Rect): CentroidPoint { |
|||
val rect = android.graphics.Rect(0, 0, image.width, image.height) |
|||
val left = Math.max(viewport.left, rect.left) |
|||
val right = Math.min(viewport.right, rect.right) |
|||
val top = Math.max(viewport.top, rect.top) |
|||
val bottom = Math.min(viewport.bottom, rect.bottom) |
|||
|
|||
var sumA = 0L |
|||
var sumX = 0L |
|||
var sumY = 0L |
|||
for (y in top until bottom) { |
|||
for (x in left until right) { |
|||
val pixel = image.getPixel(x, y) |
|||
val r = Color.red(pixel) |
|||
val g = Color.green(pixel) |
|||
val b = Color.blue(pixel) |
|||
val gary = qGary(r, g, b) |
|||
if (gary >= threshold) { |
|||
val v=255 |
|||
sumA += v |
|||
sumX += v * (x - left) |
|||
sumY += v * (y - top) |
|||
} |
|||
|
|||
} |
|||
} |
|||
val cp = if (sumA == 0L) |
|||
CentroidPoint(0.0,0.0) |
|||
else { |
|||
CentroidPoint(-1.0 * sumX / sumA, 1.0 * sumY / sumA) |
|||
} |
|||
|
|||
return cp |
|||
} |
|||
|
|||
|
|||
fun qGary(r :Int,g :Int ,b :Int):Int{ |
|||
return (r*11+g*16+b*5)/32 |
|||
} |
|||
} |
|||
|
|||
|
|||
|
@ -0,0 +1,170 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:width="108dp" |
|||
android:height="108dp" |
|||
android:viewportWidth="108" |
|||
android:viewportHeight="108"> |
|||
<path |
|||
android:fillColor="#3DDC84" |
|||
android:pathData="M0,0h108v108h-108z" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M9,0L9,108" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M19,0L19,108" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M29,0L29,108" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M39,0L39,108" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M49,0L49,108" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M59,0L59,108" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M69,0L69,108" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M79,0L79,108" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M89,0L89,108" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M99,0L99,108" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M0,9L108,9" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M0,19L108,19" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M0,29L108,29" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M0,39L108,39" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M0,49L108,49" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M0,59L108,59" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M0,69L108,69" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M0,79L108,79" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M0,89L108,89" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M0,99L108,99" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M19,29L89,29" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M19,39L89,39" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M19,49L89,49" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M19,59L89,59" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M19,69L89,69" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M19,79L89,79" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M29,19L29,89" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M39,19L39,89" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M49,19L49,89" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M59,19L59,89" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M69,19L69,89" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
<path |
|||
android:fillColor="#00000000" |
|||
android:pathData="M79,19L79,89" |
|||
android:strokeWidth="0.8" |
|||
android:strokeColor="#33FFFFFF" /> |
|||
</vector> |
@ -0,0 +1,30 @@ |
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:aapt="http://schemas.android.com/aapt" |
|||
android:width="108dp" |
|||
android:height="108dp" |
|||
android:viewportWidth="108" |
|||
android:viewportHeight="108"> |
|||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> |
|||
<aapt:attr name="android:fillColor"> |
|||
<gradient |
|||
android:endX="85.84757" |
|||
android:endY="92.4963" |
|||
android:startX="42.9492" |
|||
android:startY="49.59793" |
|||
android:type="linear"> |
|||
<item |
|||
android:color="#44000000" |
|||
android:offset="0.0" /> |
|||
<item |
|||
android:color="#00000000" |
|||
android:offset="1.0" /> |
|||
</gradient> |
|||
</aapt:attr> |
|||
</path> |
|||
<path |
|||
android:fillColor="#FFFFFF" |
|||
android:fillType="nonZero" |
|||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" |
|||
android:strokeWidth="1" |
|||
android:strokeColor="#00000000" /> |
|||
</vector> |
After Width: | Height: | Size: 167 KiB |
After Width: | Height: | Size: 326 KiB |
After Width: | Height: | Size: 82 KiB |
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<background android:drawable="@drawable/ic_launcher_background" /> |
|||
<foreground android:drawable="@drawable/ic_launcher_foreground" /> |
|||
<monochrome android:drawable="@drawable/ic_launcher_foreground" /> |
|||
</adaptive-icon> |
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> |
|||
<background android:drawable="@drawable/ic_launcher_background" /> |
|||
<foreground android:drawable="@drawable/ic_launcher_foreground" /> |
|||
<monochrome android:drawable="@drawable/ic_launcher_foreground" /> |
|||
</adaptive-icon> |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 982 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 7.6 KiB |
@ -0,0 +1,10 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<resources> |
|||
<color name="purple_200">#FFBB86FC</color> |
|||
<color name="purple_500">#FF6200EE</color> |
|||
<color name="purple_700">#FF3700B3</color> |
|||
<color name="teal_200">#FF03DAC5</color> |
|||
<color name="teal_700">#FF018786</color> |
|||
<color name="black">#FF000000</color> |
|||
<color name="white">#FFFFFFFF</color> |
|||
</resources> |
@ -0,0 +1,4 @@ |
|||
<resources> |
|||
<string name="app_name">MyOpenCv</string> |
|||
<string name="centroid">raw centroid</string> |
|||
</resources> |
@ -0,0 +1,5 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<resources> |
|||
|
|||
<style name="Theme.MyOpenCv" parent="android:Theme.Material.Light.NoActionBar" /> |
|||
</resources> |
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="utf-8"?><!-- |
|||
Sample backup rules file; uncomment and customize as necessary. |
|||
See https://developer.android.com/guide/topics/data/autobackup |
|||
for details. |
|||
Note: This file is ignored for devices older that API 31 |
|||
See https://developer.android.com/about/versions/12/backup-restore |
|||
--> |
|||
<full-backup-content> |
|||
<!-- |
|||
<include domain="sharedpref" path="."/> |
|||
<exclude domain="sharedpref" path="device.xml"/> |
|||
--> |
|||
</full-backup-content> |
@ -0,0 +1,19 @@ |
|||
<?xml version="1.0" encoding="utf-8"?><!-- |
|||
Sample data extraction rules file; uncomment and customize as necessary. |
|||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes |
|||
for details. |
|||
--> |
|||
<data-extraction-rules> |
|||
<cloud-backup> |
|||
<!-- TODO: Use <include> and <exclude> to control what is backed up. |
|||
<include .../> |
|||
<exclude .../> |
|||
--> |
|||
</cloud-backup> |
|||
<!-- |
|||
<device-transfer> |
|||
<include .../> |
|||
<exclude .../> |
|||
</device-transfer> |
|||
--> |
|||
</data-extraction-rules> |
@ -0,0 +1,122 @@ |
|||
package com.example.myopencv |
|||
|
|||
|
|||
import androidx.test.core.app.ActivityScenario.launch |
|||
import com.example.myopencv.models.CentroidData |
|||
import com.example.myopencv.models.CentroidPoint |
|||
import com.example.myopencv.models.Command |
|||
import com.example.myopencv.models.ManSetting |
|||
import com.example.myopencv.models.Sensor |
|||
import com.example.myopencv.models.SensorData |
|||
import kotlinx.coroutines.* |
|||
import kotlinx.serialization.json.Json |
|||
import kotlinx.serialization.json.JsonArray |
|||
import kotlinx.serialization.json.JsonNull |
|||
import kotlinx.serialization.json.JsonObject |
|||
import kotlinx.serialization.json.JsonPrimitive |
|||
import kotlinx.serialization.json.jsonArray |
|||
import kotlinx.serialization.json.jsonObject |
|||
import org.junit.Test |
|||
import org.junit.Assert.* |
|||
|
|||
/** |
|||
* Example local unit test, which will execute on the development machine (host). |
|||
* |
|||
* See [testing documentation](http://d.android.com/tools/testing). |
|||
*/ |
|||
class ExampleUnitTest { |
|||
@Test |
|||
fun addition_isCorrect() { |
|||
assertEquals(4, 2 + 2) |
|||
} |
|||
//@Test |
|||
// fun calc_CentroidPoint() { |
|||
// val cp2 = CentroidPoint(-284.11025502125176, 210.8166722226852) |
|||
// val centroidDataArray2 = CentroidData("", mutableListOf(cp2)) |
|||
// val deflectometer = Deflectometer() |
|||
// deflectometer.updateSensor( mutableListOf( |
|||
// Sensor( |
|||
// arg = "0.448", |
|||
// des = "", |
|||
// pos = "1", |
|||
// tar = "n", |
|||
// x = "97", |
|||
// y = "1054", |
|||
// h = "425", |
|||
// w = "569", |
|||
// xZero = 0.0, |
|||
// yZero = 0.0 |
|||
// ) |
|||
// ) |
|||
// ) |
|||
// deflectometer.readSensor().forEach { |
|||
// val sd = SensorData(sensor = it) |
|||
// deflectometer.m_result.sensorsData.add(sd) |
|||
// } |
|||
// |
|||
// //设置上次数据的avg |
|||
// deflectometer.m_result.sensorsData[0].xAvg=-284.03910070884103 |
|||
// deflectometer.m_result.sensorsData[0].yAvg= 210.88589473100282 |
|||
// |
|||
// val r2 = deflectometer.dataProcess(centroidDataArray2) |
|||
// assertEquals(r2.sensorsData[0].yReal,94.4458,0.0001) |
|||
// assertEquals(r2.sensorsData[0].xReal,-127.2813,0.0001) |
|||
// } |
|||
|
|||
@Test |
|||
fun parseCommand(){ |
|||
|
|||
val json = Json { ignoreUnknownKeys = true } // 允许JSON中存在未知键 |
|||
//报文处理 |
|||
|
|||
val payLoad="""{"command":"sensors","type":"set","values":[{"arg":"0.448","des":"","h":"425","pos":"1","tar":"n","w":"569","x":"97","y":"1054"}]} |
|||
""" |
|||
//val payLoad2="""{"command":"sensors","type":"set","values":0} """ |
|||
// decodeCmd(payLoad) |
|||
} |
|||
|
|||
@Test |
|||
fun testLaunch(){ |
|||
println("开始") |
|||
val r= runAsync() |
|||
println("结束") |
|||
Thread.sleep(2000) // 让当前线程暂停2秒 |
|||
} |
|||
|
|||
private fun run() { |
|||
val result= runBlocking { |
|||
val lr=launch { |
|||
doWorld() |
|||
println("Hello") |
|||
"123" |
|||
} |
|||
println("lr=${lr}") |
|||
"234" |
|||
} |
|||
println("r=${result}") |
|||
} |
|||
|
|||
private fun runAsync() { |
|||
val result=runBlocking { |
|||
val lr=async { |
|||
doWorld() |
|||
"123" |
|||
} |
|||
delay(1100L) |
|||
println("Hello") |
|||
println("lr=${lr.await()}") |
|||
"234" |
|||
} |
|||
|
|||
println("r=${result}") |
|||
} |
|||
|
|||
private suspend fun doWorld(){ |
|||
delay(1000L) |
|||
println("world") |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
|
@ -0,0 +1,5 @@ |
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules. |
|||
plugins { |
|||
alias(libs.plugins.android.application) apply false |
|||
alias(libs.plugins.kotlin.android) apply false |
|||
} |
@ -0,0 +1,23 @@ |
|||
# Project-wide Gradle settings. |
|||
# IDE (e.g. Android Studio) users: |
|||
# Gradle settings configured through the IDE *will override* |
|||
# any settings specified in this file. |
|||
# For more details on how to configure your build environment visit |
|||
# http://www.gradle.org/docs/current/userguide/build_environment.html |
|||
# Specifies the JVM arguments used for the daemon process. |
|||
# The setting is particularly useful for tweaking memory settings. |
|||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 |
|||
# When configured, Gradle will run in incubating parallel mode. |
|||
# This option should only be used with decoupled projects. For more details, visit |
|||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects |
|||
# org.gradle.parallel=true |
|||
# AndroidX package structure to make it clearer which packages are bundled with the |
|||
# Android operating system, and which are packaged with your app's APK |
|||
# https://developer.android.com/topic/libraries/support-library/androidx-rn |
|||
android.useAndroidX=true |
|||
# Kotlin code style for this project: "official" or "obsolete": |
|||
kotlin.code.style=official |
|||
# Enables namespacing of each library's R class so that its R class includes only the |
|||
# resources declared in the library itself and none from the library's dependencies, |
|||
# thereby reducing the size of the R class for that library |
|||
android.nonTransitiveRClass=true |
@ -0,0 +1,40 @@ |
|||
[versions] |
|||
agp = "8.6.0" |
|||
cameraCoreVersion = "1.3.1" |
|||
kotlin = "1.9.0" |
|||
coreKtx = "1.10.1" |
|||
junit = "4.13.2" |
|||
junitVersion = "1.1.5" |
|||
espressoCore = "3.5.1" |
|||
kotlinxSerializationJson = "1.6.3" |
|||
lifecycleRuntimeKtx = "2.6.1" |
|||
activityCompose = "1.8.0" |
|||
composeBom = "2024.04.01" |
|||
coreKtxVersion = "1.6.1" |
|||
|
|||
[libraries] |
|||
androidx-camera-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCoreVersion" } |
|||
androidx-camera-camera-camera22 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCoreVersion" } |
|||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } |
|||
camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraCoreVersion" } |
|||
camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraCoreVersion" } |
|||
junit = { group = "junit", name = "junit", version.ref = "junit" } |
|||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } |
|||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } |
|||
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } |
|||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } |
|||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } |
|||
androidx-ui = { group = "androidx.compose.ui", name = "ui" } |
|||
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } |
|||
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } |
|||
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } |
|||
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } |
|||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } |
|||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" } |
|||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } |
|||
core-ktx = { group = "androidx.test", name = "core-ktx", version.ref = "coreKtxVersion" } |
|||
|
|||
[plugins] |
|||
android-application = { id = "com.android.application", version.ref = "agp" } |
|||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } |
|||
|
@ -0,0 +1,6 @@ |
|||
#Sat Nov 16 20:08:14 CST 2024 |
|||
distributionBase=GRADLE_USER_HOME |
|||
distributionPath=wrapper/dists |
|||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip |
|||
zipStoreBase=GRADLE_USER_HOME |
|||
zipStorePath=wrapper/dists |
@ -0,0 +1,185 @@ |
|||
#!/usr/bin/env sh |
|||
|
|||
# |
|||
# Copyright 2015 the original author or authors. |
|||
# |
|||
# Licensed under the Apache License, Version 2.0 (the "License"); |
|||
# you may not use this file except in compliance with the License. |
|||
# You may obtain a copy of the License at |
|||
# |
|||
# https://www.apache.org/licenses/LICENSE-2.0 |
|||
# |
|||
# Unless required by applicable law or agreed to in writing, software |
|||
# distributed under the License is distributed on an "AS IS" BASIS, |
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
# See the License for the specific language governing permissions and |
|||
# limitations under the License. |
|||
# |
|||
|
|||
############################################################################## |
|||
## |
|||
## Gradle start up script for UN*X |
|||
## |
|||
############################################################################## |
|||
|
|||
# Attempt to set APP_HOME |
|||
# Resolve links: $0 may be a link |
|||
PRG="$0" |
|||
# Need this for relative symlinks. |
|||
while [ -h "$PRG" ] ; do |
|||
ls=`ls -ld "$PRG"` |
|||
link=`expr "$ls" : '.*-> \(.*\)$'` |
|||
if expr "$link" : '/.*' > /dev/null; then |
|||
PRG="$link" |
|||
else |
|||
PRG=`dirname "$PRG"`"/$link" |
|||
fi |
|||
done |
|||
SAVED="`pwd`" |
|||
cd "`dirname \"$PRG\"`/" >/dev/null |
|||
APP_HOME="`pwd -P`" |
|||
cd "$SAVED" >/dev/null |
|||
|
|||
APP_NAME="Gradle" |
|||
APP_BASE_NAME=`basename "$0"` |
|||
|
|||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
|||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' |
|||
|
|||
# Use the maximum available, or set MAX_FD != -1 to use that value. |
|||
MAX_FD="maximum" |
|||
|
|||
warn () { |
|||
echo "$*" |
|||
} |
|||
|
|||
die () { |
|||
echo |
|||
echo "$*" |
|||
echo |
|||
exit 1 |
|||
} |
|||
|
|||
# OS specific support (must be 'true' or 'false'). |
|||
cygwin=false |
|||
msys=false |
|||
darwin=false |
|||
nonstop=false |
|||
case "`uname`" in |
|||
CYGWIN* ) |
|||
cygwin=true |
|||
;; |
|||
Darwin* ) |
|||
darwin=true |
|||
;; |
|||
MINGW* ) |
|||
msys=true |
|||
;; |
|||
NONSTOP* ) |
|||
nonstop=true |
|||
;; |
|||
esac |
|||
|
|||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar |
|||
|
|||
|
|||
# Determine the Java command to use to start the JVM. |
|||
if [ -n "$JAVA_HOME" ] ; then |
|||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
|||
# IBM's JDK on AIX uses strange locations for the executables |
|||
JAVACMD="$JAVA_HOME/jre/sh/java" |
|||
else |
|||
JAVACMD="$JAVA_HOME/bin/java" |
|||
fi |
|||
if [ ! -x "$JAVACMD" ] ; then |
|||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME |
|||
|
|||
Please set the JAVA_HOME variable in your environment to match the |
|||
location of your Java installation." |
|||
fi |
|||
else |
|||
JAVACMD="java" |
|||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
|||
|
|||
Please set the JAVA_HOME variable in your environment to match the |
|||
location of your Java installation." |
|||
fi |
|||
|
|||
# Increase the maximum file descriptors if we can. |
|||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then |
|||
MAX_FD_LIMIT=`ulimit -H -n` |
|||
if [ $? -eq 0 ] ; then |
|||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then |
|||
MAX_FD="$MAX_FD_LIMIT" |
|||
fi |
|||
ulimit -n $MAX_FD |
|||
if [ $? -ne 0 ] ; then |
|||
warn "Could not set maximum file descriptor limit: $MAX_FD" |
|||
fi |
|||
else |
|||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" |
|||
fi |
|||
fi |
|||
|
|||
# For Darwin, add options to specify how the application appears in the dock |
|||
if $darwin; then |
|||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" |
|||
fi |
|||
|
|||
# For Cygwin or MSYS, switch paths to Windows format before running java |
|||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then |
|||
APP_HOME=`cygpath --path --mixed "$APP_HOME"` |
|||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` |
|||
|
|||
JAVACMD=`cygpath --unix "$JAVACMD"` |
|||
|
|||
# We build the pattern for arguments to be converted via cygpath |
|||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |
|||
SEP="" |
|||
for dir in $ROOTDIRSRAW ; do |
|||
ROOTDIRS="$ROOTDIRS$SEP$dir" |
|||
SEP="|" |
|||
done |
|||
OURCYGPATTERN="(^($ROOTDIRS))" |
|||
# Add a user-defined pattern to the cygpath arguments |
|||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then |
|||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |
|||
fi |
|||
# Now convert the arguments - kludge to limit ourselves to /bin/sh |
|||
i=0 |
|||
for arg in "$@" ; do |
|||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |
|||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option |
|||
|
|||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition |
|||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |
|||
else |
|||
eval `echo args$i`="\"$arg\"" |
|||
fi |
|||
i=`expr $i + 1` |
|||
done |
|||
case $i in |
|||
0) set -- ;; |
|||
1) set -- "$args0" ;; |
|||
2) set -- "$args0" "$args1" ;; |
|||
3) set -- "$args0" "$args1" "$args2" ;; |
|||
4) set -- "$args0" "$args1" "$args2" "$args3" ;; |
|||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |
|||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |
|||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |
|||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |
|||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |
|||
esac |
|||
fi |
|||
|
|||
# Escape application args |
|||
save () { |
|||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done |
|||
echo " " |
|||
} |
|||
APP_ARGS=`save "$@"` |
|||
|
|||
# Collect all arguments for the java command, following the shell quoting and substitution rules |
|||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" |
|||
|
|||
exec "$JAVACMD" "$@" |
@ -0,0 +1,89 @@ |
|||
@rem |
|||
@rem Copyright 2015 the original author or authors. |
|||
@rem |
|||
@rem Licensed under the Apache License, Version 2.0 (the "License"); |
|||
@rem you may not use this file except in compliance with the License. |
|||
@rem You may obtain a copy of the License at |
|||
@rem |
|||
@rem https://www.apache.org/licenses/LICENSE-2.0 |
|||
@rem |
|||
@rem Unless required by applicable law or agreed to in writing, software |
|||
@rem distributed under the License is distributed on an "AS IS" BASIS, |
|||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
@rem See the License for the specific language governing permissions and |
|||
@rem limitations under the License. |
|||
@rem |
|||
|
|||
@if "%DEBUG%" == "" @echo off |
|||
@rem ########################################################################## |
|||
@rem |
|||
@rem Gradle startup script for Windows |
|||
@rem |
|||
@rem ########################################################################## |
|||
|
|||
@rem Set local scope for the variables with windows NT shell |
|||
if "%OS%"=="Windows_NT" setlocal |
|||
|
|||
set DIRNAME=%~dp0 |
|||
if "%DIRNAME%" == "" set DIRNAME=. |
|||
set APP_BASE_NAME=%~n0 |
|||
set APP_HOME=%DIRNAME% |
|||
|
|||
@rem Resolve any "." and ".." in APP_HOME to make it shorter. |
|||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi |
|||
|
|||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
|||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" |
|||
|
|||
@rem Find java.exe |
|||
if defined JAVA_HOME goto findJavaFromJavaHome |
|||
|
|||
set JAVA_EXE=java.exe |
|||
%JAVA_EXE% -version >NUL 2>&1 |
|||
if "%ERRORLEVEL%" == "0" goto execute |
|||
|
|||
echo. |
|||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
|||
echo. |
|||
echo Please set the JAVA_HOME variable in your environment to match the |
|||
echo location of your Java installation. |
|||
|
|||
goto fail |
|||
|
|||
:findJavaFromJavaHome |
|||
set JAVA_HOME=%JAVA_HOME:"=% |
|||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe |
|||
|
|||
if exist "%JAVA_EXE%" goto execute |
|||
|
|||
echo. |
|||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% |
|||
echo. |
|||
echo Please set the JAVA_HOME variable in your environment to match the |
|||
echo location of your Java installation. |
|||
|
|||
goto fail |
|||
|
|||
:execute |
|||
@rem Setup the command line |
|||
|
|||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar |
|||
|
|||
|
|||
@rem Execute Gradle |
|||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* |
|||
|
|||
:end |
|||
@rem End local scope for the variables with windows NT shell |
|||
if "%ERRORLEVEL%"=="0" goto mainEnd |
|||
|
|||
:fail |
|||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of |
|||
rem the _cmd.exe /c_ return code! |
|||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 |
|||
exit /b 1 |
|||
|
|||
:mainEnd |
|||
if "%OS%"=="Windows_NT" endlocal |
|||
|
|||
:omega |
@ -0,0 +1,193 @@ |
|||
// This file is part of OpenCV project. |
|||
// It is subject to the license terms in the LICENSE file found in the top-level directory |
|||
// of this distribution and at http://opencv.org/license.html. |
|||
|
|||
// |
|||
// Notes about integration OpenCV into existed Android Studio application project are below (application 'app' module should exist). |
|||
// |
|||
// This file is located in <OpenCV-android-sdk>/sdk directory (near 'etc', 'java', 'native' subdirectories) |
|||
// |
|||
// Add module into Android Studio application project: |
|||
// |
|||
// - Android Studio way: |
|||
// (will copy almost all OpenCV Android SDK into your project, ~200Mb) |
|||
// |
|||
// Import module: Menu -> "File" -> "New" -> "Module" -> "Import Gradle project": |
|||
// Source directory: select this "sdk" directory |
|||
// Module name: ":opencv" |
|||
// |
|||
// - or attach library module from OpenCV Android SDK |
|||
// (without copying into application project directory, allow to share the same module between projects) |
|||
// |
|||
// Edit "settings.gradle" and add these lines: |
|||
// |
|||
// def opencvsdk='<path_to_opencv_android_sdk_rootdir>' |
|||
// // You can put declaration above into gradle.properties file instead (including file in HOME directory), |
|||
// // but without 'def' and apostrophe symbols ('): opencvsdk=<path_to_opencv_android_sdk_rootdir> |
|||
// include ':opencv' |
|||
// project(':opencv').projectDir = new File(opencvsdk + '/sdk') |
|||
// |
|||
// |
|||
// |
|||
// Add dependency into application module: |
|||
// |
|||
// - Android Studio way: |
|||
// "Open Module Settings" (F4) -> "Dependencies" tab |
|||
// |
|||
// - or add "project(':opencv')" dependency into app/build.gradle: |
|||
// |
|||
// dependencies { |
|||
// implementation fileTree(dir: 'libs', include: ['*.jar']) |
|||
// ... |
|||
// implementation project(':opencv') |
|||
// } |
|||
// |
|||
// |
|||
// |
|||
// Load OpenCV native library before using: |
|||
// |
|||
// - avoid using of "OpenCVLoader.initAsync()" approach - it is deprecated |
|||
// It may load library with different version (from OpenCV Android Manager, which is installed separatelly on device) |
|||
// |
|||
// - use "System.loadLibrary("opencv_java4")" or "OpenCVLoader.initDebug()" |
|||
// TODO: Add accurate API to load OpenCV native library |
|||
// |
|||
// |
|||
// |
|||
// Native C++ support (necessary to use OpenCV in native code of application only): |
|||
// |
|||
// - Use find_package() in app/CMakeLists.txt: |
|||
// |
|||
// find_package(OpenCV 4.10 REQUIRED java) |
|||
// ... |
|||
// target_link_libraries(native-lib ${OpenCV_LIBRARIES}) |
|||
// |
|||
// - Add "OpenCV_DIR" and enable C++ exceptions/RTTI support via app/build.gradle |
|||
// Documentation about CMake options: https://developer.android.com/ndk/guides/cmake.html |
|||
// |
|||
// defaultConfig { |
|||
// ... |
|||
// externalNativeBuild { |
|||
// cmake { |
|||
// cppFlags "-std=c++11 -frtti -fexceptions" |
|||
// arguments "-DOpenCV_DIR=" + opencvsdk + "/sdk/native/jni" // , "-DANDROID_ARM_NEON=TRUE" |
|||
// } |
|||
// } |
|||
// } |
|||
// |
|||
// - (optional) Limit/filter ABIs to build ('android' scope of 'app/build.gradle'): |
|||
// Useful information: https://developer.android.com/studio/build/gradle-tips.html (Configure separate APKs per ABI) |
|||
// |
|||
// splits { |
|||
// abi { |
|||
// enable true |
|||
// universalApk false |
|||
// reset() |
|||
// include 'armeabi-v7a' // , 'x86', 'x86_64', 'arm64-v8a' |
|||
// } |
|||
// } |
|||
// |
|||
|
|||
apply plugin: 'com.android.library' |
|||
apply plugin: 'maven-publish' |
|||
apply plugin: 'kotlin-android' |
|||
|
|||
def openCVersionName = "4.10.0" |
|||
def openCVersionCode = ((4 * 100 + 10) * 100 + 0) * 10 + 0 |
|||
|
|||
println "OpenCV: " +openCVersionName + " " + project.buildscript.sourceFile |
|||
|
|||
android { |
|||
namespace 'org.opencv' |
|||
compileSdkVersion 31 |
|||
|
|||
defaultConfig { |
|||
minSdkVersion 21 |
|||
targetSdkVersion 31 |
|||
|
|||
versionCode openCVersionCode |
|||
versionName openCVersionName |
|||
|
|||
externalNativeBuild { |
|||
cmake { |
|||
arguments "-DANDROID_STL=c++_shared" |
|||
targets "opencv_jni_shared" |
|||
} |
|||
} |
|||
} |
|||
|
|||
compileOptions { |
|||
sourceCompatibility JavaVersion.VERSION_17 |
|||
targetCompatibility JavaVersion.VERSION_17 |
|||
} |
|||
|
|||
buildTypes { |
|||
debug { |
|||
packagingOptions { |
|||
doNotStrip '**/*.so' // controlled by OpenCV CMake scripts |
|||
} |
|||
} |
|||
release { |
|||
packagingOptions { |
|||
doNotStrip '**/*.so' // controlled by OpenCV CMake scripts |
|||
} |
|||
minifyEnabled false |
|||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' |
|||
} |
|||
} |
|||
|
|||
buildFeatures { |
|||
prefabPublishing true |
|||
buildConfig true |
|||
} |
|||
prefab { |
|||
opencv_jni_shared { |
|||
headers "native/jni/include" |
|||
} |
|||
} |
|||
|
|||
sourceSets { |
|||
main { |
|||
jniLibs.srcDirs = ['native/libs'] |
|||
java.srcDirs = ['java/src'] |
|||
res.srcDirs = ['java/res'] |
|||
manifest.srcFile 'java/AndroidManifest.xml' |
|||
} |
|||
} |
|||
|
|||
publishing { |
|||
singleVariant('release') { |
|||
withSourcesJar() |
|||
withJavadocJar() |
|||
} |
|||
} |
|||
|
|||
externalNativeBuild { |
|||
cmake { |
|||
path (project.projectDir.toString() + '/libcxx_helper/CMakeLists.txt') |
|||
} |
|||
} |
|||
} |
|||
|
|||
publishing { |
|||
publications { |
|||
release(MavenPublication) { |
|||
groupId = 'org.opencv' |
|||
artifactId = 'opencv' |
|||
version = '4.10.0' |
|||
|
|||
afterEvaluate { |
|||
from components.release |
|||
} |
|||
} |
|||
} |
|||
repositories { |
|||
maven { |
|||
name = 'myrepo' |
|||
url = "${project.buildDir}/repo" |
|||
} |
|||
} |
|||
} |
|||
|
|||
dependencies { |
|||
} |
@ -0,0 +1,24 @@ |
|||
pluginManagement { |
|||
repositories { |
|||
google { |
|||
content { |
|||
includeGroupByRegex("com\\.android.*") |
|||
includeGroupByRegex("com\\.google.*") |
|||
includeGroupByRegex("androidx.*") |
|||
} |
|||
} |
|||
mavenCentral() |
|||
gradlePluginPortal() |
|||
} |
|||
} |
|||
dependencyResolutionManagement { |
|||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) |
|||
repositories { |
|||
google() |
|||
mavenCentral() |
|||
} |
|||
} |
|||
|
|||
rootProject.name = "MyOpenCv" |
|||
include(":app") |
|||
include(":opencv") |