Я строю кроппер изображений в Android с помощью opencv. Приложение захватывает изображение с намерения камеры и используется для кадрирования активности с заранее определенными границами, основанными на логотипах на изображении. Проблема в том, что я не могу отображать заранее определенные границы с точностью до изображения.
Необходим масштабный коэффициент, который будет использоваться для преобразования координат мата opencv в координаты макета для точного отображения границ.
Фактические результаты -> https://ibb.co/5WDxCqR
Ожидаемые результаты
Зеленый прямоугольник границы должен появиться на логотипе "Энергия"
Аналогичный вопрос найден наopencv, но решение не предоставляется (https://answers.opencv.org/question/186911/how-to-convert-mat-coordinates-to-layout-coordinates-in-android/)
package com.example.ccsim.view
import android.app.Activity
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.util.Log
import android.view.MotionEvent
import android.widget.FrameLayout
import com.example.ccsim.processor.Corners
import com.example.ccsim.processor.SourceManager
import org.opencv.core.Point
import org.opencv.core.Size
class PaperRectangle : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attributes: AttributeSet) : super(context, attributes)
constructor(context: Context, attributes: AttributeSet, defTheme: Int) : super(context, attributes, defTheme)
private val rectPaint = Paint()
private val circlePaint = Paint()
private var ratioX: Double = 1.0
private var ratioY: Double = 1.0
private var xOffset: Double = 1.0
private var yOffset: Double = 1.0
private var ratio: Double = 1.0
private var tl: Point = Point()
private var tr: Point = Point()
private var br: Point = Point()
private var bl: Point = Point()
private val path: Path = Path()
private var point2Move = Point()
private var cropMode = false
private var latestDownX = 0.0F
private var latestDownY = 0.0F
init {
rectPaint.color = Color.GREEN
rectPaint.isAntiAlias = true
rectPaint.isDither = true
rectPaint.strokeWidth = 6F
rectPaint.style = Paint.Style.STROKE
rectPaint.strokeJoin = Paint.Join.ROUND // set the join to round you want
rectPaint.strokeCap = Paint.Cap.ROUND // set the paint cap to round too
rectPaint.pathEffect = CornerPathEffect(10f)
circlePaint.color = Color.RED
circlePaint.isDither = true
circlePaint.isAntiAlias = true
circlePaint.strokeWidth = 4F
circlePaint.style = Paint.Style.STROKE
}
fun onCorners2Crop(
corners: Corners?,
size: Size?
) {
cropMode = true
tl = corners?.corners?.get(0) ?: SourceManager.defaultTl
tr = corners?.corners?.get(1) ?: SourceManager.defaultTr
br = corners?.corners?.get(2) ?: SourceManager.defaultBr
bl = corners?.corners?.get(3) ?: SourceManager.defaultBl
val displayMetrics = DisplayMetrics()
(context as Activity).windowManager.defaultDisplay.getMetrics(displayMetrics)
//exclude status bar height
val statusBarHeight = getStatusBarHeight(context)
val navigationBarHeight = getNavigationBarHeight(context)
val width1 = size?.width ?: 1.0
val height1 = size?.height ?: 1.0
val width2 = (displayMetrics.widthPixels).toDouble()
val height2 = (displayMetrics.heightPixels - navigationBarHeight).toDouble()
ratioX = width2.div(width1)
ratioY = height2.div(height1)
Log.i("*****Ratio X",ratioX.toString())
Log.i("*****Ratio Y",ratioY.toString())
Log.i("*****width 1",width1.toString())
Log.i("*****height 1",height1.toString())
Log.i("*****width 2",width2.toString())
Log.i("*****height 2",height2.toString())
Log.i("******BEFORE RESIZE","*******")
Log.i("******tl",tl.toString())
Log.i("******tr",tr.toString())
Log.i("******br",br.toString())
Log.i("******bl",bl.toString())
reverseSize()
Log.i("******After RESIZE","*******")
Log.i("******tl",tl.toString())
Log.i("******tr",tr.toString())
Log.i("******br",br.toString())
Log.i("******bl",bl.toString())
movePoints()
}
fun getCorners2Crop(): List<Point> {
resize()
return listOf(tl, tr, br, bl)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawPath(path, rectPaint)
if (cropMode) {
canvas?.drawCircle(tl.x.toFloat(), tl.y.toFloat(), 20F, circlePaint)
canvas?.drawCircle(tr.x.toFloat(), tr.y.toFloat(), 20F, circlePaint)
canvas?.drawCircle(bl.x.toFloat(), bl.y.toFloat(), 20F, circlePaint)
canvas?.drawCircle(br.x.toFloat(), br.y.toFloat(), 20F, circlePaint)
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
if (!cropMode) {
return false
}
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
latestDownX = event.x
latestDownY = event.y
calculatePoint2Move(event.x, event.y)
}
MotionEvent.ACTION_MOVE -> {
point2Move.x = (event.x - latestDownX) + point2Move.x
point2Move.y = (event.y - latestDownY) + point2Move.y
movePoints()
latestDownY = event.y
latestDownX = event.x
}
}
return true
}
private fun calculatePoint2Move(downX: Float, downY: Float) {
val points = listOf(tl, tr, br, bl)
point2Move = points.minBy { Math.abs((it.x - downX).times(it.y - downY)) } ?: tl
}
private fun movePoints() {
path.reset()
path.moveTo(tl.x.toFloat(), tl.y.toFloat())
path.lineTo(tr.x.toFloat(), tr.y.toFloat())
path.lineTo(br.x.toFloat(), br.y.toFloat())
path.lineTo(bl.x.toFloat(), bl.y.toFloat())
path.close()
invalidate()
}
private fun resize() {
tl.x = tl.x.div(ratioX)
tl.y = tl.y.div(ratioY)
tr.x = tr.x.div(ratioX)
tr.y = tr.y.div(ratioY)
br.x = br.x.div(ratioX)
br.y = br.y.div(ratioY)
bl.x = bl.x.div(ratioX)
bl.y = bl.y.div(ratioY)
}
private fun reverseSize() {
tl.x = tl.x.times(ratioX)
tl.y = tl.y.times(ratioY)
tr.x = tr.x.times(ratioX)
tr.y = tr.y.times(ratioY)
br.x = br.x.times(ratioX)
br.y = br.y.times(ratioY)
bl.x = bl.x.times(ratioX)
bl.y = bl.y.times(ratioY)
}
private fun getNavigationBarHeight(pContext: Context): Int {
val resources = pContext.resources
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
return if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else 0
}
private fun getStatusBarHeight(pContext: Context): Int {
val resources = pContext.resources
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
return if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else 0
}
}