地震警報

此為預警系統自動上傳之資訊,準確信息請以交通部中央氣象署為主!

地震速報

地點:長野県北部

發生時間:2025/04/18
20:19:29

地震規模:🔵 芮氏 5.2

地震深度:🔴 10公里

最大震度:🔴 5弱級

震央位置:長野県北部

緯度:36.5

經度:137.9

發布時間:2025年04月18日 19:19:45


地震速報

地點:長野県北部

發生時間:2025/04/18
20:19:29

地震規模:🔵 芮氏 5.2

地震深度:🔴 10公里

最大震度:🔴 5弱級

震央位置:長野県北部

緯度:36.5

經度:137.9

發布時間:2025年04月18日 19:19:44


地震速報

地點:長野県北部

發生時間:2025/04/18
20:19:29

地震規模:🔵 芮氏 5.2

地震深度:🔴 10公里

最大震度:🔴 5弱級

震央位置:長野県北部

緯度:36.5

經度:137.9

發布時間:2025年04月18日 19:19:42


地震速報

地點:長野県北部

發生時間:2025/04/18
20:19:28

地震規模:🔵 芮氏 5.2

地震深度:🔴 10公里

最大震度:🔴 5弱級

震央位置:長野県北部

緯度:36.5

經度:137.9

發布時間:2025年04月18日 19:19:40


地震速報

地點:長野県北部

發生時間:2025/04/18
20:19:28

地震規模:🟢 芮氏 4.9

地震深度:🔴 10公里

最大震度:🔴 5弱級

震央位置:長野県北部

緯度:36.5

經度:137.9

發布時間:2025年04月18日 19:19:37


地震速報

地點:花蓮縣卓溪鄉

發生時間:2025-04-10
08:31:04

地震規模:🟢 芮氏 4.7

地震深度:🔴 20公里

最大震度:🔵 3級

震央位置:花蓮縣卓溪鄉

緯度:23.36

經度:121.29

發布時間:2025年04月10日 08:31:17


地震速報

地點:宜蘭縣冬山鄉

發生時間:2025-04-09
09:53:31

地震規模:🔵 芮氏 5.2

地震深度:🔵 40公里

最大震度:🔵 3級

震央位置:宜蘭縣冬山鄉

緯度:24.63

經度:121.74

發布時間:2025年04月09日 09:53:44


地震速報

地點:宜蘭縣蘇澳鎮

發生時間:2025-04-09
09:53:31

地震規模:🔵 芮氏 5

地震深度:🔴 30公里

最大震度:🔵 3級

震央位置:宜蘭縣蘇澳鎮

緯度:24.59

經度:121.82

發布時間:2025年04月09日 09:53:42


地震速報

地點:宜蘭縣南澳鄉

發生時間:2025-04-09
09:53:34

地震規模:🟢 芮氏 4.5

地震深度:🔴 10公里

最大震度:🟡 4級

震央位置:宜蘭縣南澳鄉

緯度:24.58

經度:121.79

發布時間:2025年04月09日 09:53:39


地震速報

地點:臺南市六甲區

發生時間:2025-04-05
20:09:04

地震規模:🟢 芮氏 4.7

地震深度:🔴 10公里

最大震度:🟡 4級

震央位置:臺南市六甲區

緯度:23.22

經度:120.4

發布時間:2025年04月05日 20:09:17


地震速報

地點:臺南市六甲區

發生時間:2025-04-05
20:09:03

地震規模:🟢 芮氏 4.6

地震深度:🔴 10公里

最大震度:🟡 4級

震央位置:臺南市六甲區

緯度:23.21

經度:120.38

發布時間:2025年04月05日 20:09:14


地震速報

地點:臺南市六甲區

發生時間:2025-04-03
11:47:01

地震規模:🔵 芮氏 5.5

地震深度:🔴 10公里

最大震度:🔴 5弱級

震央位置:臺南市六甲區

緯度:23.21

經度:120.38

發布時間:2025年04月03日 11:47:08


地震速報

地點:花蓮縣秀林鄉

發生時間:2025-04-03
02:01:35

地震規模:🟢 芮氏 4.9

地震深度:🔵 40公里

最大震度:🔵 3級

震央位置:花蓮縣秀林鄉

緯度:24.07

經度:121.56

發布時間:2025年04月03日 02:01:51


地震速報

地點:花蓮縣秀林鄉

發生時間:2025-04-03
02:01:35

地震規模:🟢 芮氏 4.6

地震深度:🔴 20公里

最大震度:🔵 3級

震央位置:花蓮縣秀林鄉

緯度:24.11

經度:121.59

發布時間:2025年04月03日 02:01:48


地震速報

地點:花蓮縣吉安鄉

發生時間:2025-03-25
09:40:50

地震規模:🔵 芮氏 5.4

地震深度:🔵 40公里

最大震度:🔵 3級

震央位置:花蓮縣吉安鄉

緯度:23.92

經度:121.58

發布時間:2025年03月25日 09:41:02


此為預警系統自動上傳之資訊,準確信息請以交通部中央氣象署為主!

地震速報

地點:高雄市六龜區

發生時間:2025-03-23 12:23:37

地震規模:🟢 芮氏 4.5

地震深度:🔴 10公里

最大震度:🟡 4級

震央位置:高雄市六龜區

緯度:23.13

經度:120.69

發布時間:2025年03月23日 12:23:49


地震速報

地點:花蓮縣近海

發生時間:2025-03-22 20:48:13

地震規模:🟢 芮氏 4.6

地震深度:🔵 40公里

最大震度:🟢 2級

震央位置:花蓮縣近海

緯度:23.66

經度:121.56

發布時間:2025年03月22日 20:48:44


地震速報

地點:花蓮縣近海

發生時間:2025-03-22 20:48:12

地震規模:🟢 芮氏 4.6

地震深度:🔵 40公里

最大震度:🟢 2級

震央位置:花蓮縣近海

緯度:23.65

經度:121.6

發布時間:2025年03月22日 20:48:35


地震速報

地點:花蓮縣近海

發生時間:2025-03-22 20:48:12

地震規模:🟢 芮氏 4.5

地震深度:🔵 40公里

最大震度:🟢 2級

震央位置:花蓮縣近海

緯度:23.7

經度:121.58

發布時間:2025年03月22日 20:48:30

分類: aleart | 發佈留言

幹話語錄2 技術總結筆記

這邊就只是很簡單記錄一下我花了3天從0開始到發布的一些過程。

資料庫

Hostinger

在寫一代的時候一直有一個遺憾就是沒有串聯資料庫,所以第二代就打算讓我的幹話語錄連接到一個線上的資料庫,而在搜尋了半天後發現我的網站所使用的HOSTINGER其實就有架設SQL 資料庫的功能,那架設的方式也很簡單點選畫面中的database你就可以創造一個你自己的資料庫,創建好之後記得要到remote SSL去開放外界連接。

HOSTINGER 頁面
新增資料庫
開放連接

用 python 快速連接

然後呢我就使用Python去連接資料庫並且創一個新的table,裡面會存取 C_ID, 案讚人數,案不喜歡的人數 以及他的詳細資訊。

import pymysql
db = pymysql.connect(host=host, port=port, user=user, passwd=passwd, db=dbname)
cursor = db.cursor()
cursor.execute("""CREATE TABLE tablename (
C_ID INT AUTO_INCREMENT,
WORD TEXT,
LIKE INT,
UNLIKE  INT,
DETAIL TEXT
);""")

db.close()

接著我們就可以使用pandas去讀取我過去的幹話並且上傳上去,

資料庫

Android stduios

我手機呢則是使用Android Studio + kotlin來寫的,基本上呢我不會寫kotlin,所以一切的功能我都會直接查詢網上解答並且直接套用。

SQL

那麼剛開始我第一個要製作的功能,也是最重要的就是連接資料庫,根據我各種查詢以及嘗試,使用了三種不同SQL的Jar庫,終於成功了連上了!

之所以會試著這麼多就是沒有發現到我使用的資料庫是MariaDB,我之前面一直在嘗試使用MYSQL的套件包去連接發現不管怎麼連都連不上,千萬不要像我一樣這麼智障。

那麼連接的方式呢就是要先去下面下載這個jna.jar

https://jar-download.com/?search_box=net%20java%20dev%20jna

把下載的檔案放到libs 裡面之後,在build.gradle裡面增加下面兩行

再來就可以嘗試使用JDBC連接資料庫

var stat:Statement?=null
var resu:ResultSet?=null

private fun connectFuncSQL(): Connection? {
    val connectionProps = Properties()
    connectionProps["user"]     = user
    connectionProps["password"] = password
    Log.d("MYSQL", "Start connection")
    // connect with mariaDb
    try {
        val conn = DriverManager.getConnection(
            "jdbc:mariadb://$host:$port/dbname",connectionProps)
        Log.d("MYSQL", "connection successful")
        return conn
    } catch (ex: SQLException) {
        Log.e("MYSQL", ex.message.toString())
        ex.printStackTrace()
    } catch (ex: Exception) {
        Log.e("MYSQL", ex.message.toString())
        ex.printStackTrace()
    }
    return null
}

如果成功連上了就可以開始下載資料庫上面的資料,我會將下載好資料使用 getSharedPreferences 的存到使用者手機裡。

@SuppressLint("ApplySharedPref")
private fun updateFunc(){
    val conn = connectFuncSQL()
    try {
        stat = conn?.createStatement()
        Log.d("MYSQL", "CreateStatement")

        val gson = Gson()
        val sharedPref = getSharedPreferences("sqlDATA",Context.MODE_PRIVATE) ?: return
        resu = stat?.executeQuery("SELECT * FROM `table_name`;")
                    Log.d("MYSQL", "call table")

        var funcCidArr    : Array<Int>    = arrayOf<Int>()
        var funcWordArr   : Array<String> = arrayOf<String>()
        var funcDetailArr : Array<String> = arrayOf<String>()
        var funcLikeArr   : Array<Int>    = arrayOf<Int>()
        var funcHateArr   : Array<Int>    = arrayOf<Int>()
        runOnUiThread {
                Toast.makeText(this, "下載資料中...",Toast.LENGTH_SHORT).show()
            }
        while(resu?.next() == true){
                val funcCid    :Int    = resu?.getInt("C_Id")!!
                val funcWord   :String = resu?.getString("WORD").toString()
                val funcDetail :String = resu?.getString("DETAIL").toString()
                val funcLike   :Int    = resu?.getInt("LIKE")!!
                val funcHate   :Int    = resu?.getInt("UNKILE")!!

                funcCidArr    += funcCid
                funcWordArr   += funcWord
                funcDetailArr += funcDetail
                funcLikeArr   += funcLike
                funcHateArr   += funcHate

            }
        Log.d("MYSQL", "Writing data")
        val editor = sharedPref.edit()
        editor.putString("funcCidArr", gson.toJson(funcCidArr))
        editor.putString("funcWordArr", gson.toJson(funcWordArr))
        editor.putString("funcDetailArr", gson.toJson(funcDetailArr))
        editor.putString("funcLikeArr", gson.toJson(funcLikeArr))
        editor.putString("funcHateArr", gson.toJson(funcHateArr))
        editor.commit()
        runOnUiThread {
                Toast.makeText(this, "資料已是最新版",Toast.LENGTH_SHORT).show()
            }
        }catch (ex: Exception) {
            Log.e("MYSQL", ex.message.toString())
            ex.printStackTrace()
        }finally {
            conn?.close()
        }

   }

動畫

按鈕標題

