6

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

 2 years ago
source link: https://www.51cto.com/article/717014.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

原创
作者: 之家技术 2022-08-24 15:57:17
为了能更好的辅助商家进行360°的车辆外观采集,提升拍摄效率和拍摄质量,我们基于不同角度的外观示例图做了车源轮廓的识别和拍摄引导,同时后期也会根据识别出来的车源轮廓做背景模糊处理。如何从示例图中提取车源轮廓,就是当时我们遇到的第一个问题。
f74177b15682be54c52459e44b4dffacb85f2d.jpg

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

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

遇到的“坑”

946c04269e3fd540d8e4630777b30e3e718954.jpg
b98b7d3663dee5b1ada341613429318db25527.jpg

四条边的点数组直接合并,然后每个点相连,生成一个闭合图形的 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];
   }
}
  • 用上、下两条边的点数组去分别补偿左、右两条边的点数组(这边主要的思路就是通过找到重合点,然后将上、下两条边重合点之间的点补偿到左、右两条边的点数组中,补偿完的效果图如下)
7318a824162b2635f57433dc5aee829517124f.jpg
b847105451a27891694726fc731dda91a04859.jpg
// 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];
487123434a93418588f8642d4423678afd98e6.jpg
9262fb732ce1bd858b17297e9a1dfb2fe9c56d.jpg
87378983200584306e8475e82e41bb930b2cd3.jpg
b4edb6e04502cd824c71275f5dbaa46254b6ec.jpg

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

责任编辑:庞桂玉 来源: 之家技术

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK