pbootcms网站模板|日韩1区2区|织梦模板||网站源码|日韩1区2区|jquery建站特效-html5模板网

在 C++ 中旋轉(zhuǎn)圖像而不使用 OpenCV 函數(shù)

Rotate an image in C++ without using OpenCV functions(在 C++ 中旋轉(zhuǎn)圖像而不使用 OpenCV 函數(shù))
本文介紹了在 C++ 中旋轉(zhuǎn)圖像而不使用 OpenCV 函數(shù)的處理方法,對(duì)大家解決問(wèn)題具有一定的參考價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧!

問(wèn)題描述

說(shuō)明:-我試圖在不使用 C++ 中的 OpenCV 函數(shù)的情況下旋轉(zhuǎn)圖像.旋轉(zhuǎn)中心不必是圖像的中心.它可能是一個(gè)不同的點(diǎn)(從圖像中心偏移).到目前為止,我遵循各種來(lái)源進(jìn)行圖像插值,并且我知道


更新:-

在受到與此問(wèn)題相關(guān)的許多答案以及下面最詳盡、最有用和最慷慨的答案的啟發(fā)后,我可以修復(fù)我的 OpenCV 代碼以獲得所需的結(jié)果.

修改后的代碼:

//平凡常量constexpr 雙圓周率 = 3.1415926535897932384626433832795;/*!* rief 函數(shù)生成變換矩陣* param angle 是用戶(hù)輸入的旋轉(zhuǎn)角度* param pivot 是 x 軸和 y 軸的平移量* 
eturn 平移矩陣*/cv::Mat CreateTransMat(double angle, std::pair<int, int> &pivot) {角度 = Pi * 角度/180;返回(cv::Mat_(3, 3)<(1, 2) <<(0, 0)/trans_mat.at(0, 2),trans_mat.at<double>(0, 1)/trans_mat.at<double>(0, 2));}/*!* rief 基于旋轉(zhuǎn)角度和平移變換圖像的函數(shù)矩陣.當(dāng)旋轉(zhuǎn)和平移同時(shí)發(fā)生時(shí),兩個(gè)矩陣可以合并* param src 是源圖像* param dest 是目標(biāo)圖像* param trans_mat 是變換(旋轉(zhuǎn)/平移)矩陣*/void ImageTransform(const cv::Mat &src, const cv::Mat &trans_mat, cv::Mat &dest) {int src_rows = src.rows;int src_cols = src.cols;int dest_rows = dest.rows;int dest_cols = dest.cols;const cv::Mat inverse_mat = trans_mat.inv();//#pragma omp parallel for simdfor (int row = 0; row < dest_rows; row++) {//#pragma omp parallel for simdfor (int col = 0; col < dest_cols; col++) {cv::Mat src_pos = CoordTransform(inverse_mat,(cv::Mat_(3, 1) (src_pos.at(0, 0) + 0.5);const int y_actual = static_cast(src_pos.at(0, 1) + 0.5);如果 (x_actual >= 0 && x_actual < src_cols &&y_actual >= 0 &&y_實(shí)際(row, col) = src.at(y_actual, x_actual);別的dest.at(row, col) = cv::Vec3b(0, 0, 0);}}}/*!* rief 命令行參數(shù)輸入的用戶(hù)手冊(cè)*/無(wú)效用法(){std::cout <<命令輸入:-

"<<"./ImageTransform <圖像><旋轉(zhuǎn)角度>><<std::endl;}/*!* rief 主函數(shù)讀取圖像的用戶(hù)輸入位置,然后應(yīng)用所需的轉(zhuǎn)換(旋轉(zhuǎn)/平移)*/int main(int argc, char *argv[]){自動(dòng)啟動(dòng) = std::chrono::steady_clock::now();if (argc == 0 || argc <3)用法();別的 {雙學(xué)位 = std::stod(argv[2]);雙角 = 度數(shù) * CV_PI/180.;cv::Mat src_img = cv::imread(argv[1]);std::pairnull_trans = std::make_pair(0, 0);std::pair翻譯_初始 =std::make_pair(src_img.cols/2 + 1, src_img.rows/2 + 1);std::pair翻譯_最終 =std::make_pair(0, -src_img.rows/2 - 4);如果(!src_img.data){std::cout <<圖像空"<<std::endl;簡(jiǎn)歷::等待鍵(0);}簡(jiǎn)歷:: imshow(來(lái)源",src_img);cv::Mat dest_img = cv::Mat(static_cast(2 * src_img.rows),static_cast(2 * src_img.cols),src_img.type());cv::Mat trans_mat1 = CreateTransMat(degree, translation_initial);ImageTransform(src_img, trans_mat1, dest_img);cv::imshow(臨時(shí)", dest_img);簡(jiǎn)歷::墊中間_img = dest_img;dest_img.release();dest_img = cv::Mat(src_img.rows, src_img.cols, src_img.type());cv::Mat trans_mat2 = CreateTransMat(0, translation_final);ImageTransform(interim_img, trans_mat2, dest_img);cv::imshow(最終圖像", dest_img);簡(jiǎn)歷::等待鍵(10);}自動(dòng)結(jié)束 = std::chrono::steady_clock::now();自動(dòng)差異 = 結(jié)束 - 開(kāi)始;std::cout <<std::chrono::duration <double, std::milli>(diff).count() <<"毫秒"<<std::endl;}

輸入圖像

旋轉(zhuǎn)圖像

解決方案

首先,我必須承認(rèn)我同意 來(lái)自我最近寫(xiě)的另一個(gè)答案.(已使用 PPM 文件格式,因?yàn)樗枰钌俚奈募?I/O 代碼.)

接下來(lái),我使用了linMath.h(我用于 3D 轉(zhuǎn)換的最小數(shù)學(xué)集合)為 2D 轉(zhuǎn)換創(chuàng)建最小數(shù)學(xué)集合–linMath.h:

#ifndef LIN_MATH_H#define LIN_MATH_H#include #include <cassert>#include <cmath>extern const double Pi;模板內(nèi)聯(lián)值 degToRad(值角度){返回 (VALUE)Pi * 角度/(VALUE)180;}模板內(nèi)聯(lián)值 radToDeg(VALUE 角度){返回 (VALUE)180 * 角度/(VALUE)Pi;}枚舉 ArgNull { Null };模板struct Vec2T {typedef VALUE 值;值 x, y;//默認(rèn)構(gòu)造函數(shù)(使元素未初始化)Vec2T() { }Vec2T(ArgNull): x((Value)0), y((Value)0) { }Vec2T(值 x, 值 y): x(x), y(y) { }};typedef Vec2Tvec2f;typedef Vec2Tvec2;模板struct Vec3T {typedef VALUE 值;值 x, y, z;//默認(rèn)構(gòu)造函數(shù)(使元素未初始化)Vec3T() { }Vec3T(ArgNull): x((Value)0), y((Value)0), z((Value)0) { }Vec3T(x 值,y 值,z 值):x(x), y(y), z(z) { }Vec3T(const Vec2T &xy, Value z): x(xy.x), y(xy.y), z(z) { }顯式運(yùn)算符 Vec2T() const { return Vec2T(x, y);}const Vec2f xy() const { return Vec2f(x, y);}const Vec2f xz() const { return Vec2f(x, z);}const Vec2f yz() const { return Vec2f(y, z);}};typedef Vec3Tvec3f;typedef Vec3Tvec3;枚舉 ArgInitIdent { InitIdent };枚舉 ArgInitTrans { InitTrans };枚舉 ArgInitRot { InitRot };枚舉 ArgInitScale { InitScale };枚舉 ArgInitFrame { InitFrame };模板結(jié)構(gòu) Mat3x3T {聯(lián)合{價(jià)值補(bǔ)償[3 * 3];結(jié)構(gòu){值_00、_01、_02;值_10、_11、_12;值_20、_21、_22;};};//默認(rèn)構(gòu)造函數(shù)(使元素未初始化)Mat3x3T() { }//構(gòu)造函數(shù)以按元素構(gòu)建矩陣Mat3x3T(價(jià)值_00,價(jià)值_01,價(jià)值_02,價(jià)值_10,價(jià)值_11,價(jià)值_12,價(jià)值_20、價(jià)值_21、價(jià)值_22):_00(_00), _01(_01), _02(_02),_10(_10), _11(_11), _12(_12),_20(_20)、_21(_21)、_22(_22){ }//構(gòu)造單位矩陣的構(gòu)造函數(shù)Mat3x3T(ArgInitIdent):_00((VALUE)1)、_01((VALUE)0)、_02((VALUE)0)、_10((VALUE)0)、_11((VALUE)1)、_12((VALUE)0)、_20((VALUE)0)、_21((VALUE)0)、_22((VALUE)1){ }//構(gòu)造一個(gè)用于翻譯的矩陣Mat3x3T(ArgInitTrans, const Vec2T &t):_00((VALUE)1)、_01((VALUE)0)、_02((VALUE)t.x)、_10((VALUE)0), _11((VALUE)1), _12((VALUE)t.y),_20((VALUE)0)、_21((VALUE)0)、_22((VALUE)1){ }//構(gòu)造函數(shù)來(lái)構(gòu)建旋轉(zhuǎn)矩陣Mat3x3T(ArgInitRot, VALUE 角度):_00(std::cos(angle)), _01(-std::sin(angle)), _02((VALUE)0),_10(std::sin(angle))、_11(std::cos(angle))、_12((VALUE)0)、_20((VALUE)0)、_21((VALUE)0)、_22((VALUE)1){ }//構(gòu)造函數(shù)來(lái)構(gòu)建平移/旋轉(zhuǎn)矩陣Mat3x3T(ArgInitFrame, const Vec2T &t, VALUE 角度):_00(std::cos(angle)), _01(-std::sin(angle)), _02((VALUE)t.x),_10(std::sin(angle))、_11(std::cos(angle))、_12((VALUE)t.y)、_20((VALUE)0)、_21((VALUE)0)、_22((VALUE)1){ }//構(gòu)造函數(shù)以構(gòu)建用于縮放的矩陣Mat3x3T(ArgInitScale, VALUE sx, VALUE sy):_00((VALUE)sx)、_01((VALUE)0)、_02((VALUE)0)、_10((VALUE)0)、_11((VALUE)sy)、_12((VALUE)0)、_20((VALUE)0)、_21((VALUE)0)、_22((VALUE)1){ }//允許使用 [][] 訪(fǎng)問(wèn)的運(yùn)算符VALUE* 運(yùn)算符 [] (int i){斷言(i >= 0 && i < 3);返回補(bǔ)償 + 3 * i;}//允許使用 [][] 訪(fǎng)問(wèn)的運(yùn)算符const VALUE* 運(yùn)算符 [] (int i) const{斷言(i >= 0 && i < 3);返回補(bǔ)償 + 3 * i;}//將矩陣與矩陣相乘 ->矩陣Mat3x3T 運(yùn)算符 * (const Mat3x3T &mat) const{返回 Mat3x3T(_00 * mat._00 + _01 * mat._10 + _02 * mat._20,_00 * mat._01 + _01 * mat._11 + _02 * mat._21,_00 * mat._02 + _01 * mat._12 + _02 * mat._22,_10 * mat._00 + _11 * mat._10 + _12 * mat._20,_10 * mat._01 + _11 * mat._11 + _12 * mat._21,_10 * mat._02 + _11 * mat._12 + _12 * mat._22,_20 * mat._00 + _21 * mat._10 + _22 * mat._20,_20 * mat._01 + _21 * mat._11 + _22 * mat._21,_20 * mat._02 + _21 * mat._12 + _22 * mat._22);}//將矩陣與向量相乘 ->向量Vec3T運(yùn)算符 * (const Vec3T &vec) const{返回 Vec3T(_00 * vec.x + _01 * vec.y + _02 * vec.z,_10 * vec.x + _11 * vec.y + _12 * vec.z,_20 * vec.x + _21 * vec.y + _22 * vec.z);}};typedef Mat3x3TMat3x3f;typedef Mat3x3T;Mat3x3;模板std::ostream&運(yùn)算符<<(std::ostream &out, const Mat3x3T &m){回來(lái)<<m._00<<'	' <<m._01<<'	' <<m._02<<'
'<<m._10 <<'	' <<m._11 <<'	' <<m._12<<'
'<<m._20<<'	' <<m._21<<'	' <<m._22<<'
';}/* 計(jì)算矩陣的行列式.** det = |M|** mat ... 矩陣*/模板值行列式(const Mat3x3T&mat){返回 mat._00 * mat._11 * mat._22+ mat._01 * mat._12 * mat._20+ mat._02 * mat._10 * mat._21- mat._20 * mat._11 * mat._02- mat._21 * mat._12 * mat._00- mat._22 * mat._10 * mat._01;}/* 返回正則矩陣的逆矩陣.** mat 矩陣反轉(zhuǎn)* eps epsilon 矩陣的規(guī)律性*/模板Mat3x3T倒置(const Mat3x3T&mat, VALUE eps = (VALUE)1E-10){斷言(eps >=(值)0);//計(jì)算行列式并檢查它是否不等于 0//(否則,矩陣是奇異的!)常量值 det = 行列式(墊);if (std::abs(det) (detInvPos * (mat._11 * mat._22 - mat._12 * mat._21),detInvNeg * (mat._01 * mat._22 - mat._02 * mat._21),detInvPos * (mat._01 * mat._12 - mat._02 * mat._11),detInvNeg * (mat._10 * mat._22 - mat._12 * mat._20),detInvPos * (mat._00 * mat._22 - mat._02 * mat._20),detInvNeg * (mat._00 * mat._12 - mat._02 * mat._10),detInvPos * (mat._10 * mat._21 - mat._11 * mat._20),detInvNeg * (mat._00 * mat._21 - mat._01 * mat._20),detInvPos * (mat._00 * mat._11 - mat._01 * mat._10));}#endif//LIN_MATH_H