為了讓我APP看起來更好看一點,我在許多的地方都加了一些動畫,例如打開程式時按鈕會從左邊依序往右邊跑,還有幹話語錄的”2″會由上而下的滑下來,為了做到這些我們需要寫一個自定義的XML檔

那首先這個是幹話語錄中的”2″ ,其實寫法也很簡單只需要給他初始位置終點位置然後最後是所花的時間

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0%"
        android:fromYDelta="-100%"
        android:duration="1800"/>

    <alpha
        android:fromAlpha="0.3"
        android:toAlpha="1.0"
        android:duration="1800" />
</set>

而至於按鈕地成左至右呢,寫法是一模一樣的,那如果要讓按鈕依序出來走我們只要改變duration的時間就好了。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="-100%"
android:fromYDelta="0%"
android:duration="1000"/>
</set>

頁面切換

頁面切換的時候我也希望有動畫,可以用滑的來切換不同的頁面,這個其實也用寫XML檔來達到,只是我們需要寫兩個,一個負責處理頁面離開,一個負責處理頁面進來。

離開

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0%"
        android:fromYDelta="0%"
        android:toYDelta="100%"
        android:duration="200"/>
</set>

進來

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0%"
        android:fromYDelta="-100%"
        android:toYDelta="0%"
        android:duration="200"/>
</set>

接著只要再切換頁面的時候加以下這一句就好了

override fun onBackPressed() {
        super.onBackPressed();
        overridePendingTransition(R.anim.slide_up_activity_into, R.anim.slide_up_activity_leave);
    }

幹話總覽

在開始前我們先看一下成品

可以看到這是一個清單,將所有的幹話排排列在上面,跟清單不一樣的是,他下面有按讚跟倒讚可以按,還有愛心 !

那要做到這樣子的我們需要一個自定義的adapter,用來告訴他每一行的元素該放什麼字該做什麼事情,我們還要自定義一個Layout,用來告訴他我們的文字按鈕該怎麼排列擺放。

自定義Layout

下面是我自定義的 CustonLayout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="4dp">

    <TextView
        android:id="@+id/tvContact"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:textSize="18sp"
        android:gravity = "center"
        android:clickable="true"
        android:textStyle="bold" />

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        android:layout_gravity = "right"
        android:padding="8dp">

        <com.like.LikeButton
            app:icon_type="thumb"
            app:icon_size="15dp"
            android:id="@+id/btn1"
            android:layout_marginStart="10dp"
            android:layout_width="50dp"
            android:layout_height="50dp"/>

        <TextView
            android:id="@+id/likeText"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginStart="10dp"
            android:textSize="30sp"
            android:layout_marginTop="5dp"
            android:gravity = "center_horizontal" />

        <com.like.LikeButton
            app:icon_type="thumb"
            app:icon_size="15dp"
            app:like_drawable = "@drawable/thumb_unlike"
            app:unlike_drawable="@drawable/thumb_off"
            android:rotation="180"
            android:id="@+id/btn2"
            android:layout_marginStart="20dp"
            app:circle_start_color="#000000"
            app:circle_end_color="#000000"
            android:layout_width="50dp"
            android:layout_height="50dp"/>

        <TextView
            android:id="@+id/hateText"
            android:layout_width="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_height="match_parent"
            android:layout_marginTop="5dp"

            android:textSize="30sp"
            android:gravity = "center_horizontal" />

        <com.like.LikeButton
            app:icon_type="heart"
            app:icon_size="15dp"
            android:id="@+id/btn3"
            android:layout_alignParentRight="true"
            android:layout_marginStart="20dp"
            android:layout_gravity="right"

            android:layout_width="50dp"
            android:layout_height="50dp"/>
        <TextView
            android:id="@+id/textViewBlank"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clickable="true"/>

    </LinearLayout>
</LinearLayout>

可以看到我的按鈕上面還有讚跟愛心,這個呢我是使用 :

https://github.com/jd-alexander/LikeButton

用法也很簡單只要先把以下文字放到setting.gradle

allprojects {
	repositories {
		...
		maven { url "https://jitpack.io" }
	}
}

然後再增加dependencies

接著你就可以在你的Layout裡面使用這個酷酷的讚跟愛心按鈕了

那他的按跟不按呢其實是使用圖片來偽裝成按下去的感覺,所以如果要改按下去後的顏色,必須在外面自己製作一個不同顏色的讚,再去更改它的drawable屬性

      <com.like.LikeButton
            app:icon_type="thumb"
            app:icon_size="15dp"
            app:like_drawable = "@drawable/thumb_unlike"
            app:unlike_drawable="@drawable/thumb_off"
            android:rotation="180"
            android:id="@+id/btn2"
            android:layout_marginStart="20dp"
            app:circle_start_color="#000000"
            app:circle_end_color="#000000"

            android:layout_width="50dp"
            android:layout_height="50dp"/>

自定義 Adapter

自定義一個adapter,我們會需要自己開一個新的class。

class MyAdapter(val context:        Activity    ,
                val funcCidArr:     MutableList<Int>   ,
                val funcWordArr:    MutableList<String>,
                val funcDetailArr:  MutableList<String>,
                var funcLikeArr:    MutableList<Int>   ,
                var funcHateArr:    MutableList<Int>) :  ArrayAdapter<String>(context, R.xml.customlayout,funcWordArr) {

override fun getCount(): Int {
        return funcWordArr.size
    }

override fun getItemId(position: Int): Long {
        return position.toLong()
    }

 @SuppressLint("ResourceType", "ViewHolder", "InflateParams", "SetTextI18n", "CutPasteId")
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
        val layoutInflater = context.layoutInflater
        val rowView = layoutInflater.inflate(R.xml.customlayout, null,true)
        val textPart = rowView.findViewById<TextView>(R.id.tvContact)
        val likeText = rowView.findViewById<TextView>(R.id.likeText)
        val hateText = rowView.findViewById<TextView>(R.id.hateText)

        val blackText = rowView.findViewById<TextView>(R.id.textViewBlank)
        textPart.text = funcCidArr[ position].toString() + ". "+ funcWordArr[position]
        likeText.text = numtoK(funcLikeArr[position])
        hateText.text = numtoK(funcHateArr[position])

        return rowView
}
}

