2017年11月20日 星期一

[原始碼] C / C++ 旋轉 任意角度的圖片

[原始碼] C / C++ 如何旋轉圖片

首先會用到線性插補可以參考這一篇
https://charlottehong.blogspot.tw/2017/11/bilinear.html

旋轉公式

跟上一篇差不多意思線性插補指的是for迴圈去跑新圖,然後縮放對應回去原圖的座標,因為縮放得之後的點可能比較多或比較少,對應回去就會在非整數整數位置,這時候就看比例給數值。
旋轉也是一樣意思,旋轉過後的的點會正好不在點上,旋轉有兩個公式;另外這兩個公式其實就是仿射轉換的運算過程,只是差別在沒有

公式1 - for跑原圖映射到新圖

一般看到的就是給輸入原圖得出新圖的座標
公式如下:
x' = x * cos(theta) - sin(theta) * y
y' = x * sin(theta) + cos(theta) * y
這樣做會有一種情況是,原圖的點轉換到新圖之後是小數點,把小數點四捨五入之後得出同一個點,導致新圖有某幾點沒被跑到就沒值有空洞。

公式2 - for跑新圖映射回原圖

比較正確的作法是for去跑新圖座標,輸入新圖座標得到舊圖的座標(小數點),然後再利用線性插補補出來。
公式如下:
float r_rot = (j)*sin_t + (i)*cos_t;  // 原圖X座標
float c_rot = (j)*cos_t - (i)*sin_t;  // 原圖Y座標
其中 (j, i) 就是新圖的 (y, x) 座標,sin_t 與 cos_t 則是預先算好的數值,因為計算三角函數很費時,建議提出到for迴圈之外先算好,如果再for回圈內就要不斷的重複運算同一個角度浪費效能。
// 預算
float cos_t = cosf(sita*(float)(M_PI/180));
float sin_t = sinf(sita*(float)(M_PI/180));

原點在哪裡

需要注意的是上圖的公式會以圖的左上角 (0, 0) 作為定點轉換,如果需要從中心點旋轉或是任點轉需要轉換一下座標
假如現在要以 (10, 10) 為中心旋轉,那麼在旋轉前你需要先把for迴圈扣調這個範圍,也就是從 (j, i) 要從負號 -10 開始跑,經過旋轉公式計算出來之後還需要將原本的 10 加回來,這樣就可以獲得正確的位置了。
此外獲得正確位置之後還有一個問題就是旋轉後的圖比旋轉前的圖還要大,勢必會計算出超出原圖範圍的點,跑一個if過濾負號與超出原圖長寬的點即可。

範例代碼

只有給主要函式,有部分是已經寫好的函式庫,我有空再補上完整能跑的
(懶懶的~還沒補上又真的有需要然後看不懂可以直接留言問吧~我會看到的)
ImgRaw rotateImg(const ImgRaw& sou, size_t x, size_t y, float radius, float sita) {
    ImgRaw test = sou;
    //test.at2d(x, y) = 1;
    Draw::draw_arrow(test, y, x, radius, sita, 1);
    test.bmp("rotate_test.bmp");
    // 新圖長寬半徑
    float maxRadius = radius;
    int rotH = floor(maxRadius);
    int rotW = floor(maxRadius);
    ImgRaw rotate(rotW*2, rotH*2, 8);
    // 預算
    sita *= -1; // 把新圖轉回0度
    float cos_t = cosf(sita*(float)(M_PI/180));  
    float sin_t = sinf(sita*(float)(M_PI/180));
    // 跑新圖
    for (int j = -rotH; j < rotH; j++) {
        for (int i = -rotW; i < rotW; i++) {
            // 輸入新圖座標返回舊圖座標(已0, 0為圓心旋轉)
            float r_rot = (j)*sin_t + (i)*cos_t; // 原圖X座標
            float c_rot = (j)*cos_t - (i)*sin_t; // 原圖Y座標
            // 矯正回指定位置
            float rbin = r_rot + x; 
            float cbin = c_rot + y;
            // 去除原圖外的點
            if (cbin < sou.height - 1 and cbin > 0) {
                if (rbin < sou.width - 1 and rbin > 0) {
                    // 雙線姓插補
                    rotate.at2d(j+rotH, i+rotW) = test.atBilinear(cbin, rbin); 
                }
            }
        }
    }
    return rotate;
}
ImgRaw 帶有圖像的
  1. 一維陣列
  2. 長寬
  3. 位元數(彩圖灰圖)
  4. 一些方法
