diff --git a/MyOpenCv/.gitignore b/MyOpenCv/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/MyOpenCv/.gitignore @@ -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 diff --git a/MyOpenCv/app/.gitignore b/MyOpenCv/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/MyOpenCv/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/MyOpenCv/app/build.gradle.kts b/MyOpenCv/app/build.gradle.kts new file mode 100644 index 0000000..1484ed2 --- /dev/null +++ b/MyOpenCv/app/build.gradle.kts @@ -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) +} \ No newline at end of file diff --git a/MyOpenCv/app/proguard-rules.pro b/MyOpenCv/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/MyOpenCv/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/MyOpenCv/app/src/androidTest/java/com/example/myopencv/ExampleInstrumentedTest.kt b/MyOpenCv/app/src/androidTest/java/com/example/myopencv/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..2a3bcd9 --- /dev/null +++ b/MyOpenCv/app/src/androidTest/java/com/example/myopencv/ExampleInstrumentedTest.kt @@ -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) + } +} \ No newline at end of file diff --git a/MyOpenCv/app/src/main/AndroidManifest.xml b/MyOpenCv/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..641839a --- /dev/null +++ b/MyOpenCv/app/src/main/AndroidManifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/MainActivity.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/MainActivity.kt new file mode 100644 index 0000000..f354bab --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/MainActivity.kt @@ -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") + } +} diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/UdpServer.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/UdpServer.kt new file mode 100644 index 0000000..0c29fb8 --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/UdpServer.kt @@ -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("发送完毕") + } + +} + + diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/camera.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/camera.kt new file mode 100644 index 0000000..9bab8a7 --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/camera.kt @@ -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(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 + diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/configLoad.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/configLoad.kt new file mode 100644 index 0000000..effd39a --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/configLoad.kt @@ -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(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(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() + } +} diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/dataProcess.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/dataProcess.kt new file mode 100644 index 0000000..1b54caf --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/dataProcess.kt @@ -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, + 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(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{ + return m_settings.sensors + } + fun updateSensor(sensorList: MutableList):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 + } + +} \ No newline at end of file diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/dataTrans.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/dataTrans.kt new file mode 100644 index 0000000..654963a --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/dataTrans.kt @@ -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(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>(sensorSetStr) + val mSet = deflectometer.updateSensor(sensors) + val mSetStr=json.encodeToString(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(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(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(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(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(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(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(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("其他类型") + } + } +} \ No newline at end of file diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/fileUtils.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/fileUtils.kt new file mode 100644 index 0000000..7ecf020 --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/fileUtils.kt @@ -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() + } +} + diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/models/centroidData.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/models/centroidData.kt new file mode 100644 index 0000000..4969c5f --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/models/centroidData.kt @@ -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 +) + + + diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/models/command.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/models/command.kt new file mode 100644 index 0000000..7b159e8 --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/models/command.kt @@ -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") +) diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/models/mSetting.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/models/mSetting.kt new file mode 100644 index 0000000..cac6be3 --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/models/mSetting.kt @@ -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 =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 =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", +) diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/models/sensor.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/models/sensor.kt new file mode 100644 index 0000000..960bbd0 --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/models/sensor.kt @@ -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, +) \ No newline at end of file diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/models/sensorData.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/models/sensorData.kt new file mode 100644 index 0000000..2e38aaa --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/models/sensorData.kt @@ -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, +) \ No newline at end of file diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/models/uploadData.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/models/uploadData.kt new file mode 100644 index 0000000..229bd25 --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/models/uploadData.kt @@ -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, + 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 +) diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/models/userInfo.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/models/userInfo.kt new file mode 100644 index 0000000..448a5c7 --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/models/userInfo.kt @@ -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, +) + diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/tcpServer.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/tcpServer.kt new file mode 100644 index 0000000..c6b460c --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/tcpServer.kt @@ -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() + 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(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(uploadImage) + send2All(imageBase64) + } + + } + //缓存图像 + private var imageBitmapList= mutableListOf() + 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中存在未知键 + + + + diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/ui/theme/Color.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/ui/theme/Color.kt new file mode 100644 index 0000000..a2bf41e --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/ui/theme/Color.kt @@ -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) \ No newline at end of file diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/ui/theme/Theme.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/ui/theme/Theme.kt new file mode 100644 index 0000000..c434a90 --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/ui/theme/Theme.kt @@ -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 + ) +} \ No newline at end of file diff --git a/MyOpenCv/app/src/main/java/com/example/myopencv/ui/theme/Type.kt b/MyOpenCv/app/src/main/java/com/example/myopencv/ui/theme/Type.kt new file mode 100644 index 0000000..2119875 --- /dev/null +++ b/MyOpenCv/app/src/main/java/com/example/myopencv/ui/theme/Type.kt @@ -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 + ) + */ +) \ No newline at end of file diff --git a/MyOpenCv/app/src/main/java/imageHandler.kt b/MyOpenCv/app/src/main/java/imageHandler.kt new file mode 100644 index 0000000..c33afc0 --- /dev/null +++ b/MyOpenCv/app/src/main/java/imageHandler.kt @@ -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 + } +} + + + diff --git a/MyOpenCv/app/src/main/res/drawable/ic_launcher_background.xml b/MyOpenCv/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/MyOpenCv/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MyOpenCv/app/src/main/res/drawable/ic_launcher_foreground.xml b/MyOpenCv/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/MyOpenCv/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/MyOpenCv/app/src/main/res/drawable/xml_4.jpg b/MyOpenCv/app/src/main/res/drawable/xml_4.jpg new file mode 100644 index 0000000..0b85fc8 Binary files /dev/null and b/MyOpenCv/app/src/main/res/drawable/xml_4.jpg differ diff --git a/MyOpenCv/app/src/main/res/drawable/yu_5.jpg b/MyOpenCv/app/src/main/res/drawable/yu_5.jpg new file mode 100644 index 0000000..aea42a0 Binary files /dev/null and b/MyOpenCv/app/src/main/res/drawable/yu_5.jpg differ diff --git a/MyOpenCv/app/src/main/res/drawable/zp_1.png b/MyOpenCv/app/src/main/res/drawable/zp_1.png new file mode 100644 index 0000000..e06cdb3 Binary files /dev/null and b/MyOpenCv/app/src/main/res/drawable/zp_1.png differ diff --git a/MyOpenCv/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/MyOpenCv/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/MyOpenCv/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/MyOpenCv/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/MyOpenCv/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/MyOpenCv/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/MyOpenCv/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/MyOpenCv/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/MyOpenCv/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/MyOpenCv/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/MyOpenCv/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/MyOpenCv/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/MyOpenCv/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/MyOpenCv/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/MyOpenCv/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/MyOpenCv/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/MyOpenCv/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/MyOpenCv/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/MyOpenCv/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/MyOpenCv/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/MyOpenCv/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/MyOpenCv/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/MyOpenCv/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/MyOpenCv/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/MyOpenCv/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/MyOpenCv/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/MyOpenCv/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/MyOpenCv/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/MyOpenCv/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/MyOpenCv/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/MyOpenCv/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/MyOpenCv/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/MyOpenCv/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/MyOpenCv/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/MyOpenCv/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/MyOpenCv/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/MyOpenCv/app/src/main/res/values/colors.xml b/MyOpenCv/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/MyOpenCv/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/MyOpenCv/app/src/main/res/values/strings.xml b/MyOpenCv/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..8643136 --- /dev/null +++ b/MyOpenCv/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + MyOpenCv + raw centroid + \ No newline at end of file diff --git a/MyOpenCv/app/src/main/res/values/themes.xml b/MyOpenCv/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..449b754 --- /dev/null +++ b/MyOpenCv/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +