์ „์ฒด ๊ธ€ (145)

๋ฐ˜์‘ํ˜•
08
27

๋จผ์ € ์‚ฌ์šฉ๋˜๋Š” ๊ธฐ์ˆ  ๋ฐฐ๊ฒฝ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • Window10
  • VirtualBox 7.x.x
  • Ubuntu 18.04(on VirtualBox)
  • ROS Melodic
  • Android Studio (๋…ธํŠธ๋ถ์˜ ๋ชจ๋ฐ”์ผ ํ•ซ์ŠคํŒŸ์— ๋„คํŠธ์›Œํฌ๊ฐ€ ์—ฐ๊ฒฐ๋œ ์ƒํƒœ๋‹ค)
  • Galaxy S20FE

์ด์ „์— rosjava, rosandroid๋ฅผ ์‚ฌ์šฉํ•ด์„œ ROS์™€์˜ ํ†ต์‹ ์„ ๊ตฌ์ถ•ํ•ด๋ณด๋ ค๊ณ ํ–ˆ๋Š”๋ฐ ํŠน์ • ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ maven์—์„œ ๋‚ ๋ผ๊ฐ„ ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค. ์ •ํ™•ํžˆ๋Š” `org.ros.message.MessageListener` ๊ฐ€ ์•ˆ๋ผ์„œ Subscriber๋ฅผ ๋™์ž‘์‹œํ‚ฌ ์ˆ˜๊ฐ€ ์—†์—ˆ๋‹ค. 

 repositories {
    google {
        content {
            includeGroupByRegex("com\\.android.*")
            includeGroupByRegex("com\\.google.*")
            includeGroupByRegex("androidx.*")
        }
    }
    maven { url = uri("https://github.com/rosjava/rosjava_mvn_repo/raw/master") }
    mavenCentral()
    gradlePluginPortal()
}

์ด๋ ‡๊ฒŒ ๊นŒ์ง€ ํ•ด์„œ ๋ฒ„์ „ ํ•˜๋‚˜ํ•˜๋‚˜ ๋ฐ”๊ฟ”๊ฐ€๋ฉฐ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐ›์œผ๋ คํ–ˆ๋Š”๋ฐ ์‹คํŒจํ–ˆ๋‹ค..

์–ด์ฐจํ”ผ rosbridge_server๋กœ rosbridge_websocket์„ ์—ด๋ฉด ์›น์†Œ์ผ“ ์„œ๋ฒ„๊ฐ€ ์—ด๋ฆฌ๋‹ˆ๊นŒ, ์ด๊ฑธ๋กœ ์„ ํšŒํ–ˆ๋‹ค.

# WebSocketClient ๋งŒ๋“ค๊ธฐ

ROS ์ž‘์—…์„ ํ•˜๊ธฐ ์ „์— ์•ˆ๋“œ๋กœ์ด๋“œ ์ชฝ ์ฝ”๋“œ๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•œ๋‹ค.

Java Native์— ๋“ค์–ด์žˆ๋Š” WebSocket์„ ์‚ฌ์šฉํ•ด๋„ ๋์ง€๋งŒ ์ตœ์ ํ™”๊ฐ€ ์ด๋ฏธ ์ž˜ ๋˜์–ด์žˆ๊ณ  stableํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ธ okhttp3๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

[versions]
okhttp = "4.12.0"

[libraries]
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }

์‹œ์ž‘ ์ „ ์ธํ„ฐ๋„ท ๊ถŒํ•œ ๊ผญ manifest์— ํ—ˆ์šฉํ•ด๋‘ฌ์•ผํ•œ๋‹ค.

private var webSocket: WebSocket? = null
private val client = OkHttpClient.Builder()
    .readTimeout(0, TimeUnit.MILLISECONDS)
    .build()

fun connect() {
    val request = Request.Builder().url(url).build()
    webSocket = client.newWebSocket(request, createWebSocketListener())
}

์›น์†Œ์ผ“ ๊ฐ์ฒด๋ฅผ ๋จผ์ € ๋งŒ๋“ ๋‹ค. ์›น์†Œ์ผ“ ์„œ๋ฒ„ url์„ ๋ฐ›์•„์„œ request ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค๊ณ , websocket listener์™€ ํ•จ๊ป˜ client๋กœ ์ƒ์„ฑํ•˜๋ฉด ๋œ๋‹ค. ์ฝ๊ธฐ ํƒ€์ž„์•„์›ƒ์€ 0์œผ๋กœ ์„ค์ •ํ–ˆ๋‹ค. WebSocket ํŠน์„ฑ์ƒ ์—ด์–ด๋‘๊ณ  ๊ณ„์† ์“ธ๊ฑด๋ฐ ํƒ€์ž„์•„์›ƒ์ด ์žˆ๋‹ค๋ฉด ์‘๋‹ต์ด ์˜ค๊ธฐ์ „์— ๋Š์–ด์งˆ ๊ฒƒ์ด๋‹ค.

var onMessageReceived: ((String, String) -> Unit)? = null
var onConnectionOpened: (() -> Unit)? = null
var onConnectionClosed: ((code: Int, reason: String) -> Unit)? = null
var onConnectionFailed: ((Throwable) -> Unit)? = null

private fun createWebSocketListener() = object : WebSocketListener() {
    override fun onOpen(webSocket: WebSocket, response: Response) {
        onConnectionOpened?.invoke()
    }

    override fun onMessage(webSocket: WebSocket, text: String) {
        val json = JSONObject(text)
        val topic = json.optString("topic name")
        val message = json.optJSONObject("msg")?.toString() ?: ""
        onMessageReceived?.invoke(topic, message)
    }

    override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
        webSocket.close(CODE_EXIT, null)
    }

    override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
        onConnectionClosed?.invoke(code, reason)
    }

    override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
        onConnectionFailed?.invoke(t)
    }
}

JSON์˜ค๋ธŒ์ ํŠธ๋กœ ๊ฐ์‹ธ๋†จ๋‹ค. ros์—์„œ ์˜ค๋Š” ์‘๋‹ต ํ˜•ํƒœ๊ฐ€ ์•„๋ž˜์ฒ˜๋Ÿผ jsonํ˜•ํƒœ๋กœ ๋‚ด๋ ค์˜จ๋‹ค. onMessage๋Š” ๋กœ๊ทธ title์ด๊ณ  `{  }` ๋กœ ๊ฐ์‹ธ์ง„๊ฒŒ message๋‹ค.