以及linMath.ccPi的定義:

#include "linmath.h"const double Pi = 3.1415926535897932384626433832795;

有了所有可用的工具,我制作了示例應(yīng)用程序 xformRGBImg.cc:

#include #include <fstream>#include #include <字符串>#include "linMath.h"#include "image.h"#include "imagePPM.h"typedef unsigned int uint;結(jié)構(gòu)錯(cuò)誤{const std::string 文本;錯(cuò)誤(常量字符*文本):文本(文本){}};const char* readArg(int &i, int argc, char **argv){++i;if (i >= argc) throw Error("缺少參數(shù)!");返回 argv[i];}uint readArgUInt(int &i, int argc, char **argv){const char *arg = readArg(i, argc, argv);字符 * 結(jié)束;const unsigned long value = strtoul(arg, &end, 0);if (arg == end || *end) throw Error("應(yīng)為無(wú)符號(hào)整數(shù)值!");if ((uint)value != value) throw Error("無(wú)符號(hào)整數(shù)溢出!");返回(單位)值;}double readArgDouble(int &i, int argc, char **argv){const char *arg = readArg(i, argc, argv);字符 * 結(jié)束;const double value = strtod(arg, &end);if (arg == end || *end) throw Error("需要浮點(diǎn)值!");返回值;}std::pair調(diào)整大小(int &i,int argc,char **argv){const uint w = readArgUInt(i, argc, argv);const uint h = readArgUInt(i, argc, argv);返回 std::make_pair(w, h);}Mat3x3 翻譯(int &i,int argc,char **argv){const double x = readArgDouble(i, argc, argv);const double y = readArgDouble(i, argc, argv);返回 Mat3x3(InitTrans, Vec2(x, y));}Mat3x3 旋轉(zhuǎn)(int &i,int argc,char **argv){const double angle = readArgDouble(i, argc, argv);返回 Mat3x3(InitRot, degToRad(angle));}Mat3x3 比例(int &i,int argc,char **argv){const double x = readArgDouble(i, argc, argv);const double y = readArgDouble(i, argc, argv);返回 Mat3x3(InitScale, x, y);}Vec2 變換(const Mat3x3 &mat,const Vec2 &pos){const Vec3 pos_ = mat * Vec3(pos, 1.0);返回 Vec2(pos_.x/pos_.z, pos_.y/pos_.z);}空變換(const Image &imgSrc, const Mat3x3 &mat, Image &imgDst,int rgbFail = 0x808080){const Mat3x3 matInv = invert(mat);for (int y = 0; y < imgDst.h(); ++y) {for (int x = 0; x 大小輸出(0, 0);Mat3x3 mat(InitIdent);for (int i = 3; i < argc; ++i) 試試 {const std::string cmd = argv[i];if (cmd == "resize") sizeOut = resize(i, argc, argv);else if (cmd == "translate") mat = translate(i, argc, argv) * mat;else if (cmd == "rotate") mat = rotate(i, argc, argv) * mat;else if (cmd == "scale") mat = scale(i, argc, argv) * mat;別的 {std::cerr <<"錯(cuò)誤的命令!
";std::cout <<用法;返回 1;}} catch (const Error &error) {std::cerr <<$ 處的錯(cuò)誤參數(shù)" <<我<<"
"<<錯(cuò)誤文本<<'
';std::cout <<用法;返回 1;}//讀取圖像圖片 imgSrc;{ std::ifstream fIn(inFile.c_str(), std::ios::binary);如果(!readPPM(fIn,imgSrc)){std::cerr <<閱讀"<<文件中<<"'失敗!
";返回 1;}}//設(shè)置輸出圖像大小如果(sizeOut.first * sizeOut.second == 0){sizeOut = std::make_pair(imgSrc.w(), imgSrc.h());}//變換圖像圖像 imgDst;imgDst.resize(sizeOut.first, sizeOut.second, 3 * sizeOut.second);變換(imgSrc,墊,imgDst);//寫(xiě)入圖像{ std::ofstream fOut(outFile.c_str(), std::ios::binary);if (!writePPM(fOut, imgDst) || (fOut.close(), !fOut.good())) {std::cerr <<寫(xiě)'"<<輸出文件<<"'失敗!
";返回 1;}}//完畢返回0;}

注意:

命令行參數(shù)按順序處理.每個(gè)轉(zhuǎn)換命令從左乘到已經(jīng)組合的轉(zhuǎn)換矩陣,從一個(gè)單位矩陣開(kāi)始.這是因?yàn)樽儞Q的串聯(lián)導(dǎo)致矩陣的逆序乘法.(矩陣乘法是右結(jié)合的.)

例如變換的對(duì)應(yīng)矩陣:

x' = 翻譯(x)
x" = 旋轉(zhuǎn)(x')
x"' = 比例(x")

這是

x"' = 縮放(旋轉(zhuǎn)(翻譯(x)))

Mtransform = Mscale ·M旋轉(zhuǎn) ·M翻譯

x"' = Mscale · M旋轉(zhuǎn) ·Mtranslate · x = Mtransform · x

尺寸為 300 ×300.

注意:

