Как преобразовать координаты OpenCV Mat в макет координат в Android? - PullRequest
0 голосов
/ 18 октября 2019

Я строю кроппер изображений в 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
    }
}

1 Ответ

0 голосов
/ 22 октября 2019

Итак, вы можете использовать привязку OpenCV C ++ через Android JNI. Таким образом, нет такой проблемы, потому что камера Android и код OpenCV вычисляют один и тот же буфер изображения. Например, прочитайте эту статью.

...