onMessage: {
    "topic": "/chatter", 
    "msg": {"data": "hello SSAFY 1724739832.18"},
    "op": "publish"
}

์‘๋‹ต ์ฒ˜๋ฆฌํ•  ๋žŒ๋‹คํ•จ์ˆ˜๋ฅผ ๊ฐ์ž ์ง€์ •ํ•ด๋†จ๋‹ค. ์—ญํ• ์ด ํ•˜๋‚˜๋ผ์„œ invoke๋กœ ์‹คํ–‰ํ•ด๋„ ์•„๋ฌด ๋ฌธ์ œ ์—†๋‹ค. ๊ตฌ๋…ํ•œ ํ† ํ”ฝ์—์„œ ๋ฉ”์‹œ์ง€๊ฐ€ ๋“ค์–ด์˜ค๋ฉด onMessage๋ฉ”์„œ๋“œ์— text๊ฐ€ ๋“ค์–ด์˜จ๋‹ค.

 

์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์ด๊ธฐ ๋•Œ๋ฌธ์— ํ† ํ”ฝ์„ ๊ตฌ๋…ํ•˜๊ณ , ๋ฐœํ–‰ํ•˜๋Š” ์ฝ”๋“œ๋„ ๋‹น์—ฐํžˆ ์กด์žฌํ•œ๋‹ค. ๋ฐœํ–‰์‹œ ์ฃผ์˜ํ•  ์ ์€ JSONObject๋ฅผ rosbridge_server๊ฐ€ ๋ฐ›๊ณ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— json์œผ๋กœ ๊ฐ์‹ธ์ค˜์•ผ ์ œ๋Œ€๋กœ ๊ฐ’์ด ๋“ค์–ด๊ฐ„๋‹ค. ์ •ํ™•ํžˆ๋Š” JSON String์ด๋‹ค.

fun subscribe(topic: String) {
    val subscribeMsg = JSONObject().apply {
        put("op", "subscribe")
        put("topic", topic)
    }
    webSocket?.send(subscribeMsg.toString())
}

fun publish(topic: String, message: String) {
    val publishMsg = JSONObject().apply {
        put("op", "publish")
        put("topic", topic)
        put("msg", JSONObject(message))
    }
    webSocket?.send(publishMsg.toString())
}

์ด๋Ÿฐ ํ˜•ํƒœ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ”๋กœ ์†Œ์ผ“ํ†ต์‹ ์œผ๋กœ ์ „์†กํ–ˆ๋‹ค๊ณ  ๋ณด๋ฉด๋œ๋‹ค.

# MainActivity

rosClient = ROSWebSocketClient("ws://$CONNTECT_IP:9090")

๋จผ์ € ์›น์†Œ์ผ“ ์„œ๋ฒ„url์„ ๋„ฃ์–ด์„œ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ ๋‹ค. CONNETECT_IP์˜ ์ค‘์š”์„ฑ์ด ํฌ๋‹ค...

client์—์„œ ๋žŒ๋‹คํ•จ์ˆ˜๋ฅผ ๊ตฌ์ฒดํ™” ํ•˜๊ธฐ์ „, ์šฐ๋ฆฌ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์˜๊ณ , ๋ฐ›๋Š”๋‹ค๋Š” ์ ์—์„œ ์ต์ˆ™ํ•œ ๊ฐœ๋…์„ ํ•˜๋‚˜ ๋– ์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋‹ค. Flow์˜ emit, collect๊ฐ€ ๋”ฑ ๋งž๋Š”๋‹ค.

๋‹จ์œ„ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ ์ค‘์ด๋ผ์„œ ๋”ฐ๋กœ ViewModel๊นŒ์ง€ ์ž‘์„ฑํ•˜์ง€๋Š” ์•Š์•˜๋‹ค.

private lateinit var rosClient: ROSWebSocketClient
private val messageFlow = MutableStateFlow("") // StateFlow๋‹ˆ๊นŒ ์ดˆ๊ธฐํ™” ๊ฐ’ ํ•„์š”

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    enableEdgeToEdge()
    setContentView(R.layout.activity_main)
    ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
        val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
        v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
        insets
    }

    rosClient = ROSWebSocketClient("ws://$CONNTECT_IP:9090")

    lifecycleScope.launch {
        messageFlow.collect { message ->
            tvMessages.append(message)
        }
    }
}

์ตœ์‹  state๋งŒ ์œ ์ง€ํ•˜๊ธฐ ๋ณด๋‹ค๋Š” ์ผ๋‹จ ๋“ค์–ด์˜ค๋Š” ๊ฐ’๋“ค์„ ๋ชจ๋‘ ๋ณด๊ณ ์‹ถ์–ด์„œ collect๋กœ ํ–ˆ๋‹ค.

rosClient ๋žŒ๋‹ค ์„ค์ •ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค. 

private fun initROSClient() {
    rosClient.apply {
        lifecycleScope.launch(Dispatchers.IO) {
            // ๋„คํŠธ์›Œํฌ ์ž‘์—…์ด๋ผ IO์—์„œ ๋™์ž‘
            connect()
        }

        onConnectionOpened = {
            lifecycleScope.launch {
                messageFlow.emit("ROS ์—ฐ๊ฒฐ์„ฑ๊ณต")
                subscribe("/chatter")
            }
        }

        onMessageReceived = { topic, message ->
            lifecycleScope.launch {
                messageFlow.emit("ํ† ํ”ฝ: $topic, ๋ฉ”์‹œ์ง€: $message\n")
            }
        }

        onConnectionClosed = { code, reason ->
            lifecycleScope.launch {
                messageFlow.emit("์—ฐ๊ฒฐ ์ข…๋ฃŒ: $code, $reason\n")
            }
        }

        onConnectionFailed = { error ->
            lifecycleScope.launch {
                messageFlow.emit("์—ฐ๊ฒฐ ์‹คํŒจ: ${error.message}\n")
            }
        }
    }
}