所有嵌入的圖像都從 PPM 轉(zhuǎn)換為 JPEG(再次在

看起來(lái)像原來(lái)的–身份轉(zhuǎn)換應(yīng)該是什么.

現(xiàn)在,旋轉(zhuǎn) 30°:

$ ./xformRGBImg cat.ppm cat.rot30.ppm 旋轉(zhuǎn) 30$

要繞某個(gè)中心旋轉(zhuǎn),有一個(gè)相應(yīng)的方法.需要前后翻譯:

$ ./xformRGBImg cat.ppm cat.rot30c150,150.ppm 平移 -150 -150 旋轉(zhuǎn) 30 平移 150 150$

輸出圖像可以用 w · 調(diào)整大小√2 ×·√2 以適應(yīng)任何中心旋轉(zhuǎn).

因此,輸出圖像的大小調(diào)整為 425 ×425 其中最后一次翻譯分別調(diào)整為translate 212.5 212.5:

$ ./xformRGBImg cat.ppm cat.rot30c150,150.425x425.ppm 調(diào)整大小 425 425 平移 -150 -150 旋轉(zhuǎn) 30 平移 212.5 212.5$

尚未檢查縮放比例:

$ ./xformRGBImg cat.ppm cat.rot30c150,150s0.7,0.7.ppm 平移 -150 -150 旋轉(zhuǎn) 30 縮放 0.7 0.7 平移 150 150$

<小時(shí)>

最后,公平地說(shuō),我想提一下大哥".我的小玩具工具:ImageMagick.

Description :- I am trying to rotate an image without using OpenCV functions in C++. The rotation center need not be the center of the image. It could be a different point (offset from the image center). So far I followed a variety of sources to do image interpolation and I am aware of a source which does the job perfectly in MATLAB. I tried to mimic the same in C++ without OpenCV functions. But I am not getting the expected rotated image. Instead my output appears like a small horizontal line on the screen.

void RotateNearestNeighbor(cv::Mat src, double angle) {
int oldHeight = src.rows;
int oldWidth = src.cols;
int newHeight = std::sqrt(2) * oldHeight;
int newWidth = std::sqrt(2) * oldWidth;
cv::Mat output = cv::Mat(newHeight, newWidth, src.type());
double ctheta = cos(angle);
double stheta = sin(angle);

for (size_t i = 0; i < newHeight; i++) {
    for (size_t j = 0; j < newWidth; j++) {

        int oldRow = static_cast<int> ((i - newHeight / 2) * ctheta +
                                       (j - newWidth / 2) * stheta + oldHeight / 2);
        int oldCol = static_cast<int> (-(i - newHeight / 2) * stheta +
                                       (j - newWidth / 2) * ctheta + oldWidth / 2);

        if (oldRow > 0 && oldCol > 0 && oldRow <= oldHeight && oldCol <= oldWidth)
            output.at<cv::Vec3b>(i, j) = src.at<cv::Vec3b>(oldRow, oldCol);
        else
            output.at<cv::Vec3b>(i, j) = cv::Vec3b(0, 0, 0);
    }
}
cv::imshow("Rotated cat", output);
}

The following are my input (left side) and output (right side) images


UPDATE : -

After being inspired by many answers related to this question and also the most elaborate, helpful and generous answer below, I could fix my OpenCV code to get the desired result.

Modified Code :

// Trivial constant
constexpr double Pi = 3.1415926535897932384626433832795;

/*!
* rief Function to generate transformation matrix
* param angle is the angle of rotation from user input
* param pivot is the amount of translation in x and y axes
* 
eturn translation matrix
*/
cv::Mat CreateTransMat(double angle, std::pair<int, int> &pivot) {
    angle = Pi * angle / 180;
    return (cv::Mat_<double>(3, 3) << cos(angle), -sin(angle), pivot.first,
            sin(angle), cos(angle), pivot.second, 0, 0, 1);
}

/*!
* rief Function to apply coordinate transform from destination to     source
* param inv_mat being the inverse transformation matrix for the transform needed
* 
eturn pos being the homogeneous coordinates for transformation
*/
cv::Mat CoordTransform(const cv::Mat &inv_mat, const cv::Mat &pos) {
    assert(inv_mat.cols == pos.rows);
    cv::Mat trans_mat = inv_mat * pos;
    return (cv::Mat_<double>(1, 2) <<
            trans_mat.at<double>(0, 0) / trans_mat.at<double>(0, 2),
            trans_mat.at<double>(0, 1) / trans_mat.at<double>(0, 2));
}

/*!
* rief Function to transform an image based on a rotation angle and translation
         matrix. When rotation and translation happen at the same time, the
         two matrices can be combined
* param src being source image
* param dest being destination image
* param trans_mat being the transformation (rotation/ translation) matrix
*/
void ImageTransform(const cv::Mat &src, const cv::Mat &trans_mat, cv::Mat &dest) {
    int src_rows = src.rows;
    int src_cols = src.cols;
    int dest_rows = dest.rows;
    int dest_cols = dest.cols;
    const cv::Mat inverse_mat = trans_mat.inv();
    //#pragma omp parallel for simd
    for (int row = 0; row < dest_rows; row++) {
        //#pragma omp parallel for simd
        for (int col = 0; col < dest_cols; col++) {
            cv::Mat src_pos = CoordTransform(inverse_mat,
                                         (cv::Mat_<double>(3, 1) << col, row, 1));
            const int x_actual = static_cast<int>(src_pos.at<double>(0, 0) + 0.5);
            const int y_actual = static_cast<int>(src_pos.at<double>(0, 1) + 0.5);

            if (x_actual >= 0 && x_actual < src_cols &&
                y_actual >= 0 && y_actual < src_rows)
                dest.at<cv::Vec3b>(row, col) = src.at<cv::Vec3b>(y_actual, x_actual);
            else
                dest.at<cv::Vec3b>(row, col) = cv::Vec3b(0, 0, 0);
        }
    }    
}

/*!
* rief User manual for command-line args input
*/
void Usage() {
    std::cout << "COMMAND INPUT : - 

" <<
              "          ./ImageTransform <image> <rotation-angle>" <<
              std::endl;
}
/*!
* rief main function to read a user input location for an image and then apply the
         required transformations (rotation / translation)
*/
int main(int argc, char *argv[])
{
    auto start = std::chrono::steady_clock::now();
    if (argc == 0 || argc < 3)
        Usage();
    else {
        double degree = std::stod(argv[2]);
        double angle = degree * CV_PI / 180.;
        cv::Mat src_img = cv::imread(argv[1]);
        std::pair<int, int> null_trans = std::make_pair(0, 0);
        std::pair<int, int> translation_initial =
            std::make_pair(src_img.cols / 2 + 1, src_img.rows / 2 + 1);
        std::pair<int, int> translation_final =
            std::make_pair(0, -src_img.rows / 2 - 4);
        if (!src_img.data)
        {
            std::cout << "image null" << std::endl;
            cv::waitKey(0);
        }
        cv::imshow("Source", src_img);
        cv::Mat dest_img = cv::Mat(static_cast<int>(2 * src_img.rows),
                                   static_cast<int>(2 * src_img.cols),
                                   src_img.type());
        cv::Mat trans_mat1 = CreateTransMat(degree, translation_initial);
        ImageTransform(src_img, trans_mat1, dest_img);
        cv::imshow("Interim", dest_img);
        cv::Mat interim_img = dest_img;
        dest_img.release();
        dest_img = cv::Mat(src_img.rows, src_img.cols, src_img.type());
        cv::Mat trans_mat2 = CreateTransMat(0, translation_final);
        ImageTransform(interim_img, trans_mat2, dest_img);
        cv::imshow("Final image", dest_img);
        cv::waitKey(10);
    }
    auto end = std::chrono::steady_clock::now();
    auto diff = end - start;
    std::cout << std::chrono::duration <double, std::milli> (diff).count() <<
              " ms" << std::endl;
}

Input image

Rotated image

解決方案

First, I have to admit I agree with generic_opto_guy:

The approach with the loop looks good, so we would need to check the math. On thing I noticed: if (oldRow > 0 && oldCol > 0 && oldRow <= oldHeight && oldCol <= oldWidth) implies you start indexing with 1. I belife that opencv starts indexing with 0.

For all that, I couldn't resist to answer. (May be, it's just an image phase of mine.)

