2017年11月26日 星期日

Windows系統 MBR 轉 GPT(EFI) 無損資料可開機不須重灌系統

Windows系統 MBR 轉 GPT(EFI) 無損資料可開機不須重灌系統

重新寫一篇比較簡潔的文章,有影片操作過程,建議直接看這篇
- [圖/影] 升級 Windsows11 不用重灌 MBR 轉 GPT
   https://charlottehong.blogspot.com/2022/02/windsows11-mbr-gpt.html
- 如果是反過來的 GPT -> MBR 請參考:

快速轉換 mbr2gpt

這個是微軟內建的轉換工具,可以安心使用。
這個有條件限制,必須是三個主分區以內才能轉,其他任何狀況都會出問題。
操作前要注意主機板有沒有支持EFI啟動。(英特爾第二代開始H61以後都有)
使用方式比較簡單,不用第二台電腦,直接在需要轉換的電腦操作下面步驟:
  1. 開啟 RE or PE 中的 命令提示符
    (按住SIFT->再按重新開機->疑難排解->命令提示字元)
  2. 重啟之後會需要選擇帳號,並輸入密碼再來會開啟終端機
  3. 輸入 mbr2gpt /validate 檢查是否可轉
  4. 輸入 mbr2gpt /convert 轉換完畢
  5. 重新啟動

關於 [1] 還有另一種是PE,光碟在安裝的時候按SIFT+F6,會跳出命令提示字。
關於 [5] 一般有支持EFI啟動的主機板,預設狀態都是同時支援EFI與MBR的;可以先重開看看,不能再去設置調整,不知道怎麼調整就直接重設也可以~

上圖找不到命令提示符
是因為救援系統RE分區被砍掉了,可以從以下站內文重新開啟
https://charlottehong.blogspot.tw/2018/02/windows-re.html
有遇過 [2] 的時候明明有系統管理員帳號卻是顯示空白的狀況;
遇到的話從光碟或USB開啟吧~或參考下面手動設置說明。
詳細的 mbr2gpt.exe 說明可以參考微軟網站:
https://docs.microsoft.com/zh-cn/windows/deployment/mbr-to-gpt









以下是舊文,上面介紹的工具比較簡單也無風險,但是有一些條件限制。
如果遇到條件限制轉不過去才需要做下面的步驟。


前言

大概補一下前言知識GPT跟MBR是磁碟格式,以前的主機板內的韌體叫做BIOS他是16位元的,後來出了EFI大概一年就淘汰換UEFI。
BIOS基於16位元的關係磁碟最多就只能切2T,所以你如果裝1顆3T的硬碟,要麼你切1T+2T要麼就只能換GPT。
MBR 最多切4個分區,擴展分區全部和算算一個,如果要在同一顆硬碟安裝4個以上的系統需要一些技巧。
UEFI可以支援傳統MBR模式開機,這個選項叫做CSM開啟之後也可以讀到,不過今年開始新版的UEFI拿掉了CSM,XP已經成為歷史沒辦法在新主機板安裝了。
UEFI多了安全開機,系統必須有被他檢測過才可以開機,這導致linux安裝稍微麻煩了點(是能裝的)。
UEFI 多了快速啟動開機可以神速開到桌面,不過啟動條件比較多需要 1.系統本身支持並啟用 2.安裝系統的時候選用EFI安裝 3.主機板支持並且啟用該功能 4.其他周邊硬件:如顯示卡。
此外IDE跟AHCI是另一回事,這個也可以無損轉要補驅動但是建議重灌保險;轉了之後比較容易發生衝突問題變得怪怪的。

開機引導

UEFI的開機管理比較特別一點,可以由BIOS裡面決定要開哪一個系統,對就是選系統(也就是EFI內的哪一個啟動文件),有趣的是這個選擇完畢開進去之後也會啟動grub或bcd,然後又可以再選一次系統。
以往MBR的硬碟BIOS只能決定開哪一個硬碟的grub或bcd無權決定開誰,之後再由grub或bcd決定開哪一個系統(這時候也可以開別的硬碟的系統)。
Linux比較特別的EFI啟動的時候會有兩個EFI文件檔,就是BIOS裡面會看到兩個系統,一個是沒過驗證的grub這個如果安全啟動沒開不能開機,另一個是有過安全啟動驗證的,如果你沒關閉安全啟動記得要選否則預設是grub會黑屏不能開機。
某些筆電設計成必須設置密碼才能選擇載入其他EFI文件選擇第二個系統開機,詳細可以蒐一下參考站內文。


確認你有滿足條件

確定一下你有滿足上面所提的條件,一般就是進入BIOS看一下有沒有一項 Secure Boot安全啟動,這個最好找,有的話就可以啟動EFI了。
顯示卡在自己爬文一下有沒有支持,沒支持也可以拉只是 超級啟動 不能用。

需要一個獨立系統

需要一個獨立系統,這裡建議把硬碟拆下來裝在第二台台電腦上並安裝以下軟體;其他方法可以在USB安裝WindowsPE用USB啟動去做,因為有些複雜就不介紹了,這裡會用第二台電腦做說明。
需要軟體diskgenius:http://www.diskgenius.cn/download.php

轉換流程

記得備份重要資料,避免操作失誤資料遺失
分區如果不見了左上角有個搜尋分割可以救回來

MBR -> GPT

首先擴展分區無法保存資料,請先轉為主分區或備份再重新寫入
某些情況會導致不能讓擴展分區轉為主分區,MBR最多切4個分區(擴展分區則是全部合只算1個),所以如果你切了3個主分區+2個或以上的擴展分區(1個好像也不行),那就沒辦法在同一顆無損轉了,因為轉完之後就超出4個了。
這種情況處理方式建議就拿另一顆來複製資料了。
1.有擴展分區要先處理掉只留主分區
2.直接轉換成GPT -> 然後左上角保存
3.C曹壓縮讓前面空出500M以上的區間
並將其格式調整成EFI,然後保存
再來是新增盤符,需要重開機才可以新增不然會跳錯
(如果是非原生sata的主機板可以熱拔插,拔掉sata線重接就好)
4.下面指令重新寫入EFI引導文件
這時候系統是沒有開機引導的,必須手動
  • 修復EFI開機引導
  • 重建BCD選單
打入下面 cmd 指令即可,也可以寫成bat批次檔,做批處理
# C = 系統槽, Z = EFI槽
bcdboot C:\windows /f UEFI /s Z:\ /l zh-tw

選用::壓縮系統

在效能影響不大的情況下,有效壓縮系統空間
這個隨時都可以[壓縮\解壓縮],開進系統再執行即可。
Windows10 如何啟用 壓縮系統 compactos 霍夫曼壓縮:
http://charlottehong.blogspot.com/2018/03/windows10-compactos.html

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的數量對應即可。