上面是我程式的一個大概架構,因為我的程式中含有廣告投放,還有伺服器的連接(按讚跟倒讚),還有全部按鈕的listener,所以我這邊就把一些比較重要的show出來。

複製文字

複製文字的部分我是建一個listener,如果按如果使用者按了文字,就會將文字複製到他的剪貼簿裡。

textPart.setOnClickListener{view->
    if (funcDetailArr[position] != "None") {
                var thieDetail = funcDetailArr[position]
                if ("政治" in thieDetail) {
                    thieDetail = "\t\t"+ thieDetail.subSequence(3, thieDetail.length).toString()
                }
                showAlert(thieDetail)
            }else{
                showAlert("此幹話沒有出處")
            }

            val clipboard: ClipboardManager =
                context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
            val clip: ClipData = ClipData.newPlainText("func_say", funcWordArr[position])
            clipboard.setPrimaryClip(clip)
//            toast?.cancel()
//            toast = Toast.makeText(context,"已複製進剪貼簿", Toast.LENGTH_SHORT)
//            toast?.setGravity(Gravity.BOTTOM,0,0)
//            toast?.show()
        }

showAlert

showalert是負責處理當使用者按下文字之後跳出的彈出視窗

fun showAlert(string: String){

    val dialog: AlertDialog = object : AlertDialog(context, R.style.CustomDialog) {
        override fun dispatchTouchEvent(event: MotionEvent): Boolean {
            dismiss()
            return false

        }
    }
    dialog.setMessage(string)
    dialog.setCancelable(true)
    dialog.show()
    
}

其中 dispatchTouchEvent 是用來處理使用者只要點任一的地方都會將這個彈出視窗關閉。

按讚功能

按讚功能,按下去的瞬間會將手機端的按讚人數加一,並且同時跟資料庫講這個數字要加一,所以這些按讚並不是實時更新的,而是會等到下次你打開APP時才會更新。

"""UPDATE `table_name` SET $Label = $Label $operator 1 WHERE C_ID = $id;"""

Adapter 使用

Adapter 的使用也其實很簡單只要告訴ListView就好了

val mList: ListView = findViewById<ListView>(R.id.listview)
val customAdapter = MyAdapter(
            this,
            funcCidArr,
            funcWordArr,
            funcDetailArr,
            funcLikeArr,
            funcHateArr
        )
mList.adapter = customAdapter

上架

上架期實也很簡單,先打包,打包的時候他會請你做一個金鑰那其實按照他的步驟做就可以了,打包完之後呢,我們需要開通Google Play Console的開發者資格需要25美金,但只要開通一次就好了。

先打包

點擊建立應用程式

然後把該打的打,把勾的勾,該填的填,按下確認鍵就好了。

左邊滑到底有一個應用程式內容,記得要將裡面的東西都完成,才有辦法發佈你的APP。

而這個隱私權政策,則會需要你提供一個隱私權網址,這個網址是我用以下https://app.privacypolicies.com/wizard/privacy-policy 生成的

最後在正式版裡面建立一個新版本,上傳完之後,等待Google審核,審核成功,你的APP就成功發佈出去了。

分類: Uncategorized | 發佈留言

天常M抽卡模擬

https://www.4gamers.com.tw/news/detail/50223/dinter-lineage-m-dispute

最近實況主丁特在天堂m中砸了200萬抽鑽抽紫布,前前後後進行475次製作,但只成功11次,比官方所宣布的10%還低上許多,丁特認為這樣的製作機率跟官方公告的「所有商城機率與韓版一致」有所落差,進而向消保會提出申訴,要求遊戲橘子針對機率一事給出回應。

這到底是丁特臉黑還是遊戲橘子是有問題的呢? 所以我決定針對抽卡這件事情來做模擬。

這邊我使用的程式是 Julia + Jupyter

首先是抽卡的程式,times 決定了抽卡的次數(例如這裡就是475次),prob就是抽到紫布的機率(這裡是10%),在這裡抽卡的方式就是從0~1隨機選一個數字,如果這數字小於prob代表抽到了,如果大於則沒有,而最後他會給我們這次抽卡的機率 (例如丁特就是 11/475,抽卡475次成功11次)。

測試幾次看看

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-16.png

可以看到機率大部分都在10%附近。

測試1千萬次看看

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-17.png

這張圖是抽卡機率的分布圖(Histogram),x軸是機率,y軸是機率的密度分布(次數除以寬度),基本上密度越高,代表出現的次數越多,你可以看到10%附近的數值是最大的。

通常這種分佈,我們都可以使用一個高斯分布去做fitting(在某些情況下有可能是卜瓦松分布),μ代表的是平均,σ則是標準差,你可以看到 μ 的數值非常的接近10%。

既然已經有抽卡的分布曲線,我們可以去思考 2.3% 是在幾個標準差之外。

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-18.png

答案是 5.57個標準差。

在統計學中, 1.645 個標準差代表著是 95% C.L(confidence level 信心水準),所以基本上 5.57 比 1.645 多了3.895個標準差,可以說發生的機率非常的小,那實際上 5.57個標準差對應的機率是多少呢 ?

如果要計算機率,我們可以使用累積分布函數(Cumulative Distribution Function),我們可以經由這個函數知道要在這個模型中獲得 “2.3%”成功率的機率是多少。

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-19.png

可以看到,如果使用平均是10,標準差是1.38的模型下,成功率是2.3%的機率大概是

1億分之一!!!!


https://forum.gamer.com.tw/C.php?bsn=25908&snA=48280

後來也有人說台服的機率是 5% 而不是 10%。

OK阿,我們就在做一次模擬。

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-20.png

可以看到分布的平均確實移動到5%囉。

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-21.png

約為2.7個標準差

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-22.png

在平均是5%的情況下,成功率為2.3%的機率是 千分之3,你說這機率高嗎?不高,除非是衰到不行的人,不然這個機率其實是不太可能的,2.7 個標準差 也比 1.645 個標準差高了1.1個標準差了,可以說是不太可能了。

結論

在官方公布機率為10%,475次中只成功了11次的機率,2.3%確實有點低,若以5%的成功率來看, 475次中只成功了11次的機率仍然為 0.3%,機率還是非常的低,低於 95% 信心標準許多,如果沒有其他外力因素,可以認為與官方公告的機率不一致。

分類: Uncategorized | 標籤: , | 發佈留言

How to develop your python package ?

pip is a software package management system written in the Python computer programming language. It can install and manage software packages, with a simple command “pip”, you can get access to more then one hundred thousand of cool libraries, here I am going to show you how to upload your own code to PyPi and able to download with pip.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-1024x467.png

There are few step for you to develop your own package.

  1. Prepare your code.
  2. Create a PyPi account.
  3. Prepare the files PyPi needs.
  4. upload it to PyPi
  5. pip install your package.
  6. Upload it to github

Code

In here, I want to upload the class I wrote for research in the institute, If you are interested, It is a code for calculate the limit of TASEH(Taiwan Axion Search Experiment Haloscope).

https://github.com/OuYangMinOa/ou_Axion_limit

This code have two class in two different File

  • class Glimit in GLimit.py
  • class analyse in Analy.py

PyPi account

Create a PyPi account for us to upload our package,

https://pypi.org/account/register/

Prepare the files PyPi needs.

Now, I wish my classes are all in a same package call ou_Axion_limit

# my expectation
from ou_Axion_limit import Glimit
from ou_Axion_limit import analyse 

so first create a folder (Glimit), this folder will content every thing PyPi needs, and next create a floder name with your package name (ou_Axion_limit ), inside the folder you just created, and throw you python code inside.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-3.png

__init__.py

Creata a file __init__.py inside the ou_Axion_limit

This file allows you to determine the classes that users can directly call and use, so in my cases, I can just import my class with following way.

# my expectation
from ou_Axion_limit import Glimit
from ou_Axion_limit import analyse

setup.py

The setup.py file contains the information that PyPi needed , such as its name, description, current version, etc. Copy and paste the following code and replace the string with the matching content:

CHANGELOG.txt

The changelog of you package

LICENSE.txt

Use this file to define all license details, you can use your own license, however, I will use MIT license

MANIFEST.in

Just a file use to include everything

README.md

A markdown file can make people understand your package better.

Upload it to PyPi

Everything is prepared, we can now upload it to PyPi.

First, goto you folder open cmd and tpye:

python setup.py sdist
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-4-1024x613.png

If you see UserWarning: Unknown distribution option: ‘install_requires’, just ignore it.

It should create a file in dist folder

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-6.png

Next, we need twine for upload the package

pip install twine

Then, run the following command:

twine upload dist/{Generated_file}

You will be asked to provide your username and password. Provide your credentials you used to register with PyPi.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-7-1024x160.png

Congratulations, you successfully upload your packge to the PyPi, goto the website it gives you to see your package online.

Install your package

pip install YOURPACKAGENAME
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-8-1024x487.png

Upload to github

For the fist time using git, we need to create a ssh-key to connect to github

ssh-keygen
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-10-1024x223.png
I create a ssh key in C:\Users\USER/.ssh/

It did exist.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-11.png

Show the content

type filename
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-12.png

Copy everything and add in to your account.

https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account

Create your repository

Then upload your project.

git init
git add .
git commit -n "first upload"
git remote add origin git@github:{name/project}
git push -u origin master

If you want to upload new code later, you only need to type the following commands.

git add .
git commit -m "v1.0.0"
git push
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-15.png
分類: Uncategorized | 標籤: , | 發佈留言

SSH to a Linux by ngrok and manipulate existing terminal

In http://ouyangminwei.com/index.php/2021/09/18/build-a-discrod-music-bot/, I created a discord bot and develop on a Raspberry pi, but now there are two problems

  1. The network used by is a floating IP.
  2. Even if I can SSH in, I still can’t see or close the discord bot since It is on a terminal.

Ngrok

After searching a little bit, I found that ngrok can solve the first problem perfectly.

Ngrok is a cross-platform application that exposes local server ports to the Internet, so I can expose my raspberry pi to the internet.

First, sign up and download ngrok on this website https://ngrok.com/.

Because it runs on the Raspberry Pi, so download the 32-Bit ARM’s version

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-39.png

After you download the ngrok upzip it.

unzip ngrok-stable-linux-arm.zip.zip
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-38.png

Login the ngrok, yo can get your Authtoken

./ngrok authtoken
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-37-1024x556.png

And you are ready to fire it up.

./ngrok tcp 22
解析Ngrok 曲折的攻擊程序– 資安趨勢部落格

remember the Ip it gives you

tcp://2.tcp.ngrok.io:14139

Then you can SSH to it

ssh username@2.tcp.ngrok.io -p14139
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-41.png

Screen

In order to access to running terminal, we can use “screen” to achieve it

sudo apt-get install screen
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-40.png

This can create a new screen call “bot”

screen -mS bot

And you can start your server, for here, I will test will python httpserver

python -m SimpleHTTPServer
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-42.png

Now, the server is running, CTRL+A and then CTRL+D to detach the screen.

screen -ls

Show the screen

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-43.png

I have a “test.root” over here, Try to see if I can download this file via this python http server

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-44.png
wget 127.0.0.1:8000/test.root
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-45-1024x183.png

Every works well, the server it still running.

screen -x bot

Back to the terminal

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-46.png

So now, I can access the Raspberry pi with ngrok and use screen to control the terimal.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-47.png
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-48-1024x157.png

分類: Uncategorized | 標籤: , | 發佈留言

Build a discrod music Bot

https://www.theverge.com/2021/8/24/22640024/youtube-discord-groovy-music-bot-closure

這張圖片的 alt 屬性值為空,它的檔案名稱為 99nShh7.png

https://www.theverge.com/2021/9/12/22669502/youtube-discord-rythm-music-bot-closure

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-20.png

Recently, youtube kill’s some of the most used music robots on discord, and other public ones may also be asked to be shut down, so today let us make our discord bot!!!

Step 1 : Create your bot

Goto this website https://discord.com/developers/applications, I have already created one, click the “New Application” button.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-22-1024x480.png

And name your bot.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-23.png

If you create the bot successfully, you should see it appear on your main page, just click it.

Go and copy your APPLICATION ID, and fill in this link:

https://discordapp.com/oauth2/authorize?&client_id=xxxxxxxxxxxxx&scope=bot&permissions=8 # replace xxxxx with you Id
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-25-1024x570.png

with this link, you can invite it to your channel.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-27-1024x568.png

Step 2 : authorization token

Go to the “bot” and click “copy”, this will give you your Token

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-24-1024x615.png

Step 3 : Start coding

Here, I’m will build the bot with python in windows, so you will need python on your computer

open cmd and download discord’s API

pip install discord.py
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-28-1024x436.png

Open sublime and paste the following code to start a simple bot

save and open with cmd.

we know this is not enough…, we need a music bot, not a ping-pong idiot…, next, I will speed up and point out the difficulties and solutions I encountered.

Step 4 : A music bot

A normal music bot should be able to play youtube music and have the following functions.

  • play : Add a song or a playlist to the Queqe and invite the bot to the voice channel
  • pause : pause
  • skip : skip current song
  • list : show the playlist
  • clear : clear the playlist
  • loop : loop the playlist
  • leave : leave the voice channel

we define severals function that we want our music bot have.

Step 5 : Play a music

Let’s see how others play music

with this code we can understand how everything works, first, we need FFmpeg.

https://www.ffmpeg.org/download.html

when a user types a play command, the code will get its message channel (ctx), and get the Author’s voice channel (channel), then with “channel.connect()” to connect the bot to the voice channel.

channel = ctx.message.author.voice.channel #this line will get the user's voice channel
voice =  await channel.connect()   # connect to the user's voice channel

voice.play(discord.FFmpegPCMAudio(song_path), after = lambda e: somefuciton())
# play the music ,  'after' will run when the song is finish.

Step 6 : Create a class

you may ask: why do we need a class?

If our robot is to serve different servers, it means that there will be different ctx, channel, and voice, we also need to queue the songs of each channel, so the best choice is to create a class, each class serves a server.

class MusicBot:
    def __init__(self,channel, voice , ctx):
        self.music_msg = None       # The message for current music
        self.loop      = False      # Enable loop or not
        self.live      = True       # Kill this Musicbot if self.live = False
        self.channel   = channel    # Voice channel
        self.channelid = channel.id # This Music serves channel Id
        self.floder    = "music"    # Folder to store music
        self.ctx       = ctx        # ctx
        self.voice     = voice      # voice client
        self.queqed    = []         # music queqed for play
        self.passed    = []         # Played music
        self.state     = 0          # 0:not playing , 1:playing , 2:pause
        self.rejoin_c  = 0          # rejoin every 20 songs
        self.dont_stop = 0          # will play untill dont_stop = 3 to check again

so I define a class that needs channel, voice, and ctx as input.

Step 7 : Download youtube music

we know we can use youtube_dl to download the music.

pip install youtube-dl
self.ytl  = {
                'format': '249/250/251',
                "outtmpl" : f"{self.floder}/{this_song_name}",
                'noplaylist': False
} 
with youtube_dl.YoutubeDL(self.ytl) as ydl:
    ydl.download([this_song_url])

But I think it is difficult to manage which songs the user has queued and youtube_dl will download the entire playlist before I play the music, of course, I can use a thread, But it is too complex.

So, I will use a youtube data API to grab the playlist’s songs and download one at a time and play it. the following is the youtube API code, however, I will not explain this code since our main character is a discord music bot.

with this API, I can write a function to add music to the queue

def add_thread(self,url):
        print("[*] Adding rest in this thread ... ")
        output = grab_playlist(url,50)
        for each in output[10:50]:
            self.queqed.append(each)
        print("[*] Adding Thread done.")
        print(f"[*] Qeuqed {len(self.queqed)} songs ->",self.channelid)

async def add(self,url):
        print("[*]", url)
        if ("list" in url):
            print("[*] Adding a play list in", self.channelid)
            output = grab_playlist(url,10)
            for each in output[0:10]:
                self.queqed.append(each)
            threading.Thread(target = self.add_thread, args=(url,)).start()

        else:
            print("[*] Adding a single video in", self.channelid)
            self.queqed.append((get_title(url),url))

        print("\n[*]",self.channelid,"-> Enqueued :",len(self.queqed), "the remaining songs will continue add in the background")

        if (self.state ==0):
            self._next()  #  the play music function

add function can handle a video or a playlist, if it is a video, just add it directly, but if it is a playlist, I will first grab the first 10 videos, the rest will throw in a thread to continue, this is to speed up the time to start the song, otherwise, the user will feel impatient.

Play command

Users_class  = {}

@client.command(brief="Play music (My wonderful singing )")
async def play(ctx, url : str):
    if not ctx.message.author.voice:
        await ctx.send('you are not connected to a voice channel')
        return
    else:
        channel = ctx.message.author.voice.channel

    if (channel.id in Users_class):
        if ctx.guild.voice_client not in client.voice_clients:
            await Users_class[channel.id].kill() # kill the music player
            del Users_class[channel.id]
            print("[*] rejoin the voice channel")
            voice =  await channel.connect()
            MB = MusicBot(channel, voice , ctx)
            Users_class[channel.id] = MB
        await Users_class[channel.id].add(url)
    else:
        voice =  await channel.connect()
        MB = MusicBot(channel, voice , ctx)
        print(f"[*] creating Class id : {id(MB)} for serving channel",channel.id)
        Users_class[channel.id] = MB
        await MB.add(url)

In play command, I will create the music bot class and save the class to a dictionary by using channel id as the key.

# play next music
async def _next(self):

    if (len(self.queqed) == 0):

        if (self.loop): # refill the music queqed for play with Played music
            self.queqed = self.passed
            self.passed = []
        else:
            self.state = 0
            return    

    self.state = 1    # State = 1 -> start playing                 
    self.this_song  = self.queqed.pop(0) # get the song 
    self.passed.append(self.this_song)   # Store in history music

    print("[*] playing  :", self.this_song[0],"in", self.channelid)
    this_song_url   = self.this_song[1]
    this_song_name  = self.this_song[0]


    #  This for is beacuse the youtube_dl will change the filename
    #  when handling special char
    for each_char in ['"', "'", ":", "|"]:
        if (each_char in this_song_name):
            this_song_name = this_song_name.replace(each_char,"#")

    # youtube_dl will create a floder for \ and /
    for each_char in ["\\", "/"]:
        if (each_char in this_song_name):
            this_song_name = this_song_name.replace(each_char,"")

    song_path  = os.path.join(self.floder , this_song_name)

    # download youtube music
    if ( not os.path.isfile(song_path)):
        self.ytl  = {
            'format': '249/250/251',
            "outtmpl" : f"{self.floder}/{this_song_name}",
            'noplaylist': False,
        } 
        self.dowloading = await self.ctx.send(f'... Downloading {this_song_name}')
        print("[*] downloading ->", this_song_name,"\n")
        with youtube_dl.YoutubeDL(self.ytl) as ydl:
            ydl.download([this_song_url])
        print("\n[*] ------------ download successful ------------")

    # Check if bot is in the voice channel
    if self.ctx.guild.voice_client not in client.voice_clients:
        print("[*] get kicked from",self.channelid)
        return

    # send currently playing music
    self.music_msg = await self.ctx.send(f':musical_note:  Now playing ({len(self.passed)}/{len(self.queqed)+len(self.passed)}) : {this_song_name} :musical_note:')

    

    ######################   I will explian this later
    FFMPEG_OPTS = {
        'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 
        'options': '-vn'
        }
           self.rejoin_c += 1
   if (self.rejoin_c == 10):
         self.voice.play(
                discord.FFmpegPCMAudio(song_path, **FFMPEG_OPTS), 
                after=lambda e: print("[*] reconnecting the ffmpeg , error : ",e)
                )
        self.rejoin_c = 0

        await asyncio.sleep(5)
   ######################

    # play the music, call _endsong when the song finish
    self.voice.play(
            discord.FFmpegPCMAudio(song_path), 
            after = lambda e: asyncio.run_coroutine_threadsafe(self._endsong(e), client.loop)
            )

    
    # check every three song 
    self.dont_stop +=1
    if (self.dont_stop > 3):
        client.loop.create_task(self.check())


This is a quite long function, but most of them are actually pretty easy to understand, except the FFMPEG_OPTS part, this part is to solve a known problem.

https://support.discord.com/hc/fr/articles/360035010351–Known-Issue-Music-Bots-Not-Playing-Music-From-Certain-Sources

The robot will suddenly have no sound while playing a song, I often encounter this problem when I am testing,

To solve this problem, you have to use that FFMPEG_OPTS variable. It will reconnect the bot to the source.

But there is a bug, I’m not sure if it is my problem or how, my FFmpeg will show “reconnect” Option not found, I still trying to fix it.

Step 8 : Complete every thing

This is my MusicBot with all the functions we want.

Then try to run the bot and see if everything works well.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-29.png

Step 9 : Develop on server

Now try to put this robot on a server, you can use Heroku or something else, for me , I will put it on the Raspberry Pi

這張圖片的 alt 屬性值為空,它的檔案名稱為 dwzZ6giNPkhGbmumGqcT1PEHXUM6vGh4-KH6_DN6_W43eWgqiB8m7VIbTTmKkWdaTemgixIxJypIOTqtoiBrsGcG7nSMipYBg5k67A-0w4ft3BqpL_HRGePRdXz01S3euElu2lizy2EdfJV1iNwZ5fAT34kkylwHbI3wvwEoJZTZB8bKwSYWvw1YKUJ_qflXvbUVhjiBiQ6MBd95jHfg45hgcuPJSYYmw9UaGIPFTeUGzem7IT7AttdDi15Y3EeRxWd-ljVAurT4kifjwQ73j807SordCQY8M4Znj-xWB-22sS0CsqHsD3-t8QtbDNM8q95K0Y-vAM_X1ZaYIMs1lphaauKtIzhYAYtBsa_uSLwxEWVpFwUz6e5bPm2_mJnIylvd2h3W_qbUH9iltC3B9NVDO1_UBmOTqeS3l7q6l0lUjbGoe7ogyNwILFaFku_Cb9Je9lmhwqkkjvNfk7vqRjgzwJdU9kCWqsR8PBtPXIpCfuap6rxJ0OLem-89UQBtSetp5tZSvwbtvhXEMlRLHzOWBnKJ5QB7gZbng7n_MN84NyudLsFQZF4bRIJ3zLG-zTZFjzCmg3AtlZsdc_yGH8itL_B7C_693uM31TZvk-F47ZM5oD37Q5bP1hMBwe6_ntYX1vnKpKfwHa-5Ro0JXL1Nr_I7FY16IpCu2FGJ9JVQav8-XMGOpQhrbyMyeAihJSVTiy8GRk8lYFU3eAzCJrQ=w678-h903-no

