@ -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") |