Instead of fiddling with sin() and cos(), I would recommend to use matrix transformation. At the first glance, this might appear over-engineered but later you will recognize that it bears much more flexibility. With a transformation matrix, you can express a lot of transformations (translation, rotation, scaling, shearing, projection) as well as combining multiple transformations into one matrix.

(A teaser for what is possible: SO: How to paint / deform a QImage in 2D?)

In an image, the pixels may be addressed by 2d coordinates. Hence a 2×2 matrix comes into mind but a 2×2 matrix cannot express translations. For this, homogeneous coordinates has been introduced – a math trick to handle positions and directions in the same space by extending the dimension by one.

To make it short, a 2d position (x, y) has the homogeneous coordinates (x, y, 1).

A position transformed with a transformation matrix:

v′ = M · v.

This may or may not change the value of third component. To convert the homogeneous coordinate to 2D position again, x and y has to be divided by 3rd component.

Vec2 transform(const Mat3x3 &mat, const Vec2 &pos)
{
  const Vec3 pos_ = mat * Vec3(pos, 1.0);
  return Vec2(pos_.x / pos_.z, pos_.y / pos_.z);
}

To transform a source image into a destination image, the following function can be used:

void transform(
  const Image &imgSrc, const Mat3x3 &mat, Image &imgDst,
  int rgbFail = 0x808080)
{
  const Mat3x3 matInv = invert(mat);
  for (int y = 0; y < imgDst.h(); ++y) {
    for (int x = 0; x < imgDst.w(); ++x) {
      const Vec2 pos = transform(matInv, Vec2(x, y));
      const int xSrc = (int)(pos.x + 0.5), ySrc = http://pic.html5code.net(int)(pos.y + 0.5);
      imgDst.setPixel(x, y,
        xSrc >= 0 && xSrc < imgSrc.w() && ySrc >= 0 && ySrc < imgSrc.h()
        ? imgSrc.getPixel(xSrc, ySrc)
        : rgbFail);
    }
  }
}

Note:

The transformation matrix mat describes the transformation of source image coordinates to destination image coordinates. The nested loops iterate over destination image. Hence, the inverse matrix (representing the reverse transformation) has to be used to get the corresponding source image coordinates which map to the current destination coordinates.

… and the matrix constructor for the rotation:

enum ArgInitRot { InitRot };

template <typename VALUE>
struct Mat3x3T {
  union {
    VALUE comp[3 * 3];
    struct {
      VALUE _00, _01, _02;
      VALUE _10, _11, _12;
      VALUE _20, _21, _22;
    };
  };

  // constructor to build a matrix for rotation
  Mat3x3T(ArgInitRot, VALUE angle):
    _00(std::cos(angle)), _01(-std::sin(angle)), _02((VALUE)0),
    _10(std::sin(angle)), _11( std::cos(angle)), _12((VALUE)0),
    _20(       (VALUE)0), _21(        (VALUE)0), _22((VALUE)1)
  { }

can be used to construct a rotation with angle (in degree):

Mat3x3T<double> mat(InitRot, degToRad(30.0));

Note:

I would like to emphasize how the transformed coordinates are used:

      const Vec2 pos = transform(matInv, Vec2(x, y));
      const int xSrc = (int)(pos.x + 0.5), ySrc = http://pic.html5code.net(int)(pos.y + 0.5);

Rounding the results to yield one discrete pixel position is actually what is called Nearest Neighbour. Alternatively, the now discarded fractional parts could be used for a linear interpolation between neighbour pixels.


To make a small sample, I first copied image.h, image.cc, imagePPM.h, and imagePPM.cc from another answer I wrote recently. (The PPM file format has been used as it needs minimal code for file I/O.)

Next, I used linMath.h (my minimal math collection for 3D transformations) to make a minimal math collection for 2D transformations – linMath.h:

#ifndef LIN_MATH_H
#define LIN_MATH_H

#include <iostream>
#include <cassert>
#include <cmath>

extern const double Pi;

template <typename VALUE>
inline VALUE degToRad(VALUE angle)
{
  return (VALUE)Pi * angle / (VALUE)180;
}

template <typename VALUE>
inline VALUE radToDeg(VALUE angle)
{
  return (VALUE)180 * angle / (VALUE)Pi;
}

enum ArgNull { Null };

template <typename VALUE>
struct Vec2T {
  typedef VALUE Value;
  Value x, y;
  // default constructor (leaving elements uninitialized)
  Vec2T() { }
  Vec2T(ArgNull): x((Value)0), y((Value)0) { }
  Vec2T(Value x, Value y): x(x), y(y) { }
};

typedef Vec2T<float> Vec2f;
typedef Vec2T<double> Vec2;

template <typename VALUE>
struct Vec3T {
  typedef VALUE Value;
  Value x, y, z;
  // default constructor (leaving elements uninitialized)
  Vec3T() { }
  Vec3T(ArgNull): x((Value)0), y((Value)0), z((Value)0) { }
  Vec3T(Value x, Value y, Value z): x(x), y(y), z(z) { }
  Vec3T(const Vec2T<Value> &xy, Value z): x(xy.x), y(xy.y), z(z) { }
  explicit operator Vec2T<Value>() const { return Vec2T<Value>(x, y); }
  const Vec2f xy() const { return Vec2f(x, y); }
  const Vec2f xz() const { return Vec2f(x, z); }
  const Vec2f yz() const { return Vec2f(y, z); }
};

typedef Vec3T<float> Vec3f;
typedef Vec3T<double> Vec3;

enum ArgInitIdent { InitIdent };
enum ArgInitTrans { InitTrans };
enum ArgInitRot { InitRot };
enum ArgInitScale { InitScale };
enum ArgInitFrame { InitFrame };

template <typename VALUE>
struct Mat3x3T {
  union {
    VALUE comp[3 * 3];
    struct {
      VALUE _00, _01, _02;
      VALUE _10, _11, _12;
      VALUE _20, _21, _22;
    };
  };

