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 格式不然會讀取不到,硬碟的話可能要注意供電是否足夠。

2017年10月29日 星期日

購買 淘寶 注意事項,什麼是普貨、特貨,走私,那些台灣禁運

購買 淘寶 注意事項,什麼是普貨、特貨,禁運、敏感特貨,走私,那些台灣禁運


前言

不小心把特貨當普貨送,緊張了一下爬了不少文大致稍微弄懂了整個流程
PTT上的風向,特貨=走私=犯法,不能說錯但我覺得描述得不是很清楚,可以是對的也可以是錯的,大致講了一下會發生什麼事情。
一般來說買淘寶等於進口!這一點要注意不是單純購物,這算進口了,只是因為小量國家給你方便。
進口有許多細則扣除毒品那些誰都知道會出事的還有”藥事法”,會坐牢的重罰的;藥事法裡面會不小心踩到的坑有隱形眼鏡和電子菸油要注意。

特貨等於走私嗎?

要看走私的定義怎麼樣,大概列舉幾個
  1. 違反航空規定 (電池有爆炸疑慮)
  2. 違反輸入國家進口法律規定
  3. 違反輸出國家出口法律規定
  4. 明顯公認違禁品 (毒品、槍械)
  5. 海關主觀認定 (模糊地帶)
掏寶的特貨只有保證解決 3,他的解決方法就是上海跟深圳禁止輸出品項就跑香港,繞路合法化輸出法規,才不會被中國海關扣走 (在台灣被扣走又是另一回事)。
就只有這樣,還是可能會違法的,責任也是算你頭上的。
如果只差中國出口不合法那你跑特貨就是完全合法。
其他跑特貨集運商就是能幫你就幫你”越大咖”的就比較給力這樣,常見的被海關扣貨或是退貨他負責在送一次,或是被海關沒收的話賠錢給你。
有特貨跟沒特貨,(依我看大家討論的資料)在法規上是一樣的到機場只分能運跟不能運,所以法規上沒有特貨,這是集運商處理流程度一樣收的錢不一樣。集運商怎麼送的這部分就自行想像了,相信人家是專業的~
衝關是很危險的,你可以想像成就是改品項名稱拚人品,被抓到自己要扛刑責;有看到有些人說特貨就是衝關,有可能確實是這樣你的品項就不會過你還能怎麼辦XDD。不過~多付錢給轉運商當特貨處理,還是有很大的區別的。
如果中國放行了,到台灣還是也有可能出狀況
  • 超過$3000被抽到補稅
  • 抽查補委任書
  • 違反當地輸入法規
好的集運商特貨可能會幫你服務這些,或給你一些幫助意見
這邊沒過也有可能被退回去中國的
運了明顯有問題的東西就自行承擔責任了
詳細禁運清單可以參考這篇淘寶寫的各國禁運品項:
https://www.taobao.com/go/chn/transit/interdict/cht.php
其中 2違反輸入國家進口法律規定 可能會收到法院傳票,這點要特別當心有可能前面全部都合法安全到台灣,到台灣來查之後發現你違法;尤其是藥事法,品項有隱形眼鏡和電子菸油這不能買,被查到會被起訴QuQ
另外貨還在集運商手上絕對不要跟對方硬著幹歐,會被整得是你!;PPT有一篇集運寫一個很奇杷的項目,然後那含禁運品的貨被台灣海關抽查到的…。

流程

一般大致有這幾個流程

中國運輸:

  1. 賣家出貨
  2. 中國境內運輸
  3. 轉運商收貨 (品項敏感詞直接拒收)
  4. 轉運商出貨到機場
  5. 機場檢查
  6. 機場清關(飛往台灣)

台灣運輸

  1. 機場抽檢或全檢 (補稅、補委任書或是違法會卡在這裡)
  2. 機場清關
  3. 黑貓接手(或是超商物流)
淘寶的官方集運抓比較嚴而且不送特貨,一般如果你的國家有禁止淘寶也會自動幫你屏蔽顯示不能購買。
如果還是寄出去了,比如說汪汪私訊跟買家買的,到了集運商之後如果品項有寫出來比如說電池,集運商會直接退貨。
被集運商退是最好的狀況,如果有保運險貨會自動回到賣家那裏,沒有的話要請賣家去連略一下國內段快遞取回貨品,然後你們再自己商量回來的運費怎麼處理;貨到賣家手裡後看要退貨還是重新走特貨,賣家給退貨的話可以拿回商品+國外段運輸的錢。
怎麼退看這邊可以參考這篇站內文,要分2次退:
http://charlottehong.blogspot.com/2017/10/blog-post_26.html
在機場卡關的話就給集運商處理了,可能會要求委任書,卡在台灣海關是比較麻煩的要自己處理。
卡關想別亂丟貨麻煩處理好別不理不採,你會害死一票人
  1. 獲得海關重點關注名單(可能以後你進口都是最高規格檢查)
  2. 集運商被海關黑單+罰款賠錢+重點關注名單
  3. 跟你搭同一台飛機的貨,大家一起卡關1~2個月
嗯…終於知道為什麼等這麼久了,拜託別當雷包。
特貨被台灣海關抽檢了就不要爭執說我這是特貨怎樣的,沒這回事唷。
這句話的翻譯是:海關我運了可能不合法的東西。

關於私運與官方集運

其實就只是差在官方集運少點幾下,你實際去全球淘寶看,你會會看到你下了兩筆訂單,一筆是商品價格一筆是轉運商品價格(運費也算做一件商品的意思)。
私運其實只是多購買一個商品而已,流程是這樣的。
  1. 先問你想要的集運商能不能運(旺旺密)
  2. 你把東西買下填集運商地址
  3. 把商品淘寶貨單給集運(旺旺密),集運收到之後會報重量給你
  4. 還有想買的東西重複1~3
  5. 看多重去集運商的淘寶,一次拍下相應的運費商品
  6. 等收貨
這邊如果貨單搞錯也是有可能發生特貨當普貨寄出的狀況歐!如果要同時由同一家運普貨與特貨建議在跟集運商確認一次貨單。