線性插補建議寫成一個讀取的,這樣規劃結構還不錯用 atBilinear(y, x) 要是你輸入的點不正好在點上,比如說 (0.5, 0.5) 就算出這個位置的插補值回傳。
這個函式的功能主要為輸入
  1. 點(y, x)
  2. 角度
  3. 半徑
輸出為
  1. 畫出點與半徑的箭頭
  2. 把點與角度轉正向右
  3. 擷取點為中心半徑的距離
其實就是SIFT算法的一小部分,轉正主角度時用到到的

公式1 的用處

另外公式1也是完全沒有用處,依照適當的情況做選擇;比如說在做sift描述特徵點這一步的時候,需要把圖片旋轉然後取值。
這時候公式1就是轉完之後得知小數點(y, x)位置,sift描述特徵點這裡的重點是原圖有n點n點都要統計到,而不在意準確轉為整數後的位置。
上面所提的是因為我們要輸出來看,要有準確的整數(y, x)位置,公式1轉完之後就變成小數點很難切開,不知道這一點到底是要往上下左右哪邊取整數,極端情況可能還有同一格有兩點。

2017年11月19日 星期日

sim卡轉接卡 空卡插進去拔不出來 卡住

sim卡轉接卡 空卡插進去拔不出來 卡住

今天在測試舊手機Z1的時候為了避免弄丟轉卡,不小心把空轉卡插進去了,然後就拔不出來了!
拿了保特瓶切了一些下來試著把她弄出來都弄不出來,後來想到說,他可以退出一點點,那我把方向對準把sim卡從那個縫隙塞進去不就好了
對準塞進去之後就可以正常抽出來了,結束了這場鬧劇QuQ

轉卡放在老家,下次有回去再補圖,總而言就是卡座拉一點點出來然後sim卡從那個縫隙塞進去就可以了,塞的時候方向要對準。

2017年11月16日 星期四

[原始碼] C / C++ 線性插補 bilinear 與注意事項

[原始碼] C / C++ 線性插補 bilinear 與注意事項

linear 意思就是依照比例去補數值,可以參考維基百科也說得很詳細
https://www.wikiwand.com/en/Bilinear_interpolation
簡單來說就是現在有兩個點 A=0 與 B=10
現在 A 的座標是 0,B的座標1,那個我要取出AB這個位置的點該怎麼辦呢
就是依照比例取因為比較靠近A所以分到的A比較多,離B比較遠分到的就比較少
現在讓我們來計算算法如下
AB = A*dx2 + B*dx1
大致上就這樣而已,如果是二維空間那個就要算三次這個公式意思如下
有ABCD四個點,把他當成2條一維就是AB跟CD,分別先算出他們中間的點
然後再把AB跟CD這兩個點在做一次運算就可以算出中間那個點了
float AB = A*dx2 + B*dx1;
float CD = C*dx2 + D*dx1;
float X  = AB*dy2 + CD*dy1;
linear是一維的意思,bilinear就是二維的意思,在上去還有三維都是差不多意思推倒

實作代碼

實作的時候要注意幾點

獲取鄰點

獲取鄰近點的時候不能用+1或-1,假設現在的點的 0 和 1,需要獲取的點是 1,先使用 floor 無條件捨去獲得1在加1就變成2了;正確的來說因為正好就落在點上,就直接把AB都帶那個點就可以了算出來是正確的
使用 ceil() 這樣就可以正確獲得了

獲取比例

這時候也是一樣,不過情況倒是反過來了,一定要用1去減,否則如果出現兩個一樣的點,比例兩邊都是0。
/*****************************************************************
Name : 
Date : 2017/11/16
By   : CharlotteHonG
Final: 2017/11/16
*****************************************************************/
#include <iostream>
#include <cmath>
using namespace std;

