基礎ユニット2(イメージメディア) 第11回(2020/12/17)

関連サイトと資料

多角形ROIの頂点をマウスで入力し、CSVファイルに保存する(プログラムは未完成なので、完成させてください)

selectVertices.py
import cv2
import math
   
# 2点間の距離を求める
def distance(x1, y1, x2, y2):
    dx = x1 - x2
    dy = y1 - y2
    return math.sqrt(dx*dx + dy*dy)
  
# 1行目には横幅、高さを、2行目以降には各頂点のx座標、y座標を、CSV形式で保存する
def saveAsCSV(w, h, listX, listY, csv_filename):
    with open(csv_filename, 'w') as f:
        f.write('{0},{1}\n'.format(w, h))
        for x, y in zip(listX, listY):
            f.write('{0},{1}\n'.format(x, y))
  
# マウスコールバック関数
def Mouse2(event, x, y, flags, param):
    global img, list_x, list_y
    # 中身を考えてください

   
# メインルーチン
if __name__ == '__main__':
    list_x = []
    list_y = []
  
    target = 'sazaebon1.jpg'
    img = cv2.imread(target, cv2.IMREAD_COLOR)
    height, width, _ = img.shape
  
    cv2.namedWindow('image')
    cv2.setMouseCallback('image', Mouse2)
  
    while True:
        cv2.imshow('image', img)
        key = cv2.waitKey(17)
        if key == ord('q'):
            break
        elif key == ord('s'):
            roi_csv_filename = 'sazaebon1_roi.csv'
            saveAsCSV(width, height, list_x, list_y, roi_csv_filename)
  
    cv2.destroyAllWindows()
    

sazaebon1.jpg

CSVファイルからマスク画像を生成

createMaskImage.py
import csv
import cv2
from PolygonRoi import PolygonRoi
  
if __name__ == '__main__':
    target_csv = 'sazaebon1_roi.csv'
    mask_img_name = 'saazebon1_mask.png'
    list_x = []
    list_y = []
  
    with open(target_csv) as f:
        reader = csv.reader(f)
        i = 0
        for row in reader:
            if i == 0:
                width = int(row[0])
                height = int(row[1])
            else:
                list_x.append(int(row[0]))
                list_y.append(int(row[1]))
            i += 1
      
    roi = PolygonRoi(list_x, list_y)
    mask_img = roi.createMaskImage(width, height)
    cv2.imwrite(mask_img_name, mask_img)
    

PolygonRoi.py
import sys
import numpy as np
  
class PolygonRoi:
    def __init__(self, listX, listY):
        self.np = len(listX)
        self.xp = listX
        self.yp = listY
        self.max_x = max(self.xp)
        self.min_x = min(self.xp)
        self.max_y = max(self.yp)
        self.min_y = min(self.yp)        
  
    @classmethod
    def area2(cls, x1, y1, x2, y2, x3, y3):
        return (x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1)
    
    @classmethod
    def between(cls, x1, y1, x2, y2, x3, y3):
        if x1 != x2:
            return (x1 <= x3 and x3 <= x2) or (x1 >= x3 and x3 >= x2)
        else:
            return (y1 <= y3 and y3 <= y2) or (y1 >= y3 and y3 >= y2)
    
    @classmethod
    def linesIntersect(cls, x1, y1, x2, y2, x3, y3, x4, y4):
        aa1 = PolygonRoi.area2(x1, y1, x2, y2, x3, y3)
        aa2 = PolygonRoi.area2(x1, y1, x2, y2, x4, y4)
        aa3 = PolygonRoi.area2(x3, y3, x4, y4, x1, y1)
        aa4 = PolygonRoi.area2(x3, y3, x4, y4, x2, y2)
    
        if aa1 == 0.0:
            if PolygonRoi.between(x1, y1, x2, y2, x3, y3):
                return True
            else:
                if PolygonRoi.area2(x1, y1, x2, y2, x4, y4) == 0.0:
                    a1 = PolygonRoi.between(x3, y3, x4, y4, x1, y1)
                    b1 = PolygonRoi.between(x3, y3, x4, y4, x2, y2)
                    return a1 or b1
                else:
                    return False
        elif aa2 == 0.0:
            return PolygonRoi.between(x1, y1, x2, y2, x4, y4)
        
        if aa3 == 0.0:
            if PolygonRoi.between(x3, y3, x4, y4, x1, y1):
                return True
            else:
                if PolygonRoi.area2(x3, y3, x4, y4, x2, y2) == 0.0:
                    a2 = PolygonRoi.between(x1, y1, x2, y2, x3, y3)
                    b2 = PolygonRoi.between(x1, y1, x2, y2, x4, y4)
                    return a2 or b2
                else:
                    return False
        elif aa4 == 0.0:
            return PolygonRoi.between(x3, y3, x4, y4, x2, y2)
        else:
            return ((aa1 > 0.0) ^ (aa2 > 0.0)) and ((aa3 > 0.0) ^ (aa4 > 0.0))
  
    def evaluateCrossings(self, x, y, distance):
        crossings = 0
        epsilon = 1e-7
        x0 = self.xp[0] - x
        y0 = self.yp[0] - y
        for i in range(1, self.np):
            x1 = self.xp[i] - x
            y1 = self.yp[i] - y
            if y0 == 0.0:
                y0 -= epsilon
            
            if y1 == 0.0:
                y1 -= epsilon
            
            if y0 * y1 < 0:
                if PolygonRoi.linesIntersect(x0, y0, x1, y1, epsilon, 0.0, distance, 0.0):
                    crossings += 1
            
            x0 = self.xp[i] - x
            y0 = self.yp[i] - y
        
        x1 = self.xp[0] - x
        y1 = self.yp[0] - y
        if y0 == 0.0:
            y0 -= epsilon
        
        if y1 == 0.0:
            y1 -= epsilon
        
        if y0 * y1 < 0:
            if PolygonRoi.linesIntersect(x0, y0, x1, y1, epsilon, 0.0, distance, 0.0):
                crossings += 1
        
        return crossings
    
    def contains(self, x, y):
        if x < self.min_x or x > self.max_x or y < self.min_y or y > self.max_y:
            return False
        
        return (self.evaluateCrossings(x, y, sys.float_info.max / 10.0) & 1) != 0
    
    def createMaskImage(self, width, height):
        img = np.zeros((height, width), np.uint8)
        for y in range(self.min_y, self.max_y + 1):
            for x in range(self.min_x, self.max_x + 1):
                if self.contains(x, y):
                    img[y, x] = 255
          
        return img
    

sazaebon1_roi.csv
497,500
220,98
192,110
172,109
160,101
116,134
99,129
85,138
84,228
106,283
141,322
196,341
261,344
327,327
375,299
401,269
409,228
407,171
410,146
399,133
375,140
337,108
313,109
284,97
255,106
    

sazaebon1_mask.png

マスク画像を用いたモザイク処理

partialMosaic.py
import cv2
from mosaic import Mosaic
  
if __name__ == '__main__':
    target = 'sazaebon1.jpg'
    mask_img_name = 'saazebon1_mask.png'
    save_name = 'sazaebon1_partial_mosaic.png'
  
    org_img = cv2.imread(target, cv2.IMREAD_COLOR)
    mask_img = cv2.imread(mask_img_name, cv2.IMREAD_GRAYSCALE)
  
    mosaic_img = Mosaic(org_img, 15)
    partial_mosaic_img = org_img.copy()
    partial_mosaic_img[mask_img == 255] = mosaic_img[mask_img == 255]
  
    cv2.imwrite(save_name, partial_mosaic_img)
    

mosaic.py
import cv2
  
def Mosaic(orgImg, bs):
    h, w, _ = orgImg.shape
    img2 = cv2.resize(orgImg, None, fx=1/bs, fy=1/bs, interpolation=cv2.INTER_NEAREST)
    img3 = cv2.resize(img2, (w, h), interpolation=cv2.INTER_NEAREST)
    return img3
    

sazaebon1_partial_mosaic.png