์›น์†Œ์ผ“์— connect๋˜๊ณ  ๋‚˜์„œ ๋ฐ”๋กœ chatter ํ† ํ”ฝ์„ ๊ตฌ๋…ํ•œ๋‹ค. talker-listener ์˜ˆ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ด๋ ‡๊ฒŒ ํ–ˆ๋‹ค.

ํ•˜๋‚˜ ๋” ์ถ”๊ฐ€ํ•ด์ค˜์•ผํ•  ๊ฒŒ ์•กํ‹ฐ๋น„ํ‹ฐ๊ฐ€ onDestroy ๋  ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•œ websocket ํ†ต์‹  ๋Š๊ธฐ๋‹ค.

override fun onDestroy() {
    super.onDestroy()
    rosClient.disconnect()
}


// ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑ๋จ
fun disconnect() {
    webSocket?.close(CODE_EXIT, "disconnect")
}

 

์ด๋Ÿฌ๋ฉด ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ ์ž‘์„ฑํ•ด์•ผ๋˜๋Š” ๊ฑด ๋๋‚ฌ๋‹ค. ๋ฌธ์ œ๋Š” ์ด์ œ ์–ด๋–ป๊ฒŒ ๊ฐ€์ƒ๋จธ์‹  ์œ„์˜ ROS์—์„œ ๋ณด๋‚ด๋Š” ํ† ํ”ฝ์„ ๊ตฌ๋…ํ•ด์„œ android ๊นŒ์ง€ ๊ฐ€์ ธ์˜ค๋Š” ๊ฑธ ์–ด๋–ป๊ฒŒ ํ•˜๋Š” ์ง€๊ฐ€ ๋‚จ๋Š”๋‹ค. ์ด๊ฒŒ ์ œ์ผ ์ค‘์š”ํ•˜๋‹ค. 

# ํ†ต์‹  ์„ค์ •ํ•˜๊ธฐ

๋ฒ„์ถ”์–ผ ๋ฐ•์Šค์— ์ผ๋‹จ ROS ํ†ต์‹ ์šฉ์œผ๋กœ ์‚ฌ์šฉํ•  ํ˜ธ์ŠคํŠธ ์ „์šฉ ์–ดํƒญํ„ฐ๋ฅผ ์—ด์—ˆ๋‹ค.

์šฐ๋ถ„ํˆฌ๋กœ ๋“ค์–ด๊ฐ€ ifconfig์œผ๋กœ ์ด ์–ด๋Œ‘ํ„ฐ์˜ ip๋ฅผ ํ™•์ธํ•ด์„œ ์ ์–ด๋‘”๋‹ค. rosbridge_server๋ฅผ ์—ด๊ฒŒ๋˜๋ฉด ์ด ์•„์ดํ”ผ ๋ฐ‘์—์„œ ํฌํŠธ๋ฒˆํ˜ธ 9090์œผ๋กœ ์—ด๋ฆฐ๋‹ค.

๊ทธ๋Ÿผ ์•„๊นŒ CONNECT_IP์— ์ € ๋…ธ๋ž—๊ฒŒ ๊ฐ€๋ ค๋‘” IP๋ฅผ ์ ์œผ๋ฉด ๋ ๊นŒ? ์•ˆ๋œ๋‹ค.

์ง€๊ธˆ ์•ˆ๋“œ๋กœ์ด๋“œ - ์œˆ๋„์šฐ10 - ROS on ์šฐ๋ถ„ํˆฌ ์ด๋ ‡๊ฒŒ ์—ฐ๊ฒฐ๋˜์–ด์žˆ๋Š”๋ฐ, ์ €๋ ‡๊ฒŒ ๋“ค์–ด๊ฐ€๋ฉด ์ ‘์†ํ•  ์ˆ˜๊ฐ€ ์—†๋‹ค. ์ด์ œ ์œˆ๋„์šฐ์—์„œ ์„ค์ •์„ ํ•ด์ค˜์•ผ๋œ๋‹ค.

๋ฐฉํ™”๋ฒฝ์„ ์—ด์–ด์„œ ์ธ๋ฐ”์šด๋“œ ๊ทœ์น™์œผ๋กœ ํฌํŠธ ๋“ฑ๋ก์„ ํ•ด์ค€๋‹ค. ์ด๋ ‡๊ฒŒ ์•ˆํ•˜๋ฉด ๋ณด์•ˆ๋•Œ๋ฌธ์— ํฌํŠธ์— ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ๋„ ์žˆ๋‹ค.

์ด์ œ ipconfig์„ cmd์— ์จ์„œ ๋„์›Œ๋ณด์ž.

์ด๊ฒŒ ๋‚ด ๋…ธํŠธ๋ถ์˜ IP๋‹ค. ์•ˆ๋“œ๋กœ์ด๋“œ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋…ธํŠธ๋ถ์œผ๋กœ ๋“ค์–ด์™€์„œ ROS์ชฝ์œผ๋กœ ๋„˜์–ด๊ฐˆ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์šฐ๋ฆฌ๋Š” ์ด ์œˆ๋„์šฐ๋ฅผ ์ค‘๊ณ„๊ธฐ๋กœ ์จ์„œ, ์›น์†Œ์ผ“์„ ์—ฐ๊ฒฐํ•  ๊ฒƒ์ด๋‹ค.

 

๊ทธ๋ž˜์„œ ํฌํŠธ ํฌ์›Œ๋”ฉ์ด ํ•„์š”ํ•˜๋‹ค. ์œˆ๋„์šฐ์™€ ์•ˆ๋“œ๋กœ์ด๋“œ๊ฐ€ ํ†ต์‹ ํ•˜๋Š”๋ฐ ์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ ๋ฐ”๋กœ ์ง๊ฒฐํ•˜๋ฉด ๋‹ค๋ฅธ ํฌํŠธ๋ฅผ ๋ฐ”๋ผ๋ณด๊ณ ์žˆ๋‹ค. ๋‹ค์‹œ ์œˆ๋„์šฐ cmd๋ฅผ ์—ด์–ด์„œ ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ํฌํŠธํฌ์›Œ๋”ฉ์ด ๋๋‚œ๋‹ค. IP ์ฃผ์†Œ๋“ค์€ ๋ชจ๋‘ IPv4๋‹ค.

netsh interface portproxy add v4tov4 listenport=9090 listenaddress={๋‚ด ๋…ธํŠธ๋ถ ํ•ซ์ŠคํŒŸ IP} connectpost=9090 connectaddress={ROS IP}

