格雷码+相移法既可以减少格雷码的编码位数,加快解码速度,也可以弥补单纯的相移法和格雷码法的对不连续位置难以重建的缺点。

操作过程如下:

采用格雷码与相移结合的时间编码方法,具体的编码方法为:首先向被测物投射一系列格雷码黑白条纹图案,其中具有相同编码
的区域作为一个编码周期,然后再采用四步相移法,依次投射四幅相移图案,使得每个编码区域被进一步连续细分。但投射的格雷码图案和相移图案必须满足如下关系:格雷码图案的最小周期为相移图案周期的 4 倍,理论上格雷码周期边界与相移周期边界要严格对应。

格雷码的编码算法为:

1,由已有的前面的格雷码生成后面的格雷码

假设已经生成了k位格雷码,那么k+1位格雷码的生成方式为

(1) 按序在k位格雷码前插入一位0,生成一组编码

(2)按逆序在k位格雷码前插入一位1,生成另外一组编码

(3)两组编码合起来就是k+1位格雷码。

如下例:

已有2位格雷码:00, 01, 11, 10,要生成3位格雷码,采用此算法:

(1)按序在各码前插入0,生成 000,001, 011,010;

(2)按逆序在各码前插入1,生成 110,111, 101,100;

(3)将两组编码组合起来:000, 001, 011, 010, 110, 111, 101, 100,为3位格雷码。

另外一种算法与此算法类似,不同的是插入的位是在格雷码的后面:

对于k位格雷码,在各格雷码后面分别插入0, 1 或 1, 0,生成两个编码,所有插入完成后组合起来的编码为k+1位格雷码。

如已有2位格雷码:00,01,11,10,生成3位格雷码,采用此算法:

(1)在00编码后面分别插入0,1,生成 000, 001;

(2)在01编码后面分别插入1,0,生成 011, 010;

(3)在11编码后面分别插入0,1,生成 110, 111;

(4)在10编码后面分别插入1,0,生成 101,100;

(5)将生成的编码组合起来:000, 001, 011, 010, 110, 111, 101, 100,为3位格雷码。

对格雷码编码测试代码如下(有详细的注释):

#include <iostream>
#include <vector>
#include <string>
#include <time.h>void GrayCodeOne(int num);
void GrayCodeTwo(int num);using namespace std;int main()
{int count;cout << "Input Code Number:";  cin >> count;//输入一个整形数cout << "Produce Gray Code using method 1" << endl;clock_t beginOne = clock();GrayCodeOne(count);clock_t endOne = clock();cout << "Gray Code First Method using time: " << (endOne - beginOne) << endl;cout << "Produce Gray Code using method 2" << endl;clock_t beginTwo = clock();GrayCodeTwo(count);clock_t endTwo = clock();cout << "Gray Code Second Method using time: " << (endTwo - beginTwo) << endl;return 0;
}// Method to produce gray code using method inserting 0 in front of old gray code by positive
//方法1中的已有的格雷码序列,正向前面一位加0,逆向前面一位加1
// and inserting 1 in front of old gray code by nagative.
void GrayCodeOne(int num)
{if (num < 1){cout << "Error input Integer" << endl;return;}vector<string> codeVec;  //定义一个字符串类型的向量 用来存放编码序列int cIdx = 1;for (; cIdx <= num; cIdx++){//如果num=1,则for循环只有一次,codeVec大小就为2,里面放的是0,1//开始向量为空,for循环的第一次,该向量大小一定小于2,直接存放0,1,//存放一次结束后向量大小为2,则进入下面的else里if (codeVec.size() < 2)  {codeVec.push_back("0");codeVec.push_back("1");}else{//如果codeVec大小大于等于2个,则开始在,0和1上进行下面操作vector<string> tranVec;  tranVec.resize(2 * codeVec.size());  //定义一个中转向量,确定tranVec的大小为原有元素向量的2倍int tranIdx = 0;//迭代器对向量进行遍历vector<string>::iterator codeIter = codeVec.begin();for (; codeIter != codeVec.end(); codeIter++){//在正序输出的每个元素的前面加0string str = "0";str.append(*codeIter);tranVec[tranIdx++] = str;//将加0后的元素重新存到转移向量}vector<string>::reverse_iterator rCodeIter = codeVec.rbegin();  //逆序迭代器,rbegin指向容器c的最后一个元素for (; rCodeIter != codeVec.rend(); rCodeIter++){//在逆序输出的每个元素的前面加1string str = "1";str.append(*rCodeIter);tranVec[tranIdx++] = str; }codeVec.assign(tranVec.begin(), tranVec.end());//将区间tranVec[first,last)的元素全部赋值到当前的codeVec容器中//就是当前依序所编码的格雷码}}//vector<string>::iterator vecIter = codeVec.begin();//for (; vecIter != codeVec.end(); vecIter++)//{//    cout << *vecIter << endl;//}return;
}// Method to produce gray code using method inserting 0/1 in the back of first gray code
// then inserting 1/0 in the back of next gray code.
void GrayCodeTwo(int num)
{if (num < 1){cout << "Input error Integer" << endl;return;}vector<string> codeVec;int cIdx = 1;for (; cIdx <= num; cIdx++){if (codeVec.size() < 2){codeVec.push_back("0");codeVec.push_back("1");}else{vector<string> tranVec;int tranIdx = 0;int cIdx = codeVec.size();tranVec.resize(2 * cIdx);for (int vIdx = 0; vIdx < cIdx; vIdx++){string str = codeVec[vIdx];if (0 == (vIdx % 2)){string str0 = str;str0.append("0");tranVec[tranIdx++] = str0;string str1 = str;str1.append("1");tranVec[tranIdx++] = str1;}else{string str0 = str;str0.append("1");tranVec[tranIdx++] = str0;string str1 = str;str1.append("0");tranVec[tranIdx++] = str1;}}codeVec.assign(tranVec.begin(), tranVec.end());}}//vector<string>::iterator vecIter = codeVec.begin();//for (; vecIter != codeVec.end(); vecIter++)//{//  cout << *vecIter << endl;//}return;
}

2、由二进制转换为格雷码

这里的二进制数是整型数,直接对整型数进行操作获得格雷码。

static unsigned int binaryToGray(unsigned int num) {return (num >> 1) ^ num;
}

unsigned int型数据到格雷码的转换,最高可转换32位自然二进制码

int型数据到格雷码的转换,最高可转换31位自然二进制码。

格雷码图案的生成:

上面测试程序为一维格雷码的生成程序,下面为二维的格雷码图案的生成:

格雷码图案的生成是在一维格雷生成的原理上将一位编码扩展到一列的编码。

该图从上往下为格雷码递增级数,每一级为一幅编码投影图案的黑白分布图样,从左至右,编码值从“0000000”按照格雷码编码依次递增至“1000000”。

投影图案设计

采用与投影图案 μ、ν  轴两个方向相同的两个系列格雷码编码,其中每个方向最高级别格雷码宽度为 1 个像素,该设计方法具体分如下 2步:

1)确定投影图案分辨率,假设水平 μ 方向上编码条纹设为 m 级,竖直 ν 方向上编码条纹设为 n 级,则投影图案在 μ 方向上像素个数为 2^m,ν 方向上像素个数为 2^n,即投影图案分辨率是 2^m×2^n。  
2)投影图案分辨率确定后,使用格雷码编码方法,设计两个方向的编码投影图案,共生成 μ 方向上 m 张格雷码的编码图案,每张格雷码级别依次递增,和 ν 方向上 n 张格雷码的编码图案,每张格雷码级别依次递增。

设计代码(有注释)如下:

//设置的水平方向的条纹数是10,垂直方向的条纹数是6
static unsigned int Nhorz = 10;
static unsigned int Nvert = 6;#ifndef log2f
#define log2f(x) (log(x)/log(2.0))
#endifusing namespace std;/** The purpose of this function is to convert an unsigned* binary number to reflected binary Gray code.** The operator >> is shift right. The operator ^ is exclusive or.* Source: http://en.wikipedia.org/wiki/Gray_code*/
static unsigned int binaryToGray(unsigned int num) {return (num >> 1) ^ num;
}/** From Wikipedia: http://en.wikipedia.org/wiki/Gray_code* The purpose of this function is to convert a reflected binary* Gray code number to a binary number.
*/
static unsigned grayToBinary(unsigned num, unsigned numBits)
{for (unsigned shift = 1; shift < numBits; shift <<= 1){num ^= num >> shift;}return num;
}/** Function takes the decimal number* Function takes the Nth bit (1 to 31)* Return the value of Nth bit from decimal* Source: http://icfun.blogspot.com/2009/04/get-n-th-bit-value-of-any-integer.html*/
static int get_bit(int decimal, int N){// Shifting the 1 for N-1 bits   int constant = 1 << (N-1);   //将00000001左移N-1位// If the bit is set, return 1if( decimal & constant ){return 1;}// If the bit is not set, return 0return 0;
}static inline int powi(int num, unsigned int exponent){// NOT EQUIVALENT TO pow()if(exponent == 0)return 1;float res = num;for(unsigned int i=0; i<exponent-1; i++)res *= num;return res;
}// Encoder   参数为:行 列 方向
EncoderGrayCode::EncoderGrayCode(unsigned int _screenCols, unsigned int _screenRows, CodecDir _dir) : Encoder(_screenCols, _screenRows, _dir){N = 2;  //初始值为2// Set total pattern numberif(dir & CodecDirHorizontal)this->N += Nhorz;         //如果编码为水平方向,N为2+Nhorz  N=12if(dir & CodecDirVertical)    //如果编码为水平方向,N为2+Nvert  N=8this->N += Nvert;// Encode every pixel column     //编码每一像素列  NbitsHor=10int NbitsHorz = ceilf(log2f((float)screenCols));  //先将列数转变为浮点数,再将列数取log2f运算,将float类型的小数去掉,然后进一,// Number of vertical encoding patterns  同上 针对每一行  9.58去掉小数进1为10,即NbitsVert=10int NbitsVert = ceilf(log2f((float)screenRows));//patterns的前两个元素为patternOn和patternOffcv::Mat patternOn(1, 1, CV_8UC3, cv::Scalar(0));  //定义一个1x1的patternOn矩阵patternOn.at<cv::Vec3b>(0,0) = cv::Vec3b(255, 255, 255);   //矩阵的(0,0)点定义为(255, 255, 255)白色patterns.push_back(patternOn); cv::Mat patternOff(1, 1, CV_8UC3, cv::Scalar(0));  //定义一个1x1的patternOff矩阵patterns.push_back(patternOff);if(dir & CodecDirHorizontal){// Precompute horizontally encoding patternsfor(unsigned int p=0; p<Nhorz; p++){cv::Mat patternP(1, screenCols, CV_8UC3);   //定义一个1行xscreenCols列的patternP矩阵// Loop through columns in first row       从第一行遍历所有列for(unsigned int j=0; j<screenCols; j++){unsigned int jGray = binaryToGray(j);   //将二进制转换为格雷码// Amplitude of channels   设置每个通道的灰度值float amp = get_bit(jGray, NbitsHorz-p);patternP.at<cv::Vec3b>(0,j) = cv::Vec3b(255.0*amp,255.0*amp,255.0*amp);}patterns.push_back(patternP);}}if(dir & CodecDirVertical){// Precompute vertical encoding patterns     for(unsigned int p=0; p<Nvert; p++){cv::Mat patternP(screenRows, 1, CV_8UC3);    //定义一个screenRows行x1列的patternP矩阵// Loop through rows in first column       从第一列遍历所有行for(unsigned int i=0; i<screenRows; i++){unsigned int iGray = binaryToGray(i);// Amplitude of channelsfloat amp = get_bit(iGray, NbitsVert-p);patternP.at<cv::Vec3b>(i,0) = cv::Vec3b(255.0*amp,255.0*amp,255.0*amp);}patterns.push_back(patternP);}}#if 0for(unsigned int i=0; i<patterns.size(); i++){std::stringstream fileNameStream;fileNameStream << "pattern_" << std::setw(2) << std::setfill('0') << i << ".bmp";cv::imwrite(fileNameStream.str(), patterns[i]);}#endif
}

在上述条纹生成的代码中,以横向的条纹图案,重要的步骤为

(1)设置两个一个像素大小的矩阵

(2)先分配一个空矩阵用来存放生成的pattern,从01两位格雷码开始开始

内层for循环是对第一行的所有像素点按照编码的格雷码值进行灰度赋值,即

如果P=0,则为第1张条纹图,用第一行每一列数(从0到1023)转换的格雷码值的 第10 位值去填充该列像素灰度值,