float linear(int* arr, float pos){
    // 獲取鄰點(不能用 1+)
    size_t c0 = floor(pos);
    size_t c1 = ceil(pos);
    // 獲取比例(只能用 1-)
    float dx1 = pos - c0;
    float dx2 = 1 - dx1;
    // 乘出比例(要交叉)
    float X = arr[c0]*dx2 + arr[c1]*dx1;
    return X;
}
float bilinear(int* arr, size_t w, float y, float x){
    // 獲取鄰點(不能用 1+, 選中結尾時 x0=x1=W-1 才是對的)
    size_t x0 = floor(x);
    size_t x1 = ceil(x);
    size_t y0 = floor(y);
    size_t y1 = ceil(y);
    // 獲取比例(只能用 1-, 選中結尾時 1:0 才是對的)
    float dx1 = x - x0;
    float dx2 = 1 - dx1;
    float dy1 = y - y0;
    float dy2 = 1 - dy1;
    // 獲取點
    const float& A = arr[y0*w + x0];
    const float& B = arr[y0*w + x1];
    const float& C = arr[y1*w + x0];
    const float& D = arr[y1*w + x1];
    // 乘出比例(要交叉)
    float AB = A*dx2 + B*dx1;
    float CD = C*dx2 + D*dx1;
    float X = AB*dy2 + CD*dy1;
    return X;
}
//================================================================
int main(int argc, char const *argv[]){
    int arr[] = {0, 10};
    cout << linear(arr, 0.1f) << endl;
    int arr2[] = {0, 0, 10, 10};
    cout << bilinear(arr2, 2, 0.1f, 0.1f) << endl;
    return 0;
}
//================================================================

縮放比例

以下 dst 指的是處理過後的圖,src 指的是原圖。
縮小比例的時候,要注意新圖的點要向中間對齊,不然最右邊那一整排跟最下面一種排會沒有被計算到。
// 縮小向中間對齊
r=srcW/dstW;
double srcX = (dstX+r) * ((double)srcW/dstW) - r;
double srcY = (dstY+r) * ((double)srcH/dstH) - r;
原理其實就是原本會向左上角也就是原點對齊,然後再加上偏差值讓他向中間對齊。
不過這樣向中間對齊有個缺陷,邊緣的向素值會丟失,數值丟失還算小問題,比較大的問題是連帶圖片會稍微放大,假設你做個10次放大縮小,你會發現可能原圖邊緣一圈整個不見了。
// 縮小的倍率
double r1W = ((double)src.width )/(dst.width );
double r1H = ((double)src.height)/(dst.height);

// 縮小時候的誤差
double deviW = ((src.width-1.0)  - (dst.width -1.0)*(r1W)) /dst.width;
double deviH = ((src.height-1.0) - (dst.height-1.0)*(r1H)) /dst.height;

// 縮小保持邊緣對齊
srcX = i*(r1W+deviW);
srcY = j*(r1H+deviH);
公式從原本的向原點對齊,會產生一定誤差,把這個誤差分攤給每個位置。
放大比例的時候則是要注意最外圍的一圈正好要疊在點上面
// 放大保持邊緣對齊
double srcX = (dstX)* ((width-1.0) / (newW-1.0));
double srcY = (dstY)* ((height-1.0) / (newH-1.0));
另外上述算是為了表達沒有加上(double)轉型修飾,是因為有 +1.0 會自動隱式轉型,所以不用補。但是如果是 (int)/(double) 就要補,這個是後面的會隱式轉型成 (int)。

參考

2017年11月14日 星期二

微信支付只綁定台灣信用卡,使用權限與金額上限

微信支付只綁定台灣信用卡,使用權限與金額上限

法規

2018.11更新

目前看起來是已經恢復了,可以使用超過1000人民幣~哪時候會再封鎖不一定。


使用範圍

測試依據:2017.10站長大陸旅遊實測
微信支付只綁定台灣信用卡能夠到大陸支付嗎?
答案是部分可以的,支付有分兩種,一種是你掃對方的QR碼然後支付,這種的有出租車線上網購等等,單綁台灣信用卡即可支付使用,需要注意上限額度就好。
另一種是對方掃你的QR碼,例如大陸境內百貨公司,這個只有單綁台灣信用卡是不能支付的,推測綁定大陸銀行卡才行。
另一種比較特別的台灣境內的超商也有支持微信支付,這種支付除了要大陸銀行卡之外還要通過大陸身分證驗證。可以去全家掃掃看會傳簡訊跟你說要過身分證驗證XD

不能在百貨公司支付怎麼辦?

支付寶已經開放給台灣使用了,只要有台胞證即可,可以參考這篇站內文
台灣 支付寶 實名驗證圖文步驟
這個權限幾乎等同於大陸了,在百貨公司實測給店家掃碼可以支付;只差不能在台灣支付,一樣需要大陸身分證驗證。

2017年11月13日 星期一

C / C++ 函式傳遞二維陣列 範例與解說

C / C++ 函式傳遞二維陣列 範例與解說

一維陣列的傳遞

一維陣列常見的方法是這樣傳遞的

void fun(int* p){...}

int arr[10]={};
fun(arr);

這時候編譯器會自動將 型態::int[10] 轉成 型態::int* 然後成功的傳遞
但是這裡有個但書,只有最高維度可以自動轉換或計算,剩下的皆要手動指定