์ด๊ฑด ๋“ฑ๋กํ•˜๋Š” ๊ฒƒ์ด๊ณ  ์•„๋ž˜ ๋‘ ๊ฐœ๋Š” ์ œ๊ฑฐ, ์กฐํšŒ๋‹ค.

  • netsh interface portproxy delete v4tov4 listenport=9090 listenaddress={๋‚ด ๋…ธํŠธ๋ถ ํ•ซ์ŠคํŒŸ IP}
  • netsh interface portproxy show v4tov4

์ด๊ฑธ ํ•˜๊ณ  ๋‚˜๋ฉด ์œˆ๋„์šฐ๋ฅผ ๊ฑฐ์น˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ

flowchart LR
    A[์•ˆ๋“œ๋กœ์ด๋“œ] <-->B{์œˆ๋„์šฐ}
    B <--> C[ROS Ubuntu]
    A[์•ˆ๋“œ๋กœ์ด๋“œ] <-->|ํฌํŠธํฌ์›Œ๋”ฉ| C[ROS Ubuntu]

์•ˆ๋“œ๋กœ์ด๋“œ - ์œˆ๋„์šฐ - ROS ๊ณผ์ •์—์„œ ๋ฒ—์–ด๋‚˜ ์•ˆ๋“œ๋กœ์ด๋“œ - ROS๊ฐ€ ๋ฐ”๋กœ ์ง๊ฒฐ๋œ๋‹ค.

ROS์—์„œ talker ์˜ˆ์ œ๋ฅผ ์ผœ๋‘๊ณ , android์—์„œ chatter๋ฅผ ๊ตฌ๋…ํ•ด์„œ ๋‚˜์˜จ ๊ฒฐ๊ณผ๋‹ค.

 

๋„์›€์ด ๋๋‹ค๋ฉด ๋Œ“๊ธ€์ด๋‚˜ ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์„ธ์š”!

 

๋ฐ˜์‘ํ˜•
COMMENT
 
08
27

https://school.programmers.co.kr/learn/challenges?tab=algorithm_practice_kit

 

ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค

์ฝ”๋“œ ์ค‘์‹ฌ์˜ ๊ฐœ๋ฐœ์ž ์ฑ„์šฉ. ์Šคํƒ ๊ธฐ๋ฐ˜์˜ ํฌ์ง€์…˜ ๋งค์นญ. ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค์˜ ๊ฐœ๋ฐœ์ž ๋งž์ถคํ˜• ํ”„๋กœํ•„์„ ๋“ฑ๋กํ•˜๊ณ , ๋‚˜์™€ ๊ธฐ์ˆ  ๊ถํ•ฉ์ด ์ž˜ ๋งž๋Š” ๊ธฐ์—…๋“ค์„ ๋งค์นญ ๋ฐ›์œผ์„ธ์š”.

programmers.co.kr

ํ‘ธ๋Š” ๋™์•ˆ ์‚ฌ๊ณ ๊ณผ์ • ๋ฐ ๊ฐœ์„ ์ 

import java.util.*;

class Solution {
    public int[] solution(String[] genres, int[] plays) {
        ArrayList<Integer> arr = new ArrayList();
        HashMap<String, PriorityQueue<Song>> s = new HashMap();
        HashMap<String, Integer> g = new HashMap();
        for(int i = 0; i < genres.length; i++){
            g.put(genres[i], g.getOrDefault(genres[i], 0)+plays[i]); // ์–˜๋Š” ์žฅ๋ฅด๋ณ„ ์ด ์žฌ์ƒ ์ˆ˜ ๋„ฃ๊ฒŒ
            s.putIfAbsent(genres[i], new PriorityQueue<Song>((o1,o2)->{
                if(o2.plays == o1.plays){
                    // ์žฌ์ƒ์ˆ˜๊ฐ€ ๊ฐ™์œผ๋ฉด id๊ฐ€ ๋‚ฎ์€๊ฑธ๋กœ ์ •๋ ฌ
                    return o1.id - o2.id;
                }
                return o2.plays-o1.plays;
            }));
            s.get(genres[i]).add(new Song(i, plays[i]));
        } // ์ž…๋ ฅ์„ ๋ฐ›์•˜๋‹ค. ๊ฐ ์žฅ๋ฅด ๋ณ„๋กœ plays ์ˆœ ๋‚ด๋ฆผ์ •๋ ฌ, plays๊ฐ€ ๊ฐ™์œผ๋ฉด id ์˜ค๋ฆ„์ •๋ ฌ.
        // ๊ฐ ์žฅ๋ฅด ๋ณ„๋กœ 2๊ฐœ์”ฉ ๋ฝ‘์œผ๋ฉด ๋˜๋Š”๋ฐ, 1๊ฐœ๋งŒ ์žˆ๋‹ค๋ฉด ๊ทธ๊ฑฐ๋งŒ ์„ ํƒํ•œ๋‹ค
        // ๋งŽ์ด ์žฌ์ƒ๋œ ์žฅ๋ฅด๋ฅผ ๋จผ์ € ์ˆ˜๋กํ•œ๋‹ค -> ์ด๊ฒŒ ์ข€ ์• ๋งค
        
        // ํ‚ค-๊ฐ’ ์Œ์„ List๋กœ ๋ณ€ํ™˜
        List<Map.Entry<String, Integer>> list = new ArrayList(g.entrySet());
        // value๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌ
        Collections.sort(list, (o1, o2) -> o2.getValue().compareTo(o1.getValue()));
        
        for(Map.Entry<String, Integer> entry: list){
            // ์žฌ์ƒ ์ˆ˜๋กœ ์ •๋ ฌ๋œ ํ‚ค๋ฅผ ๊ฐ–๋‹ค๊ฐ€
            String genre = entry.getKey();
            int cnt = 0;
            while(cnt < 2){
                if(!s.get(genre).isEmpty()){
                    // ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋‚จ์•„์žˆ์œผ๋ฉด
                    arr.add(s.get(genre).poll().id);
                    cnt++;
                }else{
                    // ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋น„์–ด์žˆ๋Š” ๊ฒฝ์šฐ
                    break;
                }
            }
        }
        int[] answer = new int[arr.size()];
        int idx = 0;
        for(int id: arr){
            answer[idx++] = id;
        }
        
        return answer;
    }
}

class Song{
    int id; 
    int plays;
    Song(int id, int plays){
        this.id = id;
        this.plays = plays;
    }
}

IDE์•ˆ์“ฐ๊ณ  ๊ทธ๋ƒฅ ๋ฐ”๋กœ ํ’€์—ˆ๋‹ค. Map ์กฐ์ž‘์— ์กฐ๊ธˆ ์ต์ˆ™ํ•ด์ง„ ๊ฒƒ ๊ฐ™๋‹ค.

์กฐ๊ฑด์ด ์ข€ ๋งŽ์•„์„œ ๊นŒ๋‹ค๋กœ์› ๋˜ ๋ฌธ์ œ๋‹ค. 

  1. ์ด ์žฌ์ƒ ์ˆ˜๊ฐ€ ๋งŽ์€ ์žฅ๋ฅด ์ˆœ์„œ๋Œ€๋กœ
  2. ์žฅ๋ฅด ๋‚ด์—์„œ ์žฌ์ƒ ์ˆ˜๊ฐ€ ๋งŽ์€ ์ˆœ์„œ๋Œ€๋กœ
  3. ์žฌ์ƒ ์ˆ˜๊ฐ€ ๊ฐ™๋‹ค๋ฉด id๊ฐ€ ์ž‘์€ ์ˆœ์„œ๋Œ€๋กœ 
  4. 2๊ฐœ๊นŒ์ง€ ๋ฝ‘์•„์„œ id๋ฅผ ๊ธฐ๋กํ•ด๋ผ

์ž‘๊ฒŒ ๋ถ€๋ถ„์„ ๋‚˜๋ˆ ์„œ ์ž‘์„ฑํ•˜๋‹ˆ๊นŒ ์กฐ๊ธˆ ๋” ํŽธํ–ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

HashMap์˜ value๋กœ Collection์„ ์‚ฌ์šฉํ•ด๋ณด๋‹ˆ๊นŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉํ–ฅ์ด ๋งŽ์„ ๊ฒƒ ๊ฐ™๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. 


# ์ถ”๊ฐ€ ๊ณต๋ถ€

์ด๋ฒˆ ๋ฌธ์ œ ํ’€๋ฉด์„œ ๊ฒ€์ƒ‰ํ—€๋˜ ๊ฑด HashMap์—์„œ key ๊ธฐ์ค€์œผ๋กœ ์ •๋ ฌํ•˜๋Š” ๋ฐฉ๋ฒ•์ด์—ˆ๋‹ค.

List<Map.Entry<String, Integer>> list = new ArrayList(g.entrySet());
Collections.sort(list, (o1, o2) -> o2.getValue().compareTo(o1.getValue()));

entrySet์„ ArrayList์— ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑธ ์ฒ˜์Œ์•Œ์•˜๋‹ค.

entrysetํƒ€์ž…์€ Map.Entryํ˜•ํƒœ๋‹ˆ๊นŒ ์ฃผ์˜ํ•ด์•ผ๋˜๊ณ , compareTo๋กœ ๋‚ด๋ฆผ์ฐจ์ˆœ ์ •๋ ฌ์ด ๋œ list๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค

Collections.sort(list, (o1, o2) -> o1.getValue().compareTo(o2.getValue()));

์ด๋Ÿฌ๋ฉด ์˜ค๋ฆ„์ฐจ์ˆœ์ด๋‹ค.

compareTo์˜ ์•ž์— ์žˆ๋Š”๊ฑธ ๊ธฐ์ค€์œผ๋กœ compareTo์•ˆ์˜ ๊ฐ’์ด ํฌ๋ฉด ์˜ค๋ฆ„์ฐจ์ˆœ, ์ž‘์œผ๋ฉด ๋‚ด๋ฆผ์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌ๋œ๋‹ค. Comparator๋ฅผ ๋žŒ๋‹ค๋กœ ์ž‘์„ฑํ•œ ํ˜•ํƒœ๋ผ๊ณ  ๋ณด๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

 

๋„์›€์ด ๋๋‹ค๋ฉด ๋Œ“๊ธ€์ด๋‚˜ ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์„ธ์š”!

 

๋ฐ˜์‘ํ˜•
COMMENT
 
08
27

https://school.programmers.co.kr/learn/challenges?tab=algorithm_practice_kit

 

ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค

์ฝ”๋“œ ์ค‘์‹ฌ์˜ ๊ฐœ๋ฐœ์ž ์ฑ„์šฉ. ์Šคํƒ ๊ธฐ๋ฐ˜์˜ ํฌ์ง€์…˜ ๋งค์นญ. ํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค์˜ ๊ฐœ๋ฐœ์ž ๋งž์ถคํ˜• ํ”„๋กœํ•„์„ ๋“ฑ๋กํ•˜๊ณ , ๋‚˜์™€ ๊ธฐ์ˆ  ๊ถํ•ฉ์ด ์ž˜ ๋งž๋Š” ๊ธฐ์—…๋“ค์„ ๋งค์นญ ๋ฐ›์œผ์„ธ์š”.

programmers.co.kr

ํ‘ธ๋Š” ๋™์•ˆ ์‚ฌ๊ณ ๊ณผ์ • ๋ฐ ๊ฐœ์„ ์ 

import java.util.*;

class Solution {
    public int solution(int[] scoville, int K) {
        int answer = 0;
        PriorityQueue<Integer> pq = new PriorityQueue();
        for(int i: scoville){
            pq.add(i);
        } // ๊ธฐ๋ณธ์ด min heap
        int s = 0;
        while(s<K){
            if(pq.isEmpty()){
                answer = -1;
                break;
            }
            int easy = pq.poll();
            int eeasy = pq.poll();
            s = easy + (2*eeasy);
            answer++;
        }
        
        
        return answer;
    }
}

์ฒ˜์Œ์— ๋ฌธ์ œ์ดํ•ด๋ฅผ ์™„์ „ํžˆ ์ž˜๋ชปํ–ˆ๋‹ค. ๋ชจ๋“  ์Œ์‹์˜ ์Šค์ฝ”๋นŒ์ง€์ˆ˜๊ฐ€ K์ด์ƒ์ด ๋˜์–ด์•ผํ•œ๋‹ค.