这时,Mat矩阵中存放的是水平排布(竖直条纹)条纹第一行的一维行条纹。

需要说明的是,这里的生成思路很巧妙

首先是直接对图像的宽度进行格雷码转换,可以直接对任意的图像尺寸进行编码;

同时对格雷码值取位操作,所取的位数正好分割了尺寸,每一行被分割成N=1024/2^(NbitsHorz-P)等份,每一份像素个数为

1024/N。

如果要生成一张图像,则需要将第一行的条纹进行复制,opencv里的repeat函数,矩阵拷贝的时候指定按x/y方向重复

这里将第一行的条纹在Y方向按图像的行数重复,在X方向上重复为1次(不做操作),即可得到完整的一张格雷码图案。

(3)二进制数转为一维格雷码代码

(4)这个函数主要是将整型数所转换的一维格雷码的第N位对应的0,1状态取出

最后生成了横向条纹竖直分布的格雷码并存在了patterns中,纵向分布的类似。

Matlab代码:

function [P,offset] = graycode(width,height)% % Define height and width of screen.
% width  = 1024;
% height = 768;% Generate Gray codes for vertical and horizontal stripe patterns.
% See: http://en.wikipedia.org/wiki/Gray_code
P = cell(2,1); %胞元
offset = zeros(2,1);
for j = 1:2% Allocate storage for Gray code stripe pattern.if j == 1N = ceil(log2(width));offset(j) = floor((2^N-width)/2);elseN = ceil(log2(height));offset(j) = floor((2^N-height)/2);endP{j} = zeros(height,width,N,'uint8');% Generate N-bit Gray code sequence.B = zeros(2^N,N,'uint8');B_char = dec2bin(0:2^N-1);  %十进制转二进制for i = 1:NB(:,i) = str2num(B_char(:,i));endG = zeros(2^N,N,'uint8');G(:,1) = B(:,1);for i = 2:NG(:,i) = xor(B(:,i-1),B(:,i));end% Store Gray code stripe pattern.  B = repmat(A,m,n)  B 由 m×n 个 A 平铺而成if j ==1 for i = 1:NP{j}(:,:,i) = repmat(G((1:width)+offset(j),i)',height,1);endelsefor i = 1:NP{j}(:,:,i) = repmat(G((1:height)+offset(j),i),1,width);endendend

matlab程序的思路:

此时,格雷码图案完全生成,根据格雷码图案生成相移条纹图案,格雷码的最小周期为T=log2(width),相移条纹的周期为T/4即可,具体生成过程在另一篇博文详细说明,这里不再重复。

格雷码编码程序实例3(C++),对应于解码程序。

// Generate Gray codes.     格雷码在生成时 通过sl_scan_cols和sl_scan_rows的状态来判断生成行还是列条纹图案 int generateGrayCodes(int width, int height,     //以1024 * 768为例IplImage**& gray_codes,    int& n_cols, int& n_rows,int& col_shift, int& row_shift, bool sl_scan_cols, bool sl_scan_rows){// Determine number of required codes and row/column offsets.if(sl_scan_cols){                            //判断是生成哪种条纹n_cols = (int)ceil(log2(width));         //n_cols  格雷码的张数,即格雷码的最小宽度   n_cols=10 ;ceil是求大于该数的整数col_shift = (int)floor((pow(2.0,n_cols)-width)/2);       //列偏移量  比宽度多出来的?  col_shift=0;foolr是求小于该数的整数}else{n_cols = 0;col_shift = 0;}if(sl_scan_rows){n_rows = (int)ceil(log2(height));      //ceil(9.58) = 10 =n_rowsrow_shift = (int)floor((pow(2.0,n_rows)-height)/2);      //行偏移量 row_shift = floor((1024-768)/2)=128}else{n_rows = 0;row_shift = 0;}  // Allocate Gray codes.   分配内存  gray_codes = new IplImage* [n_cols+n_rows+1];   //为什么要加1?加一张白色图案for(int i=0; i<(n_cols+n_rows+1); i++)  //i<21gray_codes[i] = cvCreateImage(cvSize(width,height), IPL_DEPTH_8U, 1);   //创建n_cols+n_rows+1 = 21张空图像内存,其中第一张为 全白图案int step = gray_codes[0]->widthStep/sizeof(uchar);  //widthStep=图像的宽度*通道数,即每一行需要的内存长度;step即每一行有多少个uchar类型的数据// Define first code as a white image.cvSet(gray_codes[0], cvScalar(255));// Define Gray codes for projector columns.    投影仪的列         三层循环!!!for(int c=0; c<width; c++){for(int i=0; i<n_cols; i++){     //i从0到9共循环10次,一行的每一像素都要循环10次,每循环一次就是对每一张图像的操作uchar* data = (uchar*)gray_codes[i+1]->imageData;       //data指针 指向格雷码图像的数据的地址if(i>0)  //从第二张图案开始  data[c]是一维数组,只能放一个值,应该是0或者1data[c] = (((c+col_shift) >> (n_cols-i-1)) & 1)^(((c+col_shift) >> (n_cols-i)) & 1);//先右移后与   i=1时,c分为3个部分,0-255,255-767,768-1023,编码分别为0,1,0 elsedata[c] = (((c+col_shift) >> (n_cols-i-1)) & 1);    //如果i=0时,c<=511,data[c]=0,c>511,data[c]=1//col_shift的作用:如果投影仪尺寸为912*1140,n_cols为10,则在编码时是以1024位来进行编码的,以c=0为例,会导致0-511=0,512-912=1,左右不等长//所以仍需要以512为中心,通过将0-912后移56变为56-968,这样512则变为了中心,左右编码位数相等。//像素值全部乘以255data[c] *= 255;  for(int r=1; r<height; r++)     //得到一行的编码后执行的平铺操作 data[r*step+c] = data[c];    //r*step为每一行所占的内存}}// Define Gray codes for projector rows.       投影仪的行for(int r=0; r<height; r++){for(int i=0; i<n_rows; i++){uchar* data = (uchar*)gray_codes[i+n_cols+1]->imageData; //格雷码的行列图案数据内存地址是连续的  if(i>0)data[r*step] = (((r+row_shift) >> (n_rows-i-1)) & 1)^(((r+row_shift) >> (n_rows-i)) & 1);elsedata[r*step] = (((r+row_shift) >> (n_rows-i-1)) & 1);data[r*step] *= 255;                   //data[r*step] = data[r*step]*255for(int c=1; c<width; c++)data[r*step+c] = data[r*step];   }}// Return without errors.return 0;
}