  // default constructor (leaving elements uninitialized)
  Mat3x3T() { }
  // constructor to build a matrix by elements
  Mat3x3T(
    VALUE _00, VALUE _01, VALUE _02,
    VALUE _10, VALUE _11, VALUE _12,
    VALUE _20, VALUE _21, VALUE _22):
    _00(_00), _01(_01), _02(_02),
    _10(_10), _11(_11), _12(_12),
    _20(_20), _21(_21), _22(_22)
  { }
  // constructor to build an identity matrix
  Mat3x3T(ArgInitIdent):
    _00((VALUE)1), _01((VALUE)0), _02((VALUE)0),
    _10((VALUE)0), _11((VALUE)1), _12((VALUE)0),
    _20((VALUE)0), _21((VALUE)0), _22((VALUE)1)
  { }
  // constructor to build a matrix for translation
  Mat3x3T(ArgInitTrans, const Vec2T<VALUE> &t):
    _00((VALUE)1), _01((VALUE)0), _02((VALUE)t.x),
    _10((VALUE)0), _11((VALUE)1), _12((VALUE)t.y),
    _20((VALUE)0), _21((VALUE)0), _22((VALUE)1)
  { }
  // constructor to build a matrix for rotation
  Mat3x3T(ArgInitRot, VALUE angle):
    _00(std::cos(angle)), _01(-std::sin(angle)), _02((VALUE)0),
    _10(std::sin(angle)), _11( std::cos(angle)), _12((VALUE)0),
    _20(       (VALUE)0), _21(        (VALUE)0), _22((VALUE)1)
  { }
  // constructor to build a matrix for translation/rotation
  Mat3x3T(ArgInitFrame, const Vec2T<VALUE> &t, VALUE angle):
    _00(std::cos(angle)), _01(-std::sin(angle)), _02((VALUE)t.x),
    _10(std::sin(angle)), _11( std::cos(angle)), _12((VALUE)t.y),
    _20(       (VALUE)0), _21(        (VALUE)0), _22((VALUE)1)
  { }
  // constructor to build a matrix for scaling
  Mat3x3T(ArgInitScale, VALUE sx, VALUE sy):
    _00((VALUE)sx), _01( (VALUE)0), _02((VALUE)0),
    _10( (VALUE)0), _11((VALUE)sy), _12((VALUE)0),
    _20( (VALUE)0), _21( (VALUE)0), _22((VALUE)1)
  { }
  // operator to allow access with [][]
  VALUE* operator [] (int i)
  {
    assert(i >= 0 && i < 3);
    return comp + 3 * i;
  }
  // operator to allow access with [][]
  const VALUE* operator [] (int i) const
  {
    assert(i >= 0 && i < 3);
    return comp + 3 * i;
  }