舉個例子來說明

// 錯誤
int arr[][] = {{0,1},{2,3}};
// 正確
int arr[][2] = {{0,1},{2,3}};

編譯器只會自動補上 最左邊的[] ,只有最左邊可以留空

在函式上二維的傳遞方式也是一樣的邏輯,想傳遞這個二維可以改成這樣

// 寫法1
void fun(int p[][2]){...}
// 寫法2 (等價於第一種,看個人習慣選擇即可)
void fun(int (*p)[2]){...}

這樣就可以正常傳遞了,只是有個缺點二維的長度被限制了。



不定長度的二維傳遞 - 手動轉型

那如果要傳遞不定長的二維就必須使用指標的指標 int** 來傳遞
不過這樣的用法沒辦法直接從int(*)[n] 轉型,必須手動轉型

如果想要自動轉型成 int** 反推一下就要使用 int* 的陣列來轉型

  • int 的陣列是一個陣列裡面放著一堆int
  • int* 的陣列是一個陣列裡面放著一堆int*

就是說二維陣列,可以想像成有一個一維陣列裡面放著一堆一維陣列

// 宣告二維陣列
int arr1[2][2] = {{1,2},{3,4}};

// 取出一維陣列位址
int* a1 = arr1[0];
int* a2 = arr1[1];

// 把一維地址寫進陣列裡
int* p1[2];
p1[0] = a1; 
p1[1] = a2;

現在p1可以自動轉型成 int** 了,可以將它傳入了 int** 的函式內了

會弄得這麼麻煩是因為維度的長度,本身就屬於型態的一部分。陣列長度10跟陣列長度11的差別就好像 int 跟 char 的差別一樣完全是不一樣的東西

而編譯器只會幫你處理最高維度的自動計算而已,所以二維以上不定長度的傳遞在C語言上比較棘手。C++的話有樣板可以自動處理就沒這個困擾了。

範例

/*****************************************************************************
Name : 
Date : 2018/06/13
By   : CharlotteHonG
Final: 2018/06/13
*****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#define WIDTH 2
#define HEIGHT 3

void fun(int** p) {
    for (int j = 0; j < HEIGHT; ++j) {
        for (int i = 0; i < WIDTH; ++i) {
            printf("%d, ", p[j][i]);
        } printf("\n");
    }
}

int main(int argc, char **argv) {
    int arr[HEIGHT][WIDTH] = {};
    int* p[HEIGHT];

    for (int j = 0; j < HEIGHT; ++j) {
        p[j] = arr[j];
        for (int i = 0; i < WIDTH; ++i) {
            arr[j][i] = 1;
        }
    }

    fun(p);
    return 0;
}




如何傳遞陣列的長寬

這是轉成指標之後的缺點,他將會遺失陣列的長度資訊,常見辦法

  • 讓函式多一個參數傳入
  • 用陣列的第一個數值來當作長度

C++的樣板可以解決這個問題,寫法如下

template<size_t N, size_t N2>
void fun(int (&arr)[N][N2]) {...};

如此一來就可以完整的傳入鎮列了,在函式內 N1 及 N2 就是傳入的二維長度,一維或是三維以上只要調整N的數量對應即可。

2017年11月12日 星期日

Visual Studio 2017 stdio.h winres.h atlbase.h 找不到

Visual Studio 2017 stdio.h winres.h atlbase.h 找不到

更新之後出現了
Cannot open include file: 'atlstr.h' Cannot open include file: 'winres.h'
後來發現連 #include <stdio.h> 這個也會找不到
解決辦法重新安裝相關套件
  • 安裝 ATL支援,就可以有 atlstr.h 了
  • WindoiwsSDK 上面也有更新版的,那個版本沒有winres.h的樣子

Android SONY 手機 Z、XZ系列 OTG 接上後毫無反應

Android SONY 手機 Z系列 OTG 接上後沒有反應

設定方法

其實只是因為新版本的系統改成要你同意之後才偵測的到
設定內 -> 裝置連線 -> 下方 USB連線 -> 偵測USB裝置
就可以偵測到 OTG 的裝置了。

其他

主要是因為後來的防水機都沒有做防水蓋了,考量到下水的時候可能會導致USB短路燒毀零件才作成這個樣子的,至於與電腦連接因為是電腦端供電而不是手機端供電就沒問題可以直接接上去就好。
如果OTG是連接記憶卡或USB隨身碟,格式要注意超過32GB的話只接受 exFAT 格式不然會讀取不到,硬碟的話可能要注意供電是否足夠。