程序解读:

1,两个bool类型状态变量:sl_scan_cols,sl_scan_rows,判断生成行还是列条纹图案。

2,根据投影仪的投影图案的尺寸(以912*1140为例)来计算横向和纵向条纹图案的位数和生成的张数以及怕行和列的偏移量。

3,为格雷码的存储分配内存,并在第一张单独存储全白图案,其余格格雷图案内存连续存储。

4,通过3层循环来实现格雷码图案的生成,重点在每一行(列)格雷码编码的生成:

i=0时,编码方式为(((c+col_shift) >> (n_cols-i-1)) & 1);

当i从1变到n_cols时,n_cols-i-1和n_cols-i的值为:

该句代码则是对每一个像素点进行编码,并通过异或来获得编码值0或1,推导过程如下:

该程序与之前的不同之处在于:

(1)加入了行和列的偏移量,在编码时是:

col_shift的作用:如果投影仪尺寸为912*1140,n_cols为10,则在编码时是以1024位来进行编码的,以c=0为例,会导致0-511=0,512-912=1,左右不等长,所以仍需要以512为中心,通过将0-912后移56变为56-968,这样512则变为了中心,左右编码位数相等。

(2)数据全部在内存中操作,可以减小内存消耗,加快运算速度。

如果有疑问或者见到其他的不同的格雷码编码程序,请留言或QQ联系博主交流学习(857467352)。