  // multiply matrix with matrix -> matrix
  Mat3x3T operator * (const Mat3x3T &mat) const
  {
    return Mat3x3T(
      _00 * mat._00 + _01 * mat._10 + _02 * mat._20,
      _00 * mat._01 + _01 * mat._11 + _02 * mat._21,
      _00 * mat._02 + _01 * mat._12 + _02 * mat._22,
      _10 * mat._00 + _11 * mat._10 + _12 * mat._20,
      _10 * mat._01 + _11 * mat._11 + _12 * mat._21,
      _10 * mat._02 + _11 * mat._12 + _12 * mat._22,
      _20 * mat._00 + _21 * mat._10 + _22 * mat._20,
      _20 * mat._01 + _21 * mat._11 + _22 * mat._21,
      _20 * mat._02 + _21 * mat._12 + _22 * mat._22);
  }
  // multiply matrix with vector -> vector
  Vec3T<VALUE> operator * (const Vec3T<VALUE> &vec) const
  {
    return Vec3T<VALUE>(
      _00 * vec.x + _01 * vec.y + _02 * vec.z,
      _10 * vec.x + _11 * vec.y + _12 * vec.z,
      _20 * vec.x + _21 * vec.y + _22 * vec.z);
  }
};

typedef Mat3x3T<float> Mat3x3f;
typedef Mat3x3T<double> Mat3x3;

template <typename VALUE>
std::ostream& operator<<(std::ostream &out, const Mat3x3T<VALUE> &m)
{
  return out
    << m._00 << '	' << m._01 << '	' << m._02 << '
'
    << m._10 << '	' << m._11 << '	' << m._12 << '
'
    << m._20 << '	' << m._21 << '	' << m._22 << '
';
}

/* computes determinant of a matrix.
 *
 * det = |M|
 *
 * mat ... the matrix
 */
template <typename VALUE>
VALUE determinant(const Mat3x3T<VALUE> &mat)
{
  return mat._00 * mat._11 * mat._22
    + mat._01 * mat._12 * mat._20
    + mat._02 * mat._10 * mat._21
    - mat._20 * mat._11 * mat._02
    - mat._21 * mat._12 * mat._00
    - mat._22 * mat._10 * mat._01;
}

/* returns the inverse of a regular matrix.
 *
 * mat matrix to invert
 * eps epsilon for regularity of matrix
 */
template <typename VALUE>
Mat3x3T<VALUE> invert(
  const Mat3x3T<VALUE> &mat, VALUE eps = (VALUE)1E-10)
{
  assert(eps >= (VALUE)0);
  // compute determinant and check that it its unequal to 0
  // (Otherwise, matrix is singular!)
  const VALUE det = determinant(mat);
  if (std::abs(det) < eps) throw std::domain_error("Singular matrix!");
  // reciproke of determinant
  const VALUE detInvPos = (VALUE)1 / det, detInvNeg = -detInvPos;
  // compute each element by determinant of sub-matrix which is build
  // striking out row and column of pivot element itself
  // BTW, the determinant is multiplied with -1 when sum of row and column
  // index is odd (chess board rule)
  // (This is usually called cofactor of related element.)
  // transpose matrix and multiply with 1/determinant of original matrix
  return Mat3x3T<VALUE>(
    detInvPos * (mat._11 * mat._22 - mat._12 * mat._21),
    detInvNeg * (mat._01 * mat._22 - mat._02 * mat._21),
    detInvPos * (mat._01 * mat._12 - mat._02 * mat._11),
    detInvNeg * (mat._10 * mat._22 - mat._12 * mat._20),
    detInvPos * (mat._00 * mat._22 - mat._02 * mat._20),
    detInvNeg * (mat._00 * mat._12 - mat._02 * mat._10),
    detInvPos * (mat._10 * mat._21 - mat._11 * mat._20),
    detInvNeg * (mat._00 * mat._21 - mat._01 * mat._20),
    detInvPos * (mat._00 * mat._11 - mat._01 * mat._10));
}

#endif // LIN_MATH_H

and the definition of Pi in linMath.cc:

#include "linmath.h"

const double Pi = 3.1415926535897932384626433832795;

Having all tools available, I made the sample application xformRGBImg.cc:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>

#include "linMath.h"
#include "image.h"
#include "imagePPM.h"

typedef unsigned int uint;

struct Error {
  const std::string text;
  Error(const char *text): text(text) { }
};

const char* readArg(int &i, int argc, char **argv)
{
  ++i;
  if (i >= argc) throw Error("Missing argument!");
  return argv[i];
}

uint readArgUInt(int &i, int argc, char **argv)
{
  const char *arg = readArg(i, argc, argv); char *end;
  const unsigned long value = strtoul(arg, &end, 0);
  if (arg == end || *end) throw Error("Unsigned integer value expected!");
  if ((uint)value != value) throw Error("Unsigned integer overflow!");
  return (uint)value;
}

double readArgDouble(int &i, int argc, char **argv)
{
  const char *arg = readArg(i, argc, argv); char *end;
  const double value = strtod(arg, &end);
  if (arg == end || *end) throw Error("Floating point value expected!");
  return value;
}

std::pair<uint, uint> resize(int &i, int argc, char **argv)
{
  const uint w = readArgUInt(i, argc, argv);
  const uint h = readArgUInt(i, argc, argv);
  return std::make_pair(w, h);
}

Mat3x3 translate(int &i, int argc, char **argv)
{
  const double x = readArgDouble(i, argc, argv);
  const double y = readArgDouble(i, argc, argv);
  return Mat3x3(InitTrans, Vec2(x, y));
}

Mat3x3 rotate(int &i, int argc, char **argv)
{
  const double angle = readArgDouble(i, argc, argv);
  return Mat3x3(InitRot, degToRad(angle));
}

Mat3x3 scale(int &i, int argc, char **argv)
{
  const double x = readArgDouble(i, argc, argv);
  const double y = readArgDouble(i, argc, argv);
  return Mat3x3(InitScale, x, y);
}

Vec2 transform(const Mat3x3 &mat, const Vec2 &pos)
{
  const Vec3 pos_ = mat * Vec3(pos, 1.0);
  return Vec2(pos_.x / pos_.z, pos_.y / pos_.z);
}

void transform(
  const Image &imgSrc, const Mat3x3 &mat, Image &imgDst,
  int rgbFail = 0x808080)
{
  const Mat3x3 matInv = invert(mat);
  for (int y = 0; y < imgDst.h(); ++y) {
    for (int x = 0; x < imgDst.w(); ++x) {
      const Vec2 pos = transform(matInv, Vec2(x, y));
      const int xSrc = (int)(pos.x + 0.5), ySrc = http://pic.html5code.net(int)(pos.y + 0.5);
      imgDst.setPixel(x, y,
        xSrc >= 0 && xSrc < imgSrc.w() && ySrc >= 0 && ySrc < imgSrc.h()
        ? imgSrc.getPixel(xSrc, ySrc)
        : rgbFail);
    }
  }
}

const char *const usage =
  "Usage:
"
  "  xformRGBImg IN_FILE OUT_FILE [[CMD]...]
"
  "
"
  "Commands:
"
  "  resize W H
"
  "  translate X Y
"
  "  rotate ANGLE
"
  "  scale SX SY
";

int main(int argc, char **argv)
{
  // read command line arguments
  if (argc <= 2) {
    std::cerr << "Missing arguments!
";
    std::cout << usage;
    return 1;
  }
  const std::string inFile = argv[1];
  const std::string outFile = argv[2];
  std::pair<uint, uint> sizeOut(0, 0);
  Mat3x3 mat(InitIdent);
  for (int i = 3; i < argc; ++i) try {
    const std::string cmd = argv[i];
    if (cmd == "resize") sizeOut = resize(i, argc, argv);
    else if (cmd == "translate") mat = translate(i, argc, argv) * mat;
    else if (cmd == "rotate") mat = rotate(i, argc, argv) * mat;
    else if (cmd == "scale") mat = scale(i, argc, argv) * mat;
    else {
      std::cerr << "Wrong command!
";
      std::cout << usage;
      return 1;
    }
  } catch (const Error &error) {
    std::cerr << "Wrong argument at $" << i << "
"
      << error.text << '
';
    std::cout << usage;
    return 1;
  }
  // read image
  Image imgSrc;
  { std::ifstream fIn(inFile.c_str(), std::ios::binary);
    if (!readPPM(fIn, imgSrc)) {
      std::cerr << "Reading '" << inFile << "' failed!
";
      return 1;
    }
  }
  // set output image size
  if (sizeOut.first * sizeOut.second == 0) {
    sizeOut = std::make_pair(imgSrc.w(), imgSrc.h());
  }
  // transform image
  Image imgDst;
  imgDst.resize(sizeOut.first, sizeOut.second, 3 * sizeOut.second);
  transform(imgSrc, mat, imgDst);
  // write image
  { std::ofstream fOut(outFile.c_str(), std::ios::binary);
    if (!writePPM(fOut, imgDst) || (fOut.close(), !fOut.good())) {
      std::cerr << "Writing '" << outFile << "' failed!
";
      return 1;
    }
  }
  // done
  return 0;
}

Note:

The command line arguments are processed in order. Each transformation command is multiplied from left to the already combined transformation matrix, starting with an identity matrix. This is because a concatenation of transformations results in the reverse ordered multiplication of matrices. (The matrix multiplication is right associative.)

E.g. the corresponding matrix for a transform:

x' = translate(x)
x" = rotate(x')
x"' = scale(x")

which is

x"' = scale(rotate(translate(x)))

is

Mtransform = Mscale · Mrotate · Mtranslate

and

x"' = Mscale · Mrotate · Mtranslate · x = Mtransform · x

Compiled and tested in cygwin:

$ g++ -std=c++11 -o xformRGBImg image.cc imagePPM.cc linMath.cc xformRGBImg.cc

$ ./xformRGBImg                                                               
Missing arguments!
Usage:
  xformRGBImg IN_FILE OUT_FILE [[CMD]...]

Commands:
  resize W H
  translate X Y
  rotate ANGLE
  scale SX SY

$

Finally, a sample image cat.jpg (converted to PPM in GIMP):

with size 300 × 300.

Note:

All embedded images are converted from PPM to JPEG (in GIMP again). (PPM is not supported in image upload, nor can I imagine that any browser can display it properly.)

To start with a minimum:

$ ./xformRGBImg cat.ppm cat.copy.ppm

$

It looks like the original – what should be expected by an identity transform.

Now, a rotation with 30°:

$ ./xformRGBImg cat.ppm cat.rot30.ppm rotate 30

$