So, first I will use python to open a simple webserver on windows to send my bot’s code to Raspberry Pi .

python -m http.server
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-30.png

Cause I’m connect to the same route, so I can use the local internet to send my bot, use Ip config to get you computer’s local IP

ipconfig
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-35.png

Just wget the ip: port/file and you can grab the file

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-31-1024x535.png

Set up your environment and run the server

pip install google-api-python-client
pip install discord.py
pip install youtube_dl
sudo apt install ffmpeg
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-32-1024x268.png

Congratulations, you successfully made your own robot !!!

分類: Uncategorized | 標籤: , | 發佈留言

How to solve pip not found

This is because pip has not been added to the path

So first, follow my step to find python’s floder.

這張圖片的 alt 屬性值為空,它的檔案名稱為 2021-09-16-edited.png
這張圖片的 alt 屬性值為空,它的檔案名稱為 2021-09-16-5-1024x610.png
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-13-1024x607.png

so this is my python floders

C:\Users\wesle\AppData\Local\Programs\Python\Python37     # This is python
C:\Users\wesle\AppData\Local\Programs\Python\Python37\Scripts   # This is pip

Then open the setting of environment variables, for English users, try ‘env’.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-14-1024x841.png
Screenshot of Start Search
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-15.png
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-16.png

Add the floder path we Just get

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-17.png

Open cmd and type pip, your path is set up right now.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-18-1024x513.png
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-19-1024x303.png
分類: Uncategorized | 標籤: , | 發佈留言

How to download python in windows and run with Jupiter and sublime.

why python? Python is a programming language that lets you work more quickly and integrate your systems more effectively. <– (Introduction on the official website), and also my favorite language.

Here, I will show how to download python and run with Jupiter and Sublime

Goto https://www.python.org/, click download, windows

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-1024x599.png

Choose you version, for me, I will download python 3.7 (64bit)

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-1-1024x720.png

open the installer, remember to Check Add Python3.* to PATH, and Just click Install Now.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-3.png

Download Sublime, https://www.sublimetext.com/

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-4-1024x448.png

Click next and install it.

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-5.png

Fisrt program

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-6.png

CTRL + SHIFT + P type python, and save the file and with .py

CTRL + B to run the code

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-7-1024x873.png

Install Jupyter

Windows + R and type CMD

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-8.png

If the python’s installation is correct, pip should show something, if you did not see it, try to fix it with following url.

http://ouyangminwei.com/index.php/2021/09/16/how-to-solve-pip-not-found/

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-9-1024x590.png

Pip is a python package manager, you can type ‘pip install notebook’ to install Jupyter.

pip install notebook

And type ‘jupyter notebook’ to start the jupyter notebook

jupyter notebook
這張圖片的 alt 屬性值為空,它的檔案名稱為 image-10-1024x349.png

It will open a web page, press new and select python3 to start a new script

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-11-1024x382.png

And you start your coding

這張圖片的 alt 屬性值為空,它的檔案名稱為 image-12-1024x260.png

Commonly used Jupyter shortcut keys

Function short key
Show all the shortcutH
command modeEsc
edit modeEnter
Save notebooks
Create a new cell abovea
Create a new cell belowb
delete celldd
undoz
run the current cell, select belowShift + Enter
run selected cellsCtrl + Enter
Alt + Enterrun the current cell, insert below
分類: Uncategorized | 標籤: , | 發佈留言

Lyto-Different-Color Plug-in

Lyto-Different-Color It’s basically a simple color recognition game, when I saw this game for the first time, I had the idea of using python and opencv to write this game assistant.

so first we need python, If you don’t know how to download python, you can go to this page

http://ouyangminwei.com/index.php/2021/09/13/how-to-download-python-in-windows/

We need opencv and pillow for image processing, numpy for array and mss for screenshot.

pip install numpy
pip install opencv-python
pip install pillow
pip install mss==2.0.22

Next, we need to get the game screen

# select screen range
mon = {'top': 179, 'left': 706, 'width': 1214-706, 'height': 1080-179}

sct = mss()
sct.get_pixels(mon)

while True:
    # transform the screen information to numpy array
    img = np.array(Image.frombytes('RGB', (sct.width, sct.height), sct.image))

    # show the screen via opencv
    cv2.imshow('test', np.array(img))
    if cv2.waitKey(25) & 0xFF == ord('q'):
        cv2.destroyAllWindows()
        break

I want the program to find the ball in the screen first, and then get the color of the center of the ball, so that it can find the ball with a different color, there is a funcction in opencv, call HoughCircles, it can help us to find the circle in the image.

The usage is very simple, just throw the image into the function, well… and some parameters

# transform the image into gray scale to speed up the calculation
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Houghcircles
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT,1.2,10)

# circles will be something likes 
# [(x1, y1, r1),(x1, y1, r1) ...]
# where x1 y1 is the position of circle's center and r is the radius

with circle’s center, we can get correspondging color

color = []
# loop every circle
for (x, y, r) in circles:
    # store the circle's center color into 'colors'
    colors.append(list(img[y,x,:]))

I write a function to find the different one, (there must be a better way of writing, but I only use a very dumb way)

def find_different(arr):
    for num,i in enumerate(arr):
        if (arr.count(i)==1):
            return num
    else:
        return 0

At this point, we are done. All we need is to put them all together and draw the balls of different colors.

Let’s take a look at the results

Github : https://github.com/OuYangMinOa/Lyto-Different-Color

分類: Uncategorized | 標籤: , , | 發佈留言