import java.util.*;

class Solution {
    public int solution(int[] scoville, int K) {
        int answer = 0;
        PriorityQueue<Integer> pq = new PriorityQueue();
        for(int i: scoville){
            pq.add(i);
        } // ๊ธฐ๋ณธ์ด min heap
        while(pq.peek()<K){
            if(pq.isEmpty()){
                answer = -1;
                break;
            }
            if(pq.size()>=2){
                int easy = pq.poll();
                int eeasy = pq.poll();
                pq.add(easy + (2*eeasy)); // ์ƒˆ ์Œ์‹ ๋งŒ๋“ค์–ด์„œ ์ถ”๊ฐ€
                answer++;
            }
        }
        
        return answer;
    }
}

์ด๊ฒŒ ์ˆ˜์ •ํ•œ ์ฝ”๋“œ๋‹ค. ์‹œ๊ฐ„์ดˆ๊ณผ๋‚˜๋Š” ์—„์ฒญ ํฐ ์ผ€์ด์Šค๋ฅผ ์ œ์™ธํ•˜๊ณ ์„œ ๋‹ค ํ†ต๊ณผ๋œ๋‹ค...

์กฐ๊ธˆ๋งŒ ์ƒ๊ฐํ•ด๋ณด๋ฉด ํ•ด๊ฒฐ๋˜๋Š” ๋ถ€๋ถ„์ด์—ˆ๋‹ค. K ์ด์ƒ์œผ๋กœ ์Œ์‹์„ ๋งŒ๋“ค๋ฉด ๋˜๋‹ˆ๊นŒ minheap์„ ๋ฝ‘์•˜์„ ๋•Œ K์ด์ƒ์ธ ๊ฒฝ์šฐ break๋ฅผ ์ค˜๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์ด๋‹ค.

import java.util.*;

class Solution {
    public int solution(int[] scoville, int K) {
        int answer = 0;
        PriorityQueue<Integer> pq = new PriorityQueue();
        for(int i: scoville){
            pq.add(i);
        } // ๊ธฐ๋ณธ์ด min heap
        while(pq.peek()<K){
            if(pq.size()>=2){
                int easy = pq.poll();
                int eeasy = pq.poll();
                pq.add(easy + (2*eeasy)); // ์ƒˆ ์Œ์‹ ๋งŒ๋“ค์–ด์„œ ์ถ”๊ฐ€
                answer++;
            }else{
                answer = -1;
                break;
            }
            if(pq.peek() >= K) break;
        }
        
        return answer;
    }
}

pq ์‚ฌ์ด์ฆˆ๋กœ break ์กฐ๊ฑด๋„ ๋ฐ”๊ฟ”์„œ ๋„ฃ์–ด๋ดค๋‹ค.


# ์ถ”๊ฐ€ ๊ณต๋ถ€

`PriorityQueue` ํ™œ์šฉํ•˜๊ธฐ

๊ธฐ๋ณธ์ด minheap์ด๋‹ค ๊ทธ๋ž˜์„œ reverseOrderํ•ด๋ฒ„๋ฆฌ๋ฉด maxheap์ด ๋œ๋‹ค.

PriorityQueue<Integer> maxHeap = new PriorityQueue(Collections.reverseOrder());
PriorityQueue<Integer> maxHeap = new PriorityQueue((o1, o2)-> {return o2-o1;});

์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ๋‹ค๋ฅธ๊ฑธ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ด๋ ‡๊ฒŒ๋„ ์“ธ ์ˆ˜ ์žˆ๋‹ค.

PriorityQueue<Student> pq = new PriorityQueue<>((s1, s2) -> {
    if (s1.grade == s2.grade) {
        return s1.name.compareTo(s2.name);
    }
    return s2.grade - s1.grade;
});

๊ทธ๋ฆฌ๋””ํ•œ ๋ฌธ์ œ๋ฅผ ํ’€ ๋•Œ ์ด๋ ‡๊ฒŒ ํ•˜๊ณค ํ–ˆ๋‹ค.

 

๋„์›€์ด ๋๋‹ค๋ฉด ๋Œ“๊ธ€์ด๋‚˜ ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์„ธ์š”!

 

๋ฐ˜์‘ํ˜•
COMMENT
 
08
26

https://github.com/kimmandoo/android-drill/tree/main/FaceMesh

 

android-drill/FaceMesh at main · kimmandoo/android-drill

๊ธฐ์ˆ  ์—ฐ์Šต์šฉ repo. Contribute to kimmandoo/android-drill development by creating an account on GitHub.

github.com

 

์•ˆ๋“œ๋กœ์ด๋“œ ์˜จ๋””๋ฐ”์ด์Šค AI API๋กœ ์ œ๊ณต๋˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ค‘ MLKit์ด ์žˆ๋‹ค. ๊ทธ ์ค‘ ์–ผ๊ตด Mesh ๊ฐ์ง€๋ฅผ ์ด์šฉํ•œ ์กธ์Œ๊ฐ์ง€๋ฅผ ํ•ด๋ณด๋ ค๊ณ  ๋‹จ์œ„ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ–ˆ๋‹ค.

https://developers.google.com/ml-kit/vision/face-mesh-detection?hl=ko

 

์–ผ๊ตด ๋ฉ”์‹œ ๊ฐ์ง€  |  ML Kit  |  Google for Developers

์ด ํŽ˜์ด์ง€๋Š” Cloud Translation API๋ฅผ ํ†ตํ•ด ๋ฒˆ์—ญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์–ผ๊ตด ๋ฉ”์‹œ ๊ฐ์ง€ ์ปฌ๋ ‰์…˜์„ ์‚ฌ์šฉํ•ด ์ •๋ฆฌํ•˜๊ธฐ ๋‚ด ํ™˜๊ฒฝ์„ค์ •์„ ๊ธฐ์ค€์œผ๋กœ ์ฝ˜ํ…์ธ ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ถ„๋ฅ˜ํ•˜์„ธ์š”. ๋‹ฌ๋ฆฌ ๋ช…์‹œ๋˜์ง€ ์•Š๋Š” ํ•œ ์ด ํŽ˜์ด์ง€์˜ ์ฝ˜

developers.google.com

๊ต‰์žฅํžˆ API ๊ฐ€ ์ž˜๋ผ์žˆ๋‹ค. ๋”ฐ๋กœ asset ์ถ”๊ฐ€ํ•  ํ•„์š”์—†์ด gradle ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฒˆ๋“ค๋ง๋  ๋•Œ ํฌํ•จ๋˜์–ด ๋นŒ๋“œ๋œ๋‹ค.

Mesh๋ฅผ ์ด์šฉํ•˜๋Š” ์ด์œ ๋Š” ๋ˆˆ ์ฃผ๋ณ€์˜ point๋“ค์„ ๊ฐ์ง€ํ•ด์„œ ๋ˆˆ์ด ๊ฐ๊ฒผ๋Š”์ง€, ๋– ์žˆ๋Š” ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€์žฅ ๊ฐ„ํŽธํ•œ ๋ฐฉ๋ฒ•์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ด๋ ‡๊ฒŒ ์ƒ๊ธด ๊ทธ๋ฌผ๋ง์ด ์–ผ๊ตด์ด ๊ฐ์ง€๋˜๋ฉด ๋ถ€์ฐฉ๋˜๊ณ , ๊ฐ ํŒŒํŠธ๋ณ„๋กœ point๊ฐ€ ์ง€์ •๋˜์–ด์žˆ๊ณ , contour๋กœ ์œ ํ˜•์ด ๋ถ„๋ฅ˜ ๋˜์–ด์žˆ๋‹ค.

3์ ์„ ์กฐํ•ฉํ•ด์„œ ๊ตฌ์—ญ์„ ๊ฐ์ง€ํ•˜๋Š” ๊ฒƒ๋„ ๋œ๋‹ค. ๊ทธ๋ž˜์„œ ์˜ˆ์‹œ๋กœ ๋‚˜์˜จ ๋ฌธ์žฅ์„ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

์‚ผ๊ฐํ˜• ์ •๋ณด: ๊ฐ์ง€๋œ ์–ผ๊ตด์˜ ๋…ผ๋ฆฌ์  ์‚ผ๊ฐํ˜• ํ‘œ๋ฉด์„ ๋‚˜ํƒ€๋‚ด๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๊ฐ ์‚ผ๊ฐํ˜•์—๋Š” 3D ํฌ์ธํŠธ 3๊ฐœ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์ง€์  #0, #37, #164๋Š” ์ฝ”์™€ ์ž…์ˆ  ์‚ฌ์ด์— ์ž‘์€ ์‚ผ๊ฐํ˜• ์˜์—ญ์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๊ตฌํ˜„์„ ์œ„ํ•ด ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

[versions]
faceMeshDetection = "16.0.0-beta3"
cameraCore = "1.3.4"

[libraries]
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraCore" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraCore" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraCore" }
androidx-camera-core = { group = "androidx.camera", name = "camera-core", version.ref = "cameraCore" }
face-mesh-detection = { module = "com.google.mlkit:face-mesh-detection", version.ref = "faceMeshDetection" }

์‚ฌ์ง„์„ ์ฐ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์นด๋ฉ”๋ผ๋ฅผ ๋„์›Œ์„œ ์ŠคํŠธ๋ฆฌ๋ฐ๊ฐ™์ด ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค. camera-core๊ฐ€ ํ•ต์‹ฌ์ด๋‹ค.

 

์นด๋ฉ”๋ผ๋ฅผ ์“ฐ๋ ค๋ฉด ์šฐ์„  Manifest๋ฅผ ์ง€์ •ํ•ด๋‘๊ณ , ๊ถŒํ•œ ๋ฐ›๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•˜๋‹ค.

<uses-feature
    android:name="android.hardware.camera"
    android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
private fun checkPermission() = REQUIRED_PERMISSIONS.all {
    ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}

private fun requestPermissions() {
    ActivityCompat.requestPermissions(
        this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
    )
}

override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<String>,
    grantResults: IntArray
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    if (requestCode == REQUEST_CODE_PERMISSIONS) {
        if (checkPermission()) {
            openCamera()
        } else {
            Toast.makeText(
                this,
                "์นด๋ฉ”๋ผ ๊ถŒํ•œ ํ•„์š”",
                Toast.LENGTH_SHORT
            ).show()
            finish()
        }
    }
}

companion object {
    private const val REQUEST_CODE_PERMISSIONS = 10
    private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
}

`REQUEST_CODE_PERMISSIONS`๋Š” ์•Œ์•„์„œ ๋„ฃ์œผ๋ฉด ๋œ๋‹ค.

`onRequestPermissionsResult`๋Š” ๊ถŒํ•œ ์š”์ฒญ์— ๋Œ€ํ•œ REQUEST_CODE๋ฅผ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•œ ํ•จ์ˆ˜์ธ๋ฐ ActivityResult๋กœ ๋งŒ๋“ค์–ด์„œ launcher๋กœ ๋ฐ›์•„๋„ ๋ฌด๋‚œํ•˜๋‹ค. ์ง€๊ธˆ์€ ์•กํ‹ฐ๋น„ํ‹ฐ๋กœ ๊ตฌํ˜„ํ•ด์„œ ์ด๋ ‡๊ณ  fragment๋กœ๊ฐ€๋ฉด ์ง€๊ธˆ์ด๋ž‘ ๋‹ค๋ฅธ ํ˜•ํƒœ๊ฐ€ ๋œ๋‹ค.

# FaceMeshDectector ์‚ฌ์šฉํ•˜๊ธฐ

meshDetector = FaceMeshDetection.getClient(
    FaceMeshDetectorOptions.Builder()
        .setUseCase(FaceMeshDetectorOptions.FACE_MESH)
        .build()
)

๋จผ์ € client๋ฅผ ๋งŒ๋“ค์–ด๋‘”๋‹ค. ์ด ์•ˆ์— ์ด๋ฏธ์ง€๋ฅผ ๋„ฃ์–ด์„œ mesh๋ฅผ ์”Œ์šฐ๋Š” ๊ฒƒ์ด๋‹ค. MLKit ๊ณต์‹๋ฌธ์„œ์— ์žˆ๋Š” ImageAnalyzer๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

