从特定规则的图片中提取轮廓

原创 精选
开发 后端
为了能更好的辅助商家进行360°的车辆外观采集,提升拍摄效率和拍摄质量,我们基于不同角度的外观示例图做了车源轮廓的识别和拍摄引导,同时后期也会根据识别出来的车源轮廓做背景模糊处理。如何从示例图中提取车源轮廓,就是当时我们遇到的第一个问题。

背景

目前,用户线上看车、选车方式千篇一律,为了能带给用户更好的看车体验,足不出户掌握360°车源细节,商家就需要对车源进行360°的外观采集。在开发车智赢+App外观采集拍摄组件的过程中发现,如果仅仅通过示例图来引导商家完成360°的车源外观采集,效果会因为拍摄距离、拍摄角度等问题难以得到保证。

实现思路

先将示例图片转成 Bitmap 点,根据图片已知的特定规则,从上、下、左、右四个方向进行扫描,扫描出四条边的点数组,四条边的点数组进行合并,最终生成一个完整有序的点数组,再将生成的点数组每个点相连,生成一个闭合图形的 BezierPath。

遇到的“坑”

四条边的点数组直接合并,然后每个点相连,生成一个闭合图形的 BezierPath,其实这个思路是有问题的,四条边的点数组中存在重复的点,直接相邻的点相连,出来的图形就会如上图所示。如果只是简单的排重,又会遇到数组中的点不连续等问题。所以这边又重新调整了实现的思路,先水平方向左、右两条边的点数组排重,然后垂直方向上、下两条边的点数组排重。再通过垂直方向上、下两条边的点数组去补偿左、右两条边的点数组,最后合并水平方向补偿完的左、右两条边的点数组。

最终的实现思路及核心的代码

  • 先将图片转成 Bitmap 点,根据特定的规则(非透明像素),从上、下、左、右四个方向进行扫描,扫描出四条边的点数组
NSMutableArray<NSValue *> *arrLeft = [NSMutableArray array];
NSMutableArray<NSValue *> *arrBottom = [NSMutableArray array];
NSMutableArray<NSValue *> *arrRight = [NSMutableArray array];
NSMutableArray<NSValue *> *arrTop = [NSMutableArray array];
for (NSInteger row = 0; row < h; row++) {
// 获取左边的点数组
for (NSInteger col = 0; col < w; col++) {
const uint8_t *pixel = &rgba[row * bytesPerRow + col * bytesPerPixel];
if (pixel[3] != 0x00) {
[arrLeft addObject:[NSValue valueWithCGPoint:CGPointMake(col, row)]];
break;
}
}
// 获取右边的点数组
for (NSInteger col = w - 1; col >= 0; col--) {
const uint8_t *pixel = &rgba[row * bytesPerRow + col * bytesPerPixel];
if (pixel[3] != 0x00) {
[arrRight insertObject:[NSValue valueWithCGPoint:CGPointMake(col, row)] atIndex:0];
break;
}
}
}
for (NSInteger col = 0; col < w; col++) {
// 获取下边的点数组
for (NSInteger row = h - 1; row >= 0; row--) {
const uint8_t *pixel = &rgba[row * bytesPerRow + col * bytesPerPixel];
if (pixel[3] != 0x00) {
[arrBottom addObject:[NSValue valueWithCGPoint:CGPointMake(col, row)]];
break;
}
}
// 获取上边的点数组
for (NSInteger row = 0; row < h; row++) {
const uint8_t *pixel = &rgba[row * bytesPerRow + col * bytesPerPixel];
if (pixel[3] != 0x00) {
[arrTop insertObject:[NSValue valueWithCGPoint:CGPointMake(col, row)] atIndex:0];
break;
}
}
}
  • 水平方向和垂直方向的点数组排重。
NSMutableSet *arrLeftSet = [NSMutableSet setWithArray:arrLeft];
NSMutableSet *arrRightSet = [NSMutableSet setWithArray:arrRight];
NSMutableSet *arrBottomSet = [NSMutableSet setWithArray:arrBottom];
NSMutableSet *arrTopSet = [NSMutableSet setWithArray:arrTop];
// 右边排重
[arrLeftSet intersectSet:arrRightSet];
for (int i = (int)(arrRight.count - 1); i >= 0; i--) {
@autoreleasepool {
NSValue *value = arrRight[i];
if ([arrLeftSet containsObject:value]) [arrRight removeObject:value];
}
}
// 顶部排重
[arrBottomSet intersectSet:arrTopSet];
for (int i = (int)(arrTop.count - 1); i >= 0; i--) {
@autoreleasepool {
NSValue *value = arrTop[i];
if ([arrBottomSet containsObject:value]) [arrTop removeObject:value];
}
}
  • 用上、下两条边的点数组去分别补偿左、右两条边的点数组(这边主要的思路就是通过找到重合点,然后将上、下两条边重合点之间的点补偿到左、右两条边的点数组中,补偿完的效果图如下)

// arrFixPoints 其实就是上边或者下边的点数组
NSMutableSet<NSValue *> *arrCoincidentPointsSet = [NSMutableSet setWithArray:arrFixPoints];
// arrOriginalPoints 其实就是左边或者右边的点数组
[arrCoincidentPointsSet intersectSet:[NSSet setWithArray:arrOriginalPoints]];
  • 取出左、右两条边点数组的头节点和尾节点元素,用上、下两条边的点数组去再次补偿,合并左、右两条边的点数组,将生成的点数组每个点相连,生成一个闭合图形的 BezierPath
// 考虑特殊情况: 如果是示例图中是个矩形,之前的补偿方案会存在问题,所以取出左边和右边底部的点,用下边的点数组再次进行补偿
NSMutableArray<NSValue *> *arrBottomOriginalPoints = [NSMutableArray array];
NSValue *valueLB = arrLeftPoints.lastObject;
NSValue *valueRB = arrRightPoints.firstObject;
if (valueLB && valueRB) {
[arrBottomOriginalPoints addObject:valueLB];
[arrBottomOriginalPoints addObject:valueRB];
arrBottomOriginalPoints = [self handleOriginalPoints:arrBottomOriginalPoints fixPoints:arrBottomPoints isDirectionRTL:YES];
if ([arrBottomOriginalPoints.firstObject isEqualToValue:valueLB] &&
[arrBottomOriginalPoints.lastObject isEqualToValue:valueRB] &&
arrBottomOriginalPoints.count > 2) {
for (int i = 1; i < (arrBottomOriginalPoints.count - 2); i++) {
@autoreleasepool {
[arrLeftPoints addObject:arrBottomOriginalPoints[i]];
}
}
}
}
// 取出左边和右边顶部的点,用上边的点数组再次进行补偿
NSMutableArray<NSValue *> *arrTopOriginalPoints = [NSMutableArray array];
NSValue *valueLT = arrLeftPoints.firstObject;
NSValue *valueRT = arrRightPoints.lastObject;
if (valueLT && valueRT) {
[arrTopOriginalPoints addObject:valueRT];
[arrTopOriginalPoints addObject:valueLT];
arrTopOriginalPoints = [self handleOriginalPoints:arrTopOriginalPoints fixPoints:arrTopPoints isDirectionRTL:NO];
if ([arrTopOriginalPoints.firstObject isEqualToValue:valueRT] &&
[arrTopOriginalPoints.lastObject isEqualToValue:valueLT] &&
arrTopOriginalPoints.count > 2) {
for (int i = 1; i < (arrTopOriginalPoints.count - 2); i++) {
@autoreleasepool {
[arrRightPoints addObject:arrTopOriginalPoints[i]];
}
}
}
}
NSMutableArray<NSValue *> *arrPoints = [NSMutableArray array];
if (arrLeftPoints.count) [arrPoints addObjectsFromArray:arrLeftPoints];
if (arrRightPoints.count) [arrPoints addObjectsFromArray:arrRightPoints];

效果展示

结束语

从图片中获取轮廓的方案有很多种,例如:OpenCV、Vision 框架、DeepLab 等。在我们这次的需求中,示例图是有特定规则的,所以获取轮廓并不是特别复杂。一步一步按着预想的思路就可以实现,所以最终我们没有采用额外引入其他框架来实现。当然上述获取轮廓的思路并不一定是最优解,后续还需要我们继续思考和优化。

责任编辑:庞桂玉 来源: 之家技术
相关推荐

2023-11-15 13:04:30

Python提取表格

2020-02-19 14:02:49

JavaScriptthis前端

2021-05-13 23:54:12

DockerDockerfile镜像

2020-07-08 07:54:03

PythonPDF数据

2022-11-23 10:31:54

2016-01-26 11:08:54

2021-09-04 23:45:40

机器学习语言人工智能

2023-04-27 07:06:09

Categraf夜莺

2013-04-01 11:14:56

IT大数据网络信息化

2019-09-04 11:09:38

物联网数据边缘

2021-03-15 21:50:22

Linux提取文本GUI工具

2023-11-29 11:30:17

PDF语言模型

2014-07-16 17:35:03

Android表单模型

2022-07-25 10:07:26

Python可视化技巧

2021-08-16 11:51:16

微软Windows 365Azure

2021-03-10 10:20:06

Linux文本命令

2014-04-15 10:24:42

OpenCV

2020-05-08 11:12:58

恶意软件PC安全终端安全

2024-05-22 07:57:34

2016-12-01 18:49:39

LinuxISO提取和复制文件
点赞
收藏

51CTO技术栈公众号