To rotate about a certain center, there is a resp. translation before and afterwards needed:

$ ./xformRGBImg cat.ppm cat.rot30c150,150.ppm 
  translate -150 -150 rotate 30 translate 150 150

$

The output image can be resized with w · √2 × h · √2 to fit any center rotation in.

So, the output image is resized to 425 × 425 where the last translation is adjusted respectively to translate 212.5 212.5:

$ ./xformRGBImg cat.ppm cat.rot30c150,150.425x425.ppm 
  resize 425 425 translate -150 -150 rotate 30 translate 212.5 212.5

$

The scaling has not yet been checked:

$ ./xformRGBImg cat.ppm cat.rot30c150,150s0.7,0.7.ppm 
  translate -150 -150 rotate 30 scale 0.7 0.7 translate 150 150

$


Finally, to be fair, I would like to mention the “big brother” of my little toy tool: ImageMagick.

這篇關(guān)于在 C++ 中旋轉(zhuǎn)圖像而不使用 OpenCV 函數(shù)的文章就介紹到這了,希望我們推薦的答案對(duì)大家有所幫助,也希望大家多多支持html5模板網(wǎng)!

【網(wǎng)站聲明】本站部分內(nèi)容來(lái)源于互聯(lián)網(wǎng),旨在幫助大家更快的解決問(wèn)題,如果有圖片或者內(nèi)容侵犯了您的權(quán)益,請(qǐng)聯(lián)系我們刪除處理,感謝您的支持!

相關(guān)文檔推薦

Assertion failed (size.widthgt;0 amp;amp; size.heightgt;0)(斷言失敗(size.width0 amp;amp; size.height0))
OpenCV: process every frame(OpenCV:處理每一幀)
Why can#39;t I open avi video in openCV?(為什么我不能在 openCV 中打開(kāi) avi 視頻?)
OpenCV unable to set up SVM Parameters(OpenCV 無(wú)法設(shè)置 SVM 參數(shù))
Convert a single color with cvtColor(使用 cvtColor 轉(zhuǎn)換單一顏色)
Easiest way to rotate by 90 degrees an image using OpenCV?(使用 OpenCV 將圖像旋轉(zhuǎn) 90 度的最簡(jiǎn)單方法?)
主站蜘蛛池模板: 耐破强度测试仪-纸箱破裂强度试验机-济南三泉中石单品站 | 行吊_电动单梁起重机_双梁起重机_合肥起重机_厂家_合肥市神雕起重机械有限公司 | 西安微信朋友圈广告投放_微信朋友圈推广_西安度娘网络科技有限公司 | 线材成型机,线材折弯机,线材成型机厂家,贝朗自动化设备有限公司1 | 合肥网络推广_合肥SEO网站优化-安徽沃龙First | YAGEO国巨电容|贴片电阻|电容价格|三星代理商-深圳市巨优电子有限公司 | 恒温恒湿箱(药品/保健品/食品/半导体/细菌)-兰贝石(北京)科技有限公司 | 一体化污水处理设备,一体化污水设备厂家-宜兴市福源水处理设备有限公司 | 免费分销系统 — 分销商城系统_分销小程序开发 -【微商来】 | 泰安办公家具-泰安派格办公用品有限公司 | 水热合成反应釜-防爆高压消解罐-西安常仪仪器设备有限公司 | YT保温材料_YT无机保温砂浆_外墙保温材料_南阳银通节能建材高新技术开发有限公司 | 座椅式升降机_无障碍升降平台_残疾人升降平台-南京明顺机械设备有限公司 | 成都治疗尖锐湿疣比较好的医院-成都治疗尖锐湿疣那家医院好-成都西南皮肤病医院 | 威廉希尔WilliamHill·足球(中国)体育官方网站 | 碳化硅,氮化硅,冰晶石,绢云母,氟化铝,白刚玉,棕刚玉,石墨,铝粉,铁粉,金属硅粉,金属铝粉,氧化铝粉,硅微粉,蓝晶石,红柱石,莫来石,粉煤灰,三聚磷酸钠,六偏磷酸钠,硫酸镁-皓泉新材料 | 二氧化碳/活性炭投加系统,次氯酸钠发生器,紫外线消毒设备|广州新奥 | 风淋室生产厂家报价_传递窗|送风口|臭氧机|FFU-山东盛之源净化设备 | 校园文化空间设计-数字化|中医文化空间设计-党建|法治廉政主题文化空间施工-山东锐尚文化传播公司 | 拉力测试机|材料拉伸试验机|电子拉力机价格|万能试验机厂家|苏州皖仪实验仪器有限公司 | 济南保安公司加盟挂靠-亮剑国际安保服务集团总部-山东保安公司|济南保安培训学校 | 湖南自考_湖南自学考试| IHDW_TOSOKU_NEMICON_EHDW系列电子手轮,HC1系列电子手轮-上海莆林电子设备有限公司 | 离子色谱自动进样器-青岛艾力析实验科技有限公司 | 不锈钢发酵罐_水果酒发酵罐_谷物发酵罐_山东誉诚不锈钢制品有限公司 | 电线电缆厂家|沈阳电缆厂|电线厂|沈阳英联塑力线缆有限公司 | 派财经_聚焦数字经济内容服务平台 | 连栋温室大棚建造厂家-智能玻璃温室-薄膜温室_青州市亿诚农业科技 | 海外仓系统|国际货代系统|退货换标系统|WMS仓储系统|海豚云 | 冷藏车-东风吸污车-纯电动环卫车-污水净化车-应急特勤保障车-程力专汽厂家-程力专用汽车股份有限公司销售二十一分公司 | 长沙广告公司|长沙广告制作设计|长沙led灯箱招牌制作找望城湖南锦蓝广告装饰工程有限公司 | 步进_伺服_行星减速机,微型直流电机,大功率直流电机-淄博冠意传动机械 | SPC工作站-连杆综合检具-表盘气动量仪-内孔缺陷检测仪-杭州朗多检测仪器有限公司 | 耐高温电缆厂家-远洋高温电缆 | 气力输送_输送机械_自动化配料系统_负压吸送_制造主力军江苏高达智能装备有限公司! | 不锈钢管件(不锈钢弯头,不锈钢三通,不锈钢大小头),不锈钢法兰「厂家」-浙江志通管阀 | 工业插头-工业插头插座【厂家】-温州罗曼电气| 众品地板网-地板品牌招商_地板装修设计_地板门户的首选网络媒体。 | 14米地磅厂家价价格,150吨地磅厂家价格-百科 | 金属管浮子流量计_金属转子流量计厂家-淮安润中仪表科技有限公司 | 东莞注册公司-代办营业执照-东莞公司注册代理记账-极刻财税 |