๊ทธ ์ „์— xml์ƒ์—์„œ camera-view์˜ PreviewView๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์นด๋ฉ”๋ผ๋ฅผ ๋„์šธ ํ™”๋ฉด์„ ์ง€์ •ํ•ด์ค€๋‹ค. ์นด๋ฉ”๋ผ๋ฅผ PreviewView์— ๋ฌถ์–ด๋‘”๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

val preview = Preview.Builder()
            .build()
            .also {
                it.setSurfaceProvider(previewView.surfaceProvider)
            }
            
try {
    cameraProvider.unbindAll()
    cameraProvider.bindToLifecycle(
        this,
        CameraSelector.DEFAULT_FRONT_CAMERA,
        preview,
        imageAnalysis
    )
} catch (e: Exception) {
    Log.d(TAG, "error: $e")
}

์นด๋ฉ”๋ผ๋„ ProcessCameraPrivider๋กœ ์—ด๊ธฐ๋•Œ๋ฌธ์— ์—ฌ๊ธฐ์„œ ์—ฐ cameraProvider๋ฅผ surface๋กœ ๊ฐ์‹ธ์„œ PreviewView์— ๋„ฃ์–ด์ฃผ๋Š” ์ฝ”๋“œ๋ผ๊ณ  ๋ณด๋ฉด๋œ๋‹ค.

 

์ด์ œ Mesh ํƒ์ƒ‰ ์ฝ”๋“œ๋ฅผ ๋ด์•ผ๋œ๋‹ค. ์นด๋ฉ”๋ผ์—์„œ ๊ฐ€์ ธ์˜จ Input์„ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ํ”„๋ ˆ์ž„ ๋ณ„๋กœ ๋ฐ˜์‘ํ•˜๋‹ค๋ณด๋‹ˆ ๋„ˆ๋ฌด ๋นจ๋ผ์„œ ์ผ๋ถ€๋Ÿฌ delay๋ฅผ 0.2s๋กœ ๊ฑธ์–ด๋†จ๋‹ค.

imageAnalysis = ImageAnalysis.Builder()
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()

ImageAnalysis ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋ถ„์„ ์กฐ๊ฑด์„ ์ง€์ •ํ•œ๋‹ค. ๊ฐ€์žฅ ์ตœ๊ทผ ํ”„๋ ˆ์ž„๋งŒ ์œ ์ง€ํ•˜๊ณ  ๋‚˜๋จธ์ง€๋Š” ๋ฒ„๋ฆฌ๋Š” ์ „๋žต์„ ์ฑ„ํƒํ–ˆ๋‹ค. ์ฝ”๋ฃจํ‹ด Flow์˜ collectLatest์™€ ๋™์ผํ•œ ๊ฐœ๋…์ด๋ผ ๋ณด๋ฉด๋œ๋‹ค. 

imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this)) { imageProxy ->
    val mediaImage = imageProxy.image
    if (mediaImage != null) {
        val image =
            InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
        meshDetector.process(image)
            .addOnSuccessListener { faceMeshes ->
                lifecycleScope.launch {
                    delay(200)
                    for (faceMesh in faceMeshes) {
                        val leftEye = faceMesh.getPoints(FaceMesh.LEFT_EYE)
                        val rightEye = faceMesh.getPoints(FaceMesh.RIGHT_EYE)
                        Log.d(TAG, "face: $leftEye || $rightEye")

                        val leftEyeOpen = isEyeOpen(leftEye)
                        val rightEyeOpen = isEyeOpen(rightEye)

                        Log.d(TAG, "Left eye open: $leftEyeOpen, Right eye open: $rightEyeOpen")

                        // ์–‘์ชฝ ๋ˆˆ์˜ ์ƒํƒœ์— ๋”ฐ๋ผ ์ „์ฒด์ ์ธ ๋ˆˆ ๊ฐ๊น€ ์—ฌ๋ถ€ ํŒ๋‹จ
                        val bothEyesOpen = leftEyeOpen && rightEyeOpen
                        Log.d(TAG, "Both eyes open: $bothEyesOpen")

                    }
                }
            }
            .addOnFailureListener { e ->
                Log.d(TAG, "error: $e")
            }
            .addOnCompleteListener {
                imageProxy.close()
            }
    }
}

์ด์ œ ๋ˆˆ์˜ ๊ฐ€๋กœ, ์„ธ๋กœ ์–‘๋ ์ ์„ ๊ธฐ์ค€์œผ๋กœ ์–ผ๋งˆ๋‚˜ ๊ฐ๊ฒผ๋Š”์ง€ ํŒ๋‹จํ•˜๋Š” ํ•จ์ˆ˜๋งŒ ์ž‘์„ฑํ•˜๋ฉด ๋๋‚œ๋‹ค.

private fun isEyeOpen(eyePoints: List<FaceMeshPoint>): Boolean {
    // ๋ˆˆ์˜ ์„ธ๋กœ ์–‘ ๋
    val topPoint = eyePoints.minByOrNull { it.position.y }
    val bottomPoint = eyePoints.maxByOrNull { it.position.y }

    // ๋ˆˆ์˜ ๊ฐ€๋กœ ์–‘ ๋
    val leftPoint = eyePoints.minByOrNull { it.position.x }
    val rightPoint = eyePoints.maxByOrNull { it.position.x }

    if (topPoint != null && bottomPoint != null && leftPoint != null && rightPoint != null) {
        val eyeHeight = bottomPoint.position.y - topPoint.position.y
        val eyeWidth = rightPoint.position.x - leftPoint.position.x
        val aspectRatio = eyeHeight / eyeWidth

        // ์ข…ํšก๋น„๊ฐ€ ์ž„๊ณ„๊ฐ’๋ณด๋‹ค ํฌ๋ฉด ๋ˆˆ์ด ๋– ์ ธ์žˆ์Œ.
        val isOpen = aspectRatio > EYE_ASPECT_RATIO_THRESHOLD
        return isOpen
    }

    return false
}

EYE_ASPECT_RATIO_THRESHOLD๋Š” ํ•„์š”ํ•œ ๊ฒฝ์šฐ ๋ฐ”๊ฟ”์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. ๋‚˜๋Š” 0.2f๋กœ ํ–ˆ๋‹ค.

 

๋„์›€์ด ๋๋‹ค๋ฉด ๋Œ“๊ธ€์ด๋‚˜ ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์„ธ์š”!

 

๋ฐ˜์‘ํ˜•
COMMENT