结构光之格雷码编码加相移算法详解与实现(多种编码程序)相关推荐

  1. AES加解密算法详解

    0 AES简介 美国国家标准技术研究所在2001年发布了高级加密标准(AES).AES是一个对称分组密码算法,旨在取代DES成为广泛使用的标准. 根据使用的密码长度,AES最常见的有3种方案,用以适应 ...

  2. 余三码以及加减 超级详解

    在讲解余三码之前我们说说8421码是啥 如下:十进制数字(3696)对应8421码为0011 0110 1001 0110推导过程:即3 对应 00116 对应 01109 对应 10016 对应 0 ...

  3. TEA系列加解密算法详解

    文章目录 TEA系列概述 TEA算法介绍 TEA加密过程 C语言实现: XTEA算法介绍 XTEA加密过程 C语言实现 XTEA算法介绍 XXTEA加密过程 C语言实现 参考 TEA系列概述 TEA算 ...

  4. RSA 加解密算法详解

    RSA 为"非对称加密算法".也就是加密和解密用的密钥不同. (1)乙方生成两把密钥(公钥和私钥).公钥是公开的,任何人都可以获得,私钥则是保密的. (2)甲方获取乙方的公钥,然后 ...

  5. React Diff算法详解

    react diff算法 diff算法, 是 Virtual DOM 产生的一个概念, 用来计算出Virtual DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面,从而提 ...

  6. 结构光格雷码编码解码二值化 相移

    结构光三维重建学习笔记 所以,格雷码的生成步骤: 产生0,1两个字符串:0.1 在第一步基础上: 每个字符串前都+0->0+0.0+1 翻转首个元素,其余对称:1+1.1+0 最终:00.01. ...

  7. 遗传算法综述(三、 流程结构与编码解码(二进制编码,格雷码编码浮点数编码、符号编码))

    遗传算法流程结构: 遗传算法是从代表问题可能潜在的解集的一个种群(population)开始的,而一个种群则由经过基因(gene)编码的一定数目的个体(individual)组成.每个个体实际上是染色 ...

  8. 基于结构光投影三维重建:格雷码编码与解码

    一 单目结构光编码目的 类似于双目,如果把投影仪看成一个逆相机,直到空间中的一点成像平面的位置,就可以知道空间中一点的坐标. 编码的目的:知道打在物体物体表面的光是从投影仪的那个像素发出来的,就知道在 ...

  9. 4位格雷码的顺序编码_格雷码编码 解码 实现(Python)

    作者:曹博 01 二值码 02 格雷码编码 2.1 编码优点 2.2 编码生成 2.3 递归生成 2.4 二值码转换 2.5 编码图 03 格雷码投影 3.1 投影图案生成 3.2 DLP投影图像 0 ...

最新文章

  1. libyuv库的使用
  2. Select2 的简单使用
  3. Python包管理工具pip安装
  4. 今天拿到了同事给我买的《java与模式》
  5. 【2019icpc南京站网络赛 - H】Holy Grail(最短路,spfa判负环)
  6. 关于如何破坏单例我说了好几种方式,面试官却说:我其实随便问问,没想到你真会...
  7. python的array从0开始吗_为什么数组是从0开始的
  8. OpenEMR登录模块SQL注入分析
  9. [转载] Netty
  10. docker centos rpm离线安装1.8.2及pull的正确姿势
  11. 数值分析的matlab实验总结,数值分析及其MATLAB实验(第2版)
  12. 完成端口(Completion Port)详解
  13. B、KB、MB、GB、T单位换算
  14. 从气电到光伏 新加坡如何走好清洁能源之路?
  15. 解决VMware装上Mac后icloud无法激活 【安装QQ发现不能注册Apple ID】
  16. lilo是什么意思_lilo是什么意思_lilo的用法_lilo造句_趣词词典
  17. Apollo Planning决策规划算法代码详细解析 (14):SPEED_BOUNDS_PRIORI_DECIDER
  18. 如何使用企业微信工作台快速寄件?
  19. 基于R语言或Matlab直接读取压缩包中的数据
  20. 什么是嵌入式设备?/ 嵌入式设备的定义

热门文章

  1. linux的虚拟机 安装yum,虚拟机下linux用yum进行安装及其操作
  2. zoj 1203 Swordfish
  3. Sql Server 2008卸载后再次安装一直报错
  4. 麒麟操作系统之打印机适配查询
  5. java语言算法描述_六大java语言经典算法
  6. 三个人拍顺序c语言,最简单的c程拍序设计.ppt
  7. GIT 详细命令介绍
  8. 初学Python来用它制作一个简单的界面
  9. 谷歌计算机围棋程序,阿尔法狗教你下棋 谷歌上线AlphaGo围棋教学工具
  10. Linux网络编程IPv4和IPv6的inet_addr、inet_aton、inet_pton等函数小结