当前位置: 首页 > news >正文

Qt -使用OpenCV得到SDF

博客主页:【夜泉_ly】
本文专栏:【暂无】
欢迎点赞👍收藏⭐关注❤️

在这里插入图片描述

目录

  • cv::Mat
  • distanceTransform
  • 获得SDF

本文的目标,
是简单学习并使用OpenCV的相关函数,
并获得QImage的SDF(Signed Distance Field 有向距离场)
至于我要用QImage的SDF来做什么,嗯,以后再说。

cv::Mat

这个可以理解为OpenCV的QImage,嗯。
简单看看就行。

首先,Mat是可以存QImage并显示的。
其次,Mat是可以手搓的。
我们先手搓一个白底的黑色正方形,看看效果:

void Widget::on_pushButton_clicked()
{cv::Mat testMat(201, 201, CV_8UC1);for(int i = 0; i <= 200; i++){for(int j = 0; j <= 200; j++){// testMat[i][j] = 0; 这不行呢if((90 < i && i < 110) && (90 < j && j < 110)) {testMat.at<uchar>(i, j) = 0;   // 0   - 黑} else {testMat.at<uchar>(i, j) = 255; // 255 - 白}}}cv::imshow("testMat", testMat);
}

在这里插入图片描述
CV_8UC1 ,表示 8 位单通道,即灰度图,这个之后会用。

先试试把QImage转为灰度图:

cv::Mat image_to_CV_8UC1(const QImage& image){int w = image.width(),h = image.height();cv::Mat mat(w, h, CV_8UC1);for(int i = 0; i < w; i++){for(int j = 0; j < h; j++){ // 注: 只能设为全0,或全1mat.at<uchar>(i, j) = (image.pixelColor(i, j).alpha() == 0) ? 0 : 255;}}if(mat.empty()) return mat;cv::imshow("image", mat);return mat;
}

跑一下,发现图像被逆时针转了九十度。
cv::Mat 的这个构造,传的分别是 ( 行, 列, 类型)
QImage 的 width 是宽, height 是高,刚好反了:
在这里插入图片描述
改了顺序过后就对了:

cv::Mat image_to_CV_8UC1(const QImage& image){int r = image.height(), c = image.width();cv::Mat mat(r, c, CV_8UC1);for(int i = 0; i < r; i++){for(int j = 0; j < c; j++){ // 注意下面 image 是 (j, i)mat.at<uchar>(i, j) = (image.pixelColor(j, i).alpha() == 0) ? 0 : 255;}}if(mat.empty()) return mat;cv::imshow("image", mat);return mat;
}

在这里插入图片描述
再来试试翻转,我们需要把0变非0,把非0变0,
这个用条件判断加赋值有点慢,
不过,OpenCV 有现成的函数 bitwise_not :

void Widget::on_pushButton_2_clicked(bool checked)
{cv::Mat mat_front = image_to_CV_8UC1(_image);cv::Mat mat_back;cv::bitwise_not(mat_front, mat_back);if(checked) cv::imshow("image", mat_front);else cv::imshow("image", mat_back);
}

在这里插入图片描述在这里插入图片描述

distanceTransform

这个可以用来计算每个非零像素点到最近的零像素点的距离。
嗯,有点抽象,不过刚刚我们学会了手绘 Mat,
那我们可以先做个实验,看看这个距离到底是什么:

void Widget::on_pushButton_3_clicked()
{cv::Mat src(21, 21, CV_8UC1);for(int r = 0; r <= 20; r++){for(int c = 0; c <= 20; c++){if((5 < r && r < 15) && (5 < c && c < 15)) {src.at<uchar>(r, c) = 0;   // 0   - 黑} else {src.at<uchar>(r, c) = 255; // 255 - 白}}}cv::Mat dst;cv::distanceTransform(src, dst, cv::DIST_L2, 3);QString ret;for(int r = 0; r <= 20; r++){for(int c = 0; c <= 20; c++){float f = dst.at<float>(r, c);ret += QString::number(f).rightJustified(9, ' ');}   ret += "\n";}cv::imshow("dst", dst);std::cout << ret.toStdString();
}

哦,关于参数,
第一个是传入的 Mat,类型好像只能是 CV_8UC1,值只能是0或255。
第二个是得到的 Mat,只能用 at<float> 去取到它的值,这个值就是非0到最近0的距离。
第三个是距离的类型,这里用的 cv::DIST_L2,即欧几里得距离。
第四个是掩码,嗯,意义不明,取三就行。

打印结果如下:
在这里插入图片描述

  8.21576  7.80147  7.38718  6.97289  6.55859   6.1443  5.73001  5.73001  5.73001  5.73001  5.73001  5.73001  5.73001  5.73001  5.73001   6.1443  6.55859  6.97289  7.38718  7.80147  8.215767.80147  6.84647  6.43217  6.01788  5.60359   5.1893  4.77501  4.77501  4.77501  4.77501  4.77501  4.77501  4.77501  4.77501  4.77501   5.1893  5.60359  6.01788  6.43217  6.84647  7.801477.38718  6.43217  5.47717  5.06288  4.64859   4.2343  3.82001  3.82001  3.82001  3.82001  3.82001  3.82001  3.82001  3.82001  3.82001   4.2343  4.64859  5.06288  5.47717  6.43217  7.387186.97289  6.01788  5.06288  4.10788  3.69359   3.2793  2.86501  2.86501  2.86501  2.86501  2.86501  2.86501  2.86501  2.86501  2.86501   3.2793  3.69359  4.10788  5.06288  6.01788  6.972896.55859  5.60359  4.64859  3.69359  2.73859   2.3243     1.91     1.91     1.91     1.91     1.91     1.91     1.91     1.91     1.91   2.3243  2.73859  3.69359  4.64859  5.60359  6.558596.1443   5.1893   4.2343   3.2793   2.3243  1.36929 0.955002 0.955002 0.955002 0.955002 0.955002 0.955002 0.955002 0.955002 0.955002  1.36929   2.3243   3.2793   4.2343   5.1893   6.14435.73001  4.77501  3.82001  2.86501     1.91 0.955002        0        0        0        0        0        0        0        0        0 0.955002     1.91  2.86501  3.82001  4.77501  5.730015.73001  4.77501  3.82001  2.86501     1.91 0.955002        0        0        0        0        0        0        0        0        0 0.955002     1.91  2.86501  3.82001  4.77501  5.730015.73001  4.77501  3.82001  2.86501     1.91 0.955002        0        0        0        0        0        0        0        0        0 0.955002     1.91  2.86501  3.82001  4.77501  5.730015.73001  4.77501  3.82001  2.86501     1.91 0.955002        0        0        0        0        0        0        0        0        0 0.955002     1.91  2.86501  3.82001  4.77501  5.730015.73001  4.77501  3.82001  2.86501     1.91 0.955002        0        0        0        0        0        0        0        0        0 0.955002     1.91  2.86501  3.82001  4.77501  5.730015.73001  4.77501  3.82001  2.86501     1.91 0.955002        0        0        0        0        0        0        0        0        0 0.955002     1.91  2.86501  3.82001  4.77501  5.730015.73001  4.77501  3.82001  2.86501     1.91 0.955002        0        0        0        0        0        0        0        0        0 0.955002     1.91  2.86501  3.82001  4.77501  5.730015.73001  4.77501  3.82001  2.86501     1.91 0.955002        0        0        0        0        0        0        0        0        0 0.955002     1.91  2.86501  3.82001  4.77501  5.730015.73001  4.77501  3.82001  2.86501     1.91 0.955002        0        0        0        0        0        0        0        0        0 0.955002     1.91  2.86501  3.82001  4.77501  5.730016.1443   5.1893   4.2343   3.2793   2.3243  1.36929 0.955002 0.955002 0.955002 0.955002 0.955002 0.955002 0.955002 0.955002 0.955002  1.36929   2.3243   3.2793   4.2343   5.1893   6.14436.55859  5.60359  4.64859  3.69359  2.73859   2.3243     1.91     1.91     1.91     1.91     1.91     1.91     1.91     1.91     1.91   2.3243  2.73859  3.69359  4.64859  5.60359  6.558596.97289  6.01788  5.06288  4.10788  3.69359   3.2793  2.86501  2.86501  2.86501  2.86501  2.86501  2.86501  2.86501  2.86501  2.86501   3.2793  3.69359  4.10788  5.06288  6.01788  6.972897.38718  6.43217  5.47717  5.06288  4.64859   4.2343  3.82001  3.82001  3.82001  3.82001  3.82001  3.82001  3.82001  3.82001  3.82001   4.2343  4.64859  5.06288  5.47717  6.43217  7.387187.80147  6.84647  6.43217  6.01788  5.60359   5.1893  4.77501  4.77501  4.77501  4.77501  4.77501  4.77501  4.77501  4.77501  4.77501   5.1893  5.60359  6.01788  6.43217  6.84647  7.801478.21576  7.80147  7.38718  6.97289  6.55859   6.1443  5.73001  5.73001  5.73001  5.73001  5.73001  5.73001  5.73001  5.73001  5.73001   6.1443  6.55859  6.97289  7.38718  7.80147  8.21576

额,似乎有偏差?感觉明明该是整数的点却是小数。
不过偏差不大,能用就行。

试试把掩码改为5,听说这个精确一些:
在这里插入图片描述

      8.4   7.7969   7.1938   6.5907   6.3938   6.1969        6        6        6        6        6        6        6        6        6   6.1969   6.3938   6.5907   7.1938   7.7969      8.47.7969        7   6.3969   5.7938   5.3938   5.1969        5        5        5        5        5        5        5        5        5   5.1969   5.3938   5.7938   6.3969        7   7.79697.1938   6.3969      5.6   4.9969   4.3938   4.1969        4        4        4        4        4        4        4        4        4   4.1969   4.3938   4.9969      5.6   6.3969   7.19386.5907   5.7938   4.9969      4.2   3.5969   3.1969        3        3        3        3        3        3        3        3        3   3.1969   3.5969      4.2   4.9969   5.7938   6.59076.3938   5.3938   4.3938   3.5969      2.8   2.1969        2        2        2        2        2        2        2        2        2   2.1969      2.8   3.5969   4.3938   5.3938   6.39386.1969   5.1969   4.1969   3.1969   2.1969      1.4        1        1        1        1        1        1        1        1        1      1.4   2.1969   3.1969   4.1969   5.1969   6.19696        5        4        3        2        1        0        0        0        0        0        0        0        0        0        1        2        3        4        5        66        5        4        3        2        1        0        0        0        0        0        0        0        0        0        1        2        3        4        5        66        5        4        3        2        1        0        0        0        0        0        0        0        0        0        1        2        3        4        5        66        5        4        3        2        1        0        0        0        0        0        0        0        0        0        1        2        3        4        5        66        5        4        3        2        1        0        0        0        0        0        0        0        0        0        1        2        3        4        5        66        5        4        3        2        1        0        0        0        0        0        0        0        0        0        1        2        3        4        5        66        5        4        3        2        1        0        0        0        0        0        0        0        0        0        1        2        3        4        5        66        5        4        3        2        1        0        0        0        0        0        0        0        0        0        1        2        3        4        5        66        5        4        3        2        1        0        0        0        0        0        0        0        0        0        1        2        3        4        5        66.1969   5.1969   4.1969   3.1969   2.1969      1.4        1        1        1        1        1        1        1        1        1      1.4   2.1969   3.1969   4.1969   5.1969   6.19696.3938   5.3938   4.3938   3.5969      2.8   2.1969        2        2        2        2        2        2        2        2        2   2.1969      2.8   3.5969   4.3938   5.3938   6.39386.5907   5.7938   4.9969      4.2   3.5969   3.1969        3        3        3        3        3        3        3        3        3   3.1969   3.5969      4.2   4.9969   5.7938   6.59077.1938   6.3969      5.6   4.9969   4.3938   4.1969        4        4        4        4        4        4        4        4        4   4.1969   4.3938   4.9969      5.6   6.3969   7.19387.7969        7   6.3969   5.7938   5.3938   5.1969        5        5        5        5        5        5        5        5        5   5.1969   5.3938   5.7938   6.3969        7   7.79698.4   7.7969   7.1938   6.5907   6.3938   6.1969        6        6        6        6        6        6        6        6        6   6.1969   6.3938   6.5907   7.1938   7.7969      8.4

不过精确当然也有代价,比如运算速度肯定不如 3。
我们把数据改大,测测效率:

#include <QElapsedTimer>
void mask_3_VS_5(const cv::Mat& src, int mask)
{QElapsedTimer timer;timer.start();for (int i = 0; i < 10; i++) {cv::Mat dst;cv::distanceTransform(src, dst, cv::DIST_L2, mask);}qint64 elapsed = timer.nsecsElapsed();qDebug() << "mask = " << mask << ", Elapsed time:" << elapsed / 1000000.0 << "ms";
}void Widget::on_pushButton_4_clicked()
{cv::Mat src(10000, 10000, CV_8UC1);for(int r = 0; r < 10000; r++) for(int c = 0; c < 10000; c++)if((rand() % 100) < 50) src.at<uchar>(r, c) = 0;else src.at<uchar>(r, c) = 255;mask_3_VS_5(src, 5);mask_3_VS_5(src, 3);
}

10000 x 10000 的图,跑 10 次, 打印结果如下:
嗯,截图为证:
在这里插入图片描述
精度高的运行速度还更快!竟然还有这种好事😋。

获得SDF

我们已经得到了非0点到最近0点距离。
但SDF要求有正有负,即把图分为2份,一个外,一个内:
在这里插入图片描述
我们拿到一个QImage,把它转为 Mat。
其中,alpha,即透明度为 0,即纯黑的我们称为 内,其他的称为 外。
额,算了,换个说法,黑色就是障碍物,其他就是背景。
障碍物内的值为 负,外的值为 正。
那么我们拿着两个一减就得到了SDF:

// image 中, alpha为 0 的表示背景
bool image_to_SDF(const QImage& image, cv::Mat* SDF)
{int r = image.height(), c = image.width();cv::Mat mat_background(r, c, CV_8UC1);for(int i = 0; i < r; i++){for(int j = 0; j < c; j++){mat_background.at<uchar>(i, j) = (image.pixelColor(j, i).alpha() == 0) ? 0 : 255;}}if(mat_background.empty()){qDebug() << "传了个空的image计算SDF";return false;} else {qDebug() << "准备计算sdf, 地图大小: rows = " << r << ", cols = " << c;}cv::Mat mat_background_dst; // 这里面为 0 的是障碍物, 为正的是背景cv::distanceTransform(mat_background, mat_background_dst, cv::DIST_L2, 5);cv::Mat mat_front(r, c, CV_8UC1); // 这里面为 0 的是障碍物cv::Mat mat_front_dst; // 这里面为 0 的是背景, 为正的是障碍物cv::bitwise_not(mat_background, mat_front);cv::distanceTransform(mat_front, mat_front_dst, cv::DIST_L2, 5);*SDF = mat_background_dst - mat_front_dst;return true;
}

再简单测试一下:

void Widget::on_pushButton_5_clicked()
{QImage image(21, 21, QImage::Format_ARGB32);for(int i = 0; i <= 20; i++){for(int j = 0; j <= 20; j++){if((5 < i && i < 15) && (5 < j && j < 15)) {image.setPixelColor(i, j, QColor(0, 0, 0, 0)); // 透明} else {image.setPixelColor(i, j, QColor(0, 0, 0, 255));}}}cv::Mat sdf;image_to_SDF(image, &sdf);QString ret;for(int r = 0; r < sdf.rows; r++){for(int c = 0; c < sdf.cols; c++){float f = sdf.at<float>(r, c);ret += QString::number(f).rightJustified(9, ' ');}   ret += "\n";}cv::imshow("sdf", sdf);std::cout << ret.toStdString();
}

输出结果:
在这里插入图片描述
不错,和预期的一致。

然后我们再测测性能,用 10000 x 10000 的 image 跑它10次:

void Widget::on_pushButton_6_clicked()
{QImage image(10000, 10000, QImage::Format_ARGB32);for(int i = 0; i < 10000; i++){for(int j = 0; j < 10000; j++){int cur = rand() % 500 + 1;image.setPixelColor(i, j, QColor(0, 0, 0, cur < 255 ? cur : 0));}}QElapsedTimer timer;timer.start();qDebug() << "begin:" << timer.nsecsElapsed() / 1000000.0 << "ms";for (int i = 0; i < 10; i++) {cv::Mat sdf;image_to_SDF(image, &sdf);qDebug() << "第" << i << "次计算完成, time : " << timer.nsecsElapsed() / 1000000.0 << "ms";}qDebug() << "end:" << timer.nsecsElapsed() / 1000000.0 << "ms";
}

在这里插入图片描述
有点慢,但看了下,主要慢在我们每次都构造了个 cv::Mat sdf,
这里得判断 10000 x 10000次 image 是不是透明的。
那么优化方案就很明显了,我们别每次重新构造 cv::Mat 了,
我们在创建、修改 QImage时,顺便带一个 cv::Mat,
算 SDF 时,直接使用这个 cv::Mat 就行。

bool get_SDF(const cv::Mat& background, cv::Mat* SDF)
{if(background.empty()){qDebug() << "传了个空的background计算SDF";return false;} else {qDebug() << "准备计算sdf, 地图大小: rows = " << background.rows << ", cols = " << background.cols;}cv::Mat background_dst; // 这里面为 0 的是障碍物, 为正的是背景cv::distanceTransform(background, background_dst, cv::DIST_L2, 5);cv::Mat front(background.rows, background.cols, CV_8UC1); // 这里面为 0 的是障碍物cv::Mat front_dst; // 这里面为 0 的是背景, 为正的是障碍物cv::bitwise_not(background, front);cv::distanceTransform(front, front_dst, cv::DIST_L2, 5);*SDF = background_dst - front_dst;return true;
}void Widget::on_pushButton_7_clicked()
{QImage background_image(10000, 10000, QImage::Format_ARGB32);cv::Mat background_mat(10000, 10000, CV_8UC1);for(int r = 0; r < 10000; r++){for(int c = 0; c < 10000; c++){int alpha = (rand() % 2 == 0) ? 0 : (rand() % 255 + 1); // 差不多 50% 是 0background_image.setPixelColor(c, r, QColor(0, 0, 0, alpha));background_mat.at<uchar>(r, c) = (alpha == 0) ? 0 : 255;}}QElapsedTimer timer;timer.start();qDebug() << "begin:" << timer.nsecsElapsed() / 1000000.0 << "ms";for (int i = 0; i < 10; i++) {cv::Mat sdf;get_SDF(background_mat, &sdf);qDebug() << "第" << i << "次计算完成, time : " << timer.nsecsElapsed() / 1000000.0 << "ms";}qDebug() << "end:" << timer.nsecsElapsed() / 1000000.0 << "ms";
}

在这里插入图片描述
嘿嘿,非常不错,效率高多了。
至于加载,那不是我们关心的,毕竟每个游戏登录时都会让你等半天。

嗯,不过我们可以看看创建一个 10000 x 10000 的 image要多久,以及带上一个 cv::Mat又要多久:

void Widget::on_pushButton_8_clicked()
{QElapsedTimer timer;timer.start();qDebug() << "begin:" << timer.nsecsElapsed() / 1000000.0 << "ms";for(int i = 0; i < 1; i++){QImage background_image(10000, 10000, QImage::Format_ARGB32);for(int r = 0; r < 10000; r++){for(int c = 0; c < 10000; c++){int alpha = (rand() % 2 == 0) ? 0 : (rand() % 255 + 1); // 差不多 50% 是 0background_image.setPixelColor(c, r, QColor(0, 0, 0, alpha));}}}qDebug() << "end:" << timer.nsecsElapsed() / 1000000.0 << "ms";timer.restart();qDebug() << "begin:" << timer.nsecsElapsed() / 1000000.0 << "ms";for(int i = 0; i < 1; i++){QImage background_image(10000, 10000, QImage::Format_ARGB32);cv::Mat background_mat(10000, 10000, CV_8UC1);for(int r = 0; r < 10000; r++){for(int c = 0; c < 10000; c++){int alpha = (rand() % 2 == 0) ? 0 : (rand() % 255 + 1); // 差不多 50% 是 0background_image.setPixelColor(c, r, QColor(0, 0, 0, alpha));background_mat.at<uchar>(r, c) = (alpha == 0) ? 0 : 255;}}}qDebug() << "end:" << timer.nsecsElapsed() / 1000000.0 << "ms";
}

在这里插入图片描述
可以看到差不了多久,说明 cv::Mat 的创建还是很快的。

在这里插入图片描述


希望本篇文章对你有所帮助!并激发你进一步探索编程的兴趣!
本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!让我们一起学习,共同进步!

http://www.xdnf.cn/news/749035.html

相关文章:

  • thinkpad T-440p 2025.05.31
  • YOLOv10速度提升与参数缩减解析2025.5.31
  • 华为OD机试_2025 B卷_静态扫描(Python,100分)(附详细解题思路)
  • SAR ADC 同步逻辑设计
  • 【CBAP50技术手册】#31 Observation(观察法):BA(业务分析师)的“现场侦探术”
  • Kerberos面试内容整理-Kerberos 与 LDAP/Active Directory 的集成
  • 关于TongWeb数据源兼容mysql驱动的注意事项
  • 基于晶体塑性有限元(CPFEM)的钛合金圆棒拉伸过程模拟
  • 元胞自动机(Cellular Automata, CA)
  • 题海拾贝:P8598 [蓝桥杯 2013 省 AB] 错误票据
  • SHELL命令资料
  • C++类设计新思路:借鉴Promise链式调用的封装模式
  • CodeTop100 Day18
  • 【Python进阶】元编程、并发
  • @PathVariable注解-补充
  • (附代码)自定义 LangChain 文档分割器,深入探索 LangChain 文档分割策略与应用
  • 硬件开发全解:从入门教程到实战案例与丰富项目资源
  • 深入理解设计模式之解释器模式
  • Vue-过滤器
  • C++语法系列之模板进阶
  • 青柠日记:记录美好,守护隐私
  • RL 基础 (待补充)
  • 【Python Cookbook】文件与 IO(一)
  • Redis--缓存工具封装
  • 【PhysUnits】15.6 引入P1后的左移运算(shl.rs)
  • 佳能 Canon G3030 Series 打印机信息
  • 【C语言练习】075. 使用C语言访问硬件资源
  • [LitCTF 2024]浏览器也能套娃?
  • [学习] RTKlib 实用工具介绍
  • JDK17 与JDK8 